Fluente NHibernate (com Automapping) não salvar juntar-se os valores da tabela em muitos-para-muitos

StackOverflow https://stackoverflow.com/questions/2028905

Pergunta

Eu não sou exatamente um especialista NHibernate, então esta pode ser uma falta de compreensão nesse departamento. Eu tenho duas entidades simples com um muitos-para-muitos relação

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; }
}

e

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; }
}

A minha configuração é

    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();
    }

Então eu comecei sem as substituições (adicionado-los mais tarde depois de algum googling sugeriu ele), e estou gerando meu esquema com ExportSchema. ExportSchema sabe sobre o relacionamento muitos para muitos (mesmo sem as substituições), porque gerou uma junção mesa (ProductsToCategories).

Eu queria acrescentar alguns dados simples teste, então eu fiz isso como este

        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);
            }
        }

Isso economiza produtos e categorias corretamente, mas não salva qualquer um dos relacionamentos na tabela de associação. Olhando para o sql uma chamada para category.Products gera, é realmente olhando nessa unir a tabela de produtos (e não consigo encontrar qualquer, uma vez que está vazio)

Qualquer ajuda seria muito apreciada: -)

Editar : Removido o inverso, problema ainda acontecendo: (

EDIT2 :

Aqui estão os arquivos de mapeamento para catálogo e produto (nota: o nome é meio bobo, mas isso é apenas uma espécie de protótipo-ish rápida do negócio)

<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>
Foi útil?

Solução 2

@ Matt Briggs, eu percebi isso. Você tem que definir uma relação como Inverse () e salvar a outra entidade, adicionar referências a ambas as entidades (prod.Categories.Add () + cat.Products.Add ()) e confirmar a transação. A menos que você cometer a consulta para a tabela de associação não será gerado. - HeavyWave 13 de maio às 12:43

Outras dicas

eu poderia estar enganado, mas parece que você marcou a relação como "inversa" para ambos os lados.

Você deve fazer isso por apenas um lado do relacionamento, e certifique-se de chamar Save no outro.

Além disso, eu acredito que as necessidades de relacionamento para ser válido em ambos os lados antes de salvar (add cat para prod.Categories, para além do que você já está fazendo).

O meu entendimento destes tipos de relacionamentos é reconhecidamente difusa, mas isso é o que minha pesquisa ao longo dos meses surgiu com (e resolver alguns dos meus próprios problemas).

Você tem inversa no lado errado da associação. meios inversa "do outro lado gere esta associação".

O que você precisa é ou remover Inverse na categoria e colocá-lo em produtos, ou alteração de produto a ser a entidade 'possuir', como este:

//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

Que tal se você fosse aplicar os mapeamentos fluentes para o produto e categoria em primeiro lugar, antes que o mapeamento automático. Por exemplo,

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();
}

com a configuração,

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();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top