Свободный NHibernate (с автоматическим отображением), не сохраняющий значения таблицы соединений во многих-ко-многим

Я не совсем эксперт NHibernate, так что это может быть непонимание в этом отделе. У меня есть две простые сущности с отношением "многие ко многим"

public class Category
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual bool Visible { get; set; }

    public virtual IList<Product> Products { get; set; }
}

и

public class Product
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual decimal Price { get; set; }
    public virtual bool Visible { get; set; }
    public virtual IList<Category> Categories { get; set; }
}

Моя конфигурация

    public static void BuildFactory()
    {
        _factory = Fluently.Configure()
            .Database(
                MsSqlConfiguration
                    .MsSql2008
                    .ConnectionString(_connectionString)
                    .ShowSql()
            )
            .Mappings(m =>
                m.AutoMappings.Add(
                    AutoMap.AssemblyOf<Database>()
                        .Where(t => t.Namespace == "FancyStore.Models.Catalog")
                        .Override<Product>(k => 
                            k.HasManyToMany(x => x.Categories)
                                .Table("ProductsToCategories")
                                    .ParentKeyColumn("Product_id")
                                    .ChildKeyColumn("Category_id")
                                .Cascade.AllDeleteOrphan())
                        .Override<Category>(k =>
                            k.HasManyToMany(x => x.Products)
                                .Table("ProductsToCategories")
                                    .ParentKeyColumn("Category_id")
                                    .ChildKeyColumn("Product_id")
                                .Cascade.AllDeleteOrphan().Inverse())
                )
            )
            .ExposeConfiguration(SetConfiguration)
            .BuildConfiguration()
            .BuildSessionFactory();
    }

Итак, я начал без переопределений (добавил их позже после того, как это предложили некоторые поисковые запросы), и создаю свою схему с помощью ExportSchema. ExportSchema знает об отношении «многие ко многим» (даже без переопределений), поскольку сгенерировал таблицу соединений (ProductsToCategories).

Я хотел добавить несколько простых тестовых данных, поэтому сделал это так

        using (var db = new Repository<Category>())
        {
            for (int i = 0; i < 6; i++)
            {
                var cat = new Category
                {
                    Name = "Things" + i,
                    Description = "Things that are things",
                    Visible = true,
                    Products = new List<Product>()
                };

                for (int k = 0; k < 6; k++)
                {
                    var prod = new Product
                    {
                        Name = "Product" + k,
                        Description = "Product for " + cat.Name,
                        Visible = true,
                        Categories = new List<Category>(new[] { cat })
                    };
                    cat.Products.Add(prod);
                }
                db.Save(cat);
            }
        }

Это правильно сохраняет продукты и категории, но не сохраняет отношения в соединительной таблице. Глядя на sql, генерируемый вызовом category.Products, он фактически ищет в этой объединенной таблице продукты (и не может их найти, поскольку она пуста)

Любая помощь будет принята с благодарностью :-)

РЕДАКТИРОВАТЬ: удалено обратное, проблема все еще возникает :(

РЕДАКТИРОВАТЬ2:

Вот файлы сопоставления для каталога и продукта (примечание: название глупое, но это всего лишь быстрый прототип)

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="FancyStore.Models.Catalog.Product, FancyStore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Product`">
    <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="Name" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Name" />
    </property>
    <property name="Description" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Description" />
    </property>
    <property name="Price" type="System.Decimal, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Price" />
    </property>
    <property name="Visible" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Visible" />
    </property>
    <bag cascade="all-delete-orphan" inverse="true" name="Categories" table="ProductsToCategories">
      <key>
        <column name="Product_id" />
      </key>
      <many-to-many class="FancyStore.Models.Catalog.Category, FancyStore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <column name="Category_id" />
      </many-to-many>
    </bag>
  </class>
</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="FancyStore.Models.Catalog.Category, FancyStore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Category`">
    <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="Name" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Name" />
    </property>
    <property name="Description" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Description" />
    </property>
    <property name="Visible" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Visible" />
    </property>
    <bag cascade="all-delete-orphan" name="Products" table="ProductsToCategories">
      <key>
        <column name="Category_id" />
      </key>
      <many-to-many class="FancyStore.Models.Catalog.Product, FancyStore, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <column name="Product_id" />
      </many-to-many>
    </bag>
  </class>
</hibernate-mapping>

person Matt Briggs    schedule 08.01.2010    source источник
comment
Что делает пропущенный SetConfiguration метод? Кроме того, для отладки может помочь увидеть, какие сопоставления генерирует Fluent NHibernate. Добавьте это: .Mappings (m = ›m.AutoMappings.Add (...). ExportTo (@C: \ mappings)), чтобы заставить его генерировать файлы сопоставления.   -  person Daniel Schilling    schedule 12.01.2010
comment
SetConfiguration просто получает ссылку на объект конфигурации, который я использую в других местах для использования некоторых материалов NH DDL. Я экспортирую карту и отправлю ее, когда у меня будет минутка   -  person Matt Briggs    schedule 12.01.2010
comment
Удалось ли решить проблему?   -  person Egor Pavlikhin    schedule 13.05.2010
comment
@HeavyWave: Нет, в итоге просто бросил fluent-nh. Немного обидно, но что ты собираешься делать ...   -  person Matt Briggs    schedule 13.05.2010
comment
Я понял это. Вы должны установить одну связь как Inverse () и сохранить другую сущность, добавить ссылки на обе сущности (prod.Categories.Add () + cat.Products.Add ()) и зафиксировать транзакцию. Если вы не зафиксируете запрос для таблицы соединения, не будет сгенерирован.   -  person Egor Pavlikhin    schedule 13.05.2010


Ответы (4)


Я могу ошибаться, но похоже, что вы отметили отношения как «обратные» для обеих сторон.

Вы должны сделать это только для одной стороны отношений и обязательно позвонить Save на другой стороне.

Кроме того, я считаю, что отношения должны быть действительными с обеих сторон, прежде чем вы сохраните (добавьте cat к prod.Categories в дополнение к тому, что вы уже делаете).

По общему признанию, мое понимание таких отношений нечеткое, но это то, к чему пришли мои исследования, проведенные за несколько месяцев (и решившие некоторые из моих собственных проблем).

person Sapph    schedule 08.01.2010
comment
ive получил Categories = new List ‹Category› (new [] {cat}) в моем инициализаторе продукта, что является сокращением от Make a new list ‹category›, и добавить в него новый массив категорий с одним элементом (cat) . Я попытался удалить обе инверсии и добавить их к той или иной стороне, но ничего не сработало. :( Спасибо за ответ, я почти уверен, что хочу инвертировать категорию, поэтому я просто вызываю экономию на продукте. (Как только я разберусь с этой проблемой) - person Matt Briggs; 08.01.2010
comment
Ой, мне очень жаль, надо было внимательнее прочитать ваш код (пропустил инициализатор). Однако по касательной можно было просто выполнить new List<Category>() { cat } и пропустить часть массива. - person Sapph; 08.01.2010

@ Мэтт Бриггс, я понял. Вы должны установить одну связь как Inverse () и сохранить другую сущность, добавить ссылки на обе сущности (prod.Categories.Add () + cat.Products.Add ()) и зафиксировать транзакцию. Если вы не зафиксируете запрос для таблицы соединения, не будет сгенерирован. - HeavyWave 13 мая в 12:43

person Matt Briggs    schedule 17.09.2010

У вас обратная сторона ассоциации. Инверсия означает «другая сторона управляет этой ассоциацией».

Вам нужно либо удалить инверсию в категории и поместить ее в продукт, либо изменить продукт, чтобы он был `` владельцем '', например:

//make sure to add it to BOTH sides of the association
product.Categories.Add(cat);
cat.Products.Add(product);

db.Save(cat);  //if Product has Inverse
/* or */
db.Save(prod); //if Category has Inverse
person Ben Scheirman    schedule 12.01.2010

А как насчет того, чтобы вы применили беглое сопоставление для продукта и категории сначала, прежде чем автоматическое сопоставление. Например,

public class Product
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual decimal Price { get; set; }
    public virtual bool Visible { get; set; }
    public virtual IList<Category> Categories { get; set; }

    public Product()
    {
        Categories = new List<Category>();
    }
}

public class ProductMap : ClassMap<Product>
{
    Id(x => x.Id);
    Map(x => x.Name);
    Map(x => x.Description);
    Map(x => x.Price);
    Map(x => x.Visible);
    HasManyToMany(x => x.Categories)
      .Table("ProductsToCategories")
      .ParentKeyColumn("Product_id")
      .ChildKeyColumn("Category_id")
      .Cascade.AllDeleteOrphan();
}

public class Category
{
    public virtual int Id { get; private set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual bool Visible { get; set; }
    public virtual IList<Product> Products { get; set; }

    public Category()
    {
        Products = new List<Product>();
    }
}

public class CategoryMap : ClassMap<Category>
{
    Id(x => x.Id);
    Map(x => x.Name);
    Map(x => x.Description);
    Map(x => x.Visible);
    HasManyToMany(x => x.Products)
      .Table("ProductsToCategories")
      .ParentKeyColumn("Category_id")
      .ChildKeyColumn("Product_id")
      .Cascade.AllDeleteOrphan().Inverse();
}

с конфигурацией,

public static void BuildFactory()
{
    _factory = Fluently.Configure()
        .Database(
            MsSqlConfiguration
                .MsSql2008
                .ConnectionString(_connectionString)
                .ShowSql()
        )
        .Mappings(m => {
            m.FluentMappings.AddFromAssemblyOf<Database>();
            m.AutoMappings.Add(
              AutoMap.AssemblyOf<Database>()
              .Where(t => t.Namespace == "FancyStore.Models.Catalog"));
        })
        .ExposeConfiguration(SetConfiguration)
        .BuildConfiguration()
        .BuildSessionFactory();
}
person Ajw    schedule 18.01.2010