Fluent Nhibernate: Trying to create entity with co

2019-07-16 18:04发布

问题:

The references are unidirectional. The table (StoreProduct) for this entity is actually a join table that has these fields:

  • Store_id
  • Product_id
  • ExtraBit

So I went with an entity having a compoundID (store_id and product_id) and the ExtraBit is just a string:

public class StoreProduct
{
    protected StoreProduct():this(null,null,null){ }
    public StoreProduct(Store c_Store, Product c_Product, String c_ExtraBit)
    {
        Store = c_Store;
        Product = c_Product;
        ExtraBit = c_ExtraBit;
    } 
    public virtual int Product_id { get; set; }
    public virtual int Store_id { get; set; }
    public virtual Store Store { get; set; }
    public virtual Product Product { get; set; }
    public virtual String ExtraBit { get; set; }


    public override int GetHashCode()
    {
        return Store.GetHashCode() + Product.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        StoreProduct obj_StoreProduct;
        obj_StoreProduct = obj as StoreProduct;
        if (obj_StoreProduct == null)
        {
            return false;
        }
        if (obj_StoreProduct.Product != this.Product && obj_StoreProduct.Store != this.Store)
        {
            return false;
        }
        return true;
    }

}

And the mapping:

public class Order_DetailMap : ClassMap<StoreProduct>
{
    public Order_DetailMap()
    {
        Table("StoreProduct");
        LazyLoad();
        CompositeId().KeyProperty(x => x.Store_id).KeyProperty(x => x.Product_id);
        References(x => x.Store).ForeignKey("Store_id").Cascade.All();
        References(x => x.Product).ForeignKey("Product_id").Cascade.All();
        Map(x => x.ExtraBit);
    }
}

It doesn't work though, when I tried saving the StoreProduct and its newly created Store and product. Can anyone help? Here is some output:

 Unhandled Exception: System.ArgumentOutOfRangeException: Index was out of range.
 Must be non-negative and less than the size of the collection.
Parameter name: index
   at System.ThrowHelper.ThrowArgumentOutOfRangeException()
   at System.Data.SQLite.SQLiteParameterCollection.GetParameter(Int32 index)
   at System.Data.Common.DbParameterCollection.System.Collections.IList.get_Item
(Int32 index)
   at NHibernate.Type.Int32Type.Set(IDbCommand rs, Object value, Int32 index) in
 d:\CSharp\NH\NH\nhibernate\src\NHibernate\Type\Int32Type.cs:line 60
   at NHibernate.Type.NullableType.NullSafeSet(IDbCommand cmd, Object value, Int
32 index) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Type\NullableType.cs:line
 180
   at NHibernate.Type.NullableType.NullSafeSet(IDbCommand st, Object value, Int3
2 index, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHiberna
te\Type\NullableType.cs:line 139
   at NHibernate.Type.ComponentType.NullSafeSet(IDbCommand st, Object value, Int
32 begin, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibern
ate\Type\ComponentType.cs:line 221
   at NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, O
bject[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColu
mns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index
) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPe
rsister.cs:line 2418

Edit: Thanks to the help bellow I seem to have a decent solution:

Store Mapping and Class:

namespace compoundIDtest.Domain.Mappings
{
    public class StoreMap : ClassMap<Store>
    {
        public StoreMap()
        {
            Id(x => x.Id).Column("Store_id");
            Map(x => x.Name);
            HasMany(x => x.Staff)
              .Inverse()
              .Cascade.All();
            HasManyToMany(x => x.Products)
             .Cascade.All()
             .Table("StoreProduct");
        }
    }
}


namespace compoundIDtest.Domain.Entities
{
    public class Store
    {
        public virtual int Id { get; private set; }
        public virtual string Name { get; set; }
        public virtual IList<Product> Products { get; set; }
        public virtual IList<Employee> Staff { get; set; }
        public virtual IList<StoreProduct> StoreProducts { get; set; }

        public Store()
        {
            Products = new List<Product>();
            Staff = new List<Employee>();
        }

        public virtual void AddProduct(Product product)
        {
            product.StoresStockedIn.Add(this);
            Products.Add(product);
        }

        public virtual void AddEmployee(Employee employee)
        {
            employee.Store = this;
            Staff.Add(employee);
        }

        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            Store obj_Store;
            obj_Store = obj as Store;
            if (obj_Store == null)
            {
                return false;
            }
            if (obj_Store.Name != this.Name)
            {
                return false;
            }
            return true;
        }




    }
}

Product Mapping And Class

    namespace compoundIDtest.Domain.Mappings
    {
        public class ProductMap : ClassMap<Product>
        {
            public ProductMap()
            {
                Id(x => x.Id).Column("Product_id");
                Map(x => x.Name);
                Map(x => x.Price);
                HasManyToMany(x => x.StoresStockedIn)
                  .Cascade.All()
                  .Inverse()
                  .Table("StoreProduct");
            }
        }
    }


namespace compoundIDtest.Domain.Entities
{
    public class Product
    {
        public virtual int Id { get; private set; }
        public virtual string Name { get; set; }
        public virtual double Price { get; set; }
        public virtual IList<Store> StoresStockedIn { get; set; }
        public virtual IList<StoreProduct> StoreProducts { get; set; }
        public Product()
        {
            StoresStockedIn = new List<Store>();
            StoreProducts = new List<StoreProduct>();
        }


            public override int GetHashCode()
            {
                return Name.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                Product obj_Product;
                obj_Product = obj as Product;
                if (obj_Product == null)
                {
                    return false;
                }
                if (obj_Product.Name != this.Name)
                {
                    return false;
                }
                return true;
            }


        }
    }

And StoreProduct

namespace compoundIDtest.Domain.Mappings
{
    public class Order_DetailMap : ClassMap<StoreProduct>
    {
        public Order_DetailMap()
        {
            Table("StoreProduct");
            LazyLoad();
            CompositeId().KeyReference(x => x.Store, "Store_id").KeyReference(x => x.Product, "Product_id");
            References(x => x.Store, "Store_id").Not.Update().Not.Insert().Cascade.All();
            References(x => x.Product, "Product_id").Not.Update().Not.Insert().Cascade.All();
            Map(x => x.ExtraBit);
        }
    }

}

namespace compoundIDtest.Domain.Entities
{
    public class StoreProduct
    {
        public StoreProduct(){}

        public virtual Store Store { get; set; }
        public virtual Product Product { get; set; }
        public virtual String ExtraBit { get; set; }

        public override int GetHashCode()
        {

            if (this.ExtraBit != null)
            {
                return Store.GetHashCode() + Product.GetHashCode() + ExtraBit.GetHashCode();
            }

            return Store.GetHashCode() + Product.GetHashCode();

        }

        public override bool Equals(object obj)
        {
            StoreProduct obj_StoreProduct;
            obj_StoreProduct = obj as StoreProduct;
            if (obj_StoreProduct == null)
            {
                return false;
            }
            if (obj_StoreProduct.Product != this.Product && obj_StoreProduct.Store != this.Store && obj_StoreProduct.ExtraBit != this.ExtraBit)
            {
                return false;
            }
            return true;
        }

    }
}

And here is code for an app to test the above:

using System;
using System.IO;
using compoundIDtest.Domain.Entities;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Conventions;


namespace compoundIDtest
{
    class Program
    {
        private const string DbFile = "firstProgram.db";

        static void Main()
        {
            // create our NHibernate session factory
            var sessionFactory = CreateSessionFactory();

            using (var session = sessionFactory.OpenSession())
            {
                // populate the database
                using (var transaction = session.BeginTransaction())
                {
                    // create a couple of Stores each with some Products and Employees
                    var barginBasin = new Store { Name = "Bargin Basin" };
                    var superMart = new Store { Name = "SuperMart" };
                    var CornerShop = new Store { Name = "Corner Shop" };


                    var potatoes = new Product { Name = "Potatoes", Price = 3.60 };
                    var fish = new Product { Name = "Fish", Price = 4.49 };
                    var milk = new Product { Name = "Milk", Price = 0.79 };
                    var bread = new Product { Name = "Bread", Price = 1.29 };
                    var cheese = new Product { Name = "Cheese", Price = 2.10 };
                    var waffles = new Product { Name = "Waffles", Price = 2.41 };
                    var poison = new Product { Name = "Poison", Price = 1.50 };


                    var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" };
                    var jack = new Employee { FirstName = "Jack", LastName = "Torrance" };
                    var sue = new Employee { FirstName = "Sue", LastName = "Walkters" };
                    var bill = new Employee { FirstName = "Bill", LastName = "Taft" };
                    var joan = new Employee { FirstName = "Joan", LastName = "Pope" };

                    var storeproduct = new StoreProduct { Store = CornerShop, Product = poison, ExtraBit = "Extra Bit"}; 

                    //session.SaveOrUpdate(CornerShop);
                    //session.SaveOrUpdate(poison);

                    session.Save(storeproduct);


                    // add products to the stores, there's some crossover in the products in each
                    // store, because the store-product relationship is many-to-many
                    AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese);
                    AddProductsToStore(superMart, bread, cheese, waffles);

                    // add employees to the stores, this relationship is a one-to-many, so one
                    // employee can only work at one store at a time
                    AddEmployeesToStore(barginBasin, daisy, jack, sue);
                    AddEmployeesToStore(superMart, bill, joan);

                    // save both stores, this saves everything else via cascading
                    session.SaveOrUpdate(barginBasin);
                    session.SaveOrUpdate(superMart);
                    //session.SaveOrUpdate(CornerShop);
                    //session.SaveOrUpdate(poison);
                    //session.SaveOrUpdate(storeproduct);

                    transaction.Commit();

                }
            }

            using (var session = sessionFactory.OpenSession())
            {
                // retreive all stores and display them
                using (var transaction = session.BeginTransaction())
                {
                    var products = session.CreateCriteria(typeof(Product))
                        .List<Product>();

                    foreach (var product in products)
                    {
                        product.Price = 100;
                        session.SaveOrUpdate(product);

                    }



                    var storeproducts = session.CreateCriteria(typeof(StoreProduct)).List<StoreProduct>();


                    foreach (StoreProduct storeproduct in storeproducts)
                    {

                        if (storeproduct.Store.Name == "SuperMart")
                        {
                            storeproduct.ExtraBit = "Thank you, come again";
                        }

                        session.SaveOrUpdate(storeproduct);

                    }

                    transaction.Commit();

                }
            }



            Console.ReadKey();
        }

        private static ISessionFactory CreateSessionFactory()
        {
            return Fluently.Configure()
                .Database(SQLiteConfiguration.Standard
                    .UsingFile(DbFile))
                .Mappings(m =>
                    m.FluentMappings.AddFromAssemblyOf<Program>())
                .ExposeConfiguration(BuildSchema)
                .BuildSessionFactory();
        }

        private static void BuildSchema(Configuration config)
        {
            // delete the existing db on each run
            if (File.Exists(DbFile))
                File.Delete(DbFile);

            // this NHibernate tool takes a configuration (with mapping info in)
            // and exports a database schema from it
            new SchemaExport(config)
                .Create(false, true);
        }

        private static void WriteStorePretty(Store store)
        {
            Console.WriteLine(store.Name);
            Console.WriteLine(" Products:");

            foreach (var product in store.Products)
            {
                Console.WriteLine(" " + product.Name);
            }

            Console.WriteLine(" Staff:");

            foreach (var employee in store.Staff)
            {
                Console.WriteLine(" " + employee.FirstName + " " + employee.LastName);
            }

            Console.WriteLine();
        }

        public static void AddProductsToStore(Store store, params Product[] products)
        {
            foreach (var product in products)
            {
                store.AddProduct(product);
            }
        }

        public static void AddEmployeesToStore(Store store, params Employee[] employees)
        {
            foreach (var employee in employees)
            {
                store.AddEmployee(employee);
            }
        }
    }
}

回答1:

I had a mapping pretty much identical to this and the way I ended up mapping it was like this:

public class Order_DetailMap : ClassMap<StoreProduct>
{
    public Order_DetailMap()
    {
        Table("StoreProduct");
        CompositeId()
            .KeyReference(x => x.Store, "Store_id")
            .KeyReference(x => x.Product, "Product_id");

        Map(x => x.ExtraBit);
    }
}

Inside of my Store and Product classes I have add and remove methods that make the creation of this middle class almost invisible. Example below:

public class Store
{
    public IList<StoreProduct> StoreProducts { get; set; }

    //Other properties and Constructors     

    public virtual void AddProduct(Product productToAdd, string extraBit)
    {
        StoreProduct newStoreProduct = new StoreProduct(this, productToAdd, extraBit);
        storeProducts.Add(newStoreProduct);
    }
}

In addition to the above I had HasMany's to a StoreProduct collection in my Store and Product classes that are set to Cascade.AllDeleteOrphan()

I was never able to be able to map the StoreProduct such that when it was saved by itself it would create a new Store and a new Product. I had to eventually map it like the above. So your Store or Product will need to exist before you actually create the relationship (StoreProduct) between them depending on which side you are creating your new StoreProduct from.

Edit:

You may also be able to map it like this to achieve what you are wanting:

public class Order_DetailMap : ClassMap<StoreProduct>
{
    public Order_DetailMap()
    {
        Table("StoreProduct");
        CompositeId()
            .KeyReference(x => x.Store, "Store_id")
            .KeyReference(x => x.Product, "Product_id");

        References(x => x.Store, "Store_id")
            .Not.Update()
            .Not.Insert()
            .Cascade.All();

        References(x => x.Product, "Product_id")
            .Not.Update()
            .Not.Insert()
            .Cascade.All();

        Map(x => x.ExtraBit);
    }
}