Fluent Nhibernate: Trying to create entity with composite key that is also the keys for two references - nhibernate

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

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

Related

Insert into SQL Server database through NHibernate 4 with Session.Save using mapping-by-code

Currently experiencing issues with Session.Save() not inserting records and producing the following exception:
null id in NHModels.Domain.Activity entry (don't flush the Session after an exception occurs)
at NHibernate.Event.Default.DefaultFlushEntityEventListener.CheckId(Object obj, IEntityPersister persister, Object id, EntityMode entityMode)
at NHibernate.Event.Default.DefaultFlushEntityEventListener.GetValues(Object entity, EntityEntry entry, EntityMode entityMode, Boolean mightBeDirty, ISessionImplementor session)
at NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(FlushEntityEvent event)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(FlushEvent event)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at NHUnitOfWork.Dispose() in NHUnitOfWork.cs:line
at DatabaseActivityOperations.<WriteActivity>d__6.MoveNext() in DatabaseActivityOperations.cs:line 240
My class and mapping look like this.
Activity (For simplicity sake, I've removed several ILists related to this class)
public class Activity {
public Activity() {
Activityschema = new List<ActivitySchema>();
}
public virtual int ActivityKey { get; set; }
public virtual string Activityname { get; set; }
public virtual string Activitydescription { get; set; }
public virtual DateTime Averageactivitytime { get; set; }
public virtual int Averagenumberpeople { get; set; }
public virtual string Worktype { get; set; }
public virtual bool? Canautocomplete { get; set; }
public virtual IList<ActivitySchema> Activityschema { get; set; }
}
ActivityMap
public class ActivityMap : ClassMapping<Activity> {
public ActivityMap() {
Schema("dbo");
Lazy(true);
Id(x => x.ActivityKey, map => { map.Generator(Generators.Identity); });
Property(x => x.Activityname, map => { map.NotNullable(true); map.Length(50); });
Property(x => x.Activitydescription, map => { map.NotNullable(true); map.Length(100); });
Property(x => x.Averageactivitytime, map =>
{
map.NotNullable(true);
map.Type(NHibernateUtil.Time);
});
Property(x => x.Averagenumberpeople, map => { map.NotNullable(true); map.Precision(10); });
Property(x => x.Worktype, map => { map.NotNullable(true); map.Length(50); });
Property(x => x.Canautocomplete);
Bag(x => x.Activityschema, colmap => { colmap.Key(x => x.Column("ActivityKey")); colmap.Inverse(true); }, map => { map.OneToMany(); });
}
}
Finally, here's the Unit of Work class I have:
public class NHUnitOfWork : IDisposable
{
public static string ConnectingString { get; private set; } = #"data source=nh;initial catalog=db;MultipleActiveResultSets=True;";
protected static Configuration _config;
protected static NHibernate.ISessionFactory _sessionFactory;
public NHibernate.ISession Session { get; private set; }
protected NHibernate.ITransaction Transaction { get; set; }
private const System.Data.IsolationLevel ISOLATION_LEVEL = System.Data.IsolationLevel.ReadUncommitted;
private bool RollBack { get; set; } = false;
public NHUnitOfWork(string databaseConnectionString)
{
if (_config == null)
{
var cfg = new Configuration();
cfg.DataBaseIntegration(db =>
{
db.Driver<NHibernate.Driver.SqlClientDriver>();
db.ConnectionString = #"data source=nh;initial catalog=db;MultipleActiveResultSets=True;";
//db.ConnectionString = databaseConnectionString;
db.Dialect<MsSql2012Dialect>();
db.BatchSize = 500;
})
.AddAssembly(typeof(Activity).Assembly)
.SessionFactory()
.GenerateStatistics();
var mapper = new ModelMapper();
mapper.AddMappings(typeof(ActivityMap).Assembly.GetTypes());
cfg.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_config = cfg;
_sessionFactory = _config.BuildSessionFactory();
}
Session = _sessionFactory.OpenSession();
Transaction = Session.BeginTransaction(ISOLATION_LEVEL);
RollBack = false;
}
public void Commit()
{
Transaction.Commit();
}
public void Rollback()
{
if (Transaction.IsActive) Transaction.Rollback();
}
public void Dispose()
{
if (RollBack)
{
Transaction.Rollback();
}
else
{
Transaction.Commit();
}
Session.Close();
}
}
By this point, I believe my configuration for this is correct. I'm successfully used Session.Query to read data and that doesn't present issues. The problem comes when I write something like this to add a new record:
var activity = new Activity
{
Activityname = "TestActivity",
Activitydescription = "This is a test",
Averagenumberpeople = 1,
Worktype = "Test",
Canautocomplete = false,
Averageactivitytime = new DateTime(1, 1, 1, 0, 55, 55)
};
using (var uow = new NHUnitOfWork(NHUnitOfWork.ConnectingString))
{
uow.Session.Save(activity); // Produces exception here
//This also produces an exception
//uow.Session.Save(activity, Generators.Identity);
}
I think this has something to do with how I'm mapping the ID in ActivityMap and the generator isn't working as expected. I've tried to change it to several other types and get the same exception, or one stating that it's not able to convert to SystemInt32. I've also tried changing the ID to long and specifiying a data type, but no luck. What do I seem to be doing wrong here?
Literally minutes after I posted this, I figured out that the issue was actually with how I was setting the date time.
new DateTime(1, 1, 1, 0, 55, 55)
It didn't like the "1/1/0001" part, so that seemed to be causing the issue, I'm only concerned with the time piece. Changing the year to something like 2001 fixed the insert and it's working fine, the message was just not very descriptive.

After updating to nHibernate 4, cannot use ManyToMany Mappings. Could not determine type

After updating to nHibernate (4.0.2.4000 via nuget), the many to many mappings that previously worked now cause mapping exception "Could not determine type for: nHibernateManyToMany.IRole, nHibernateManyToMany, for columns: NHibernate.Mapping.Column(id)"
Seems to be only for many to many, and when the List is a interface (i.e. List<Role> vs List<IRole>).
Example code that now fails:
class Program
{
static void Main(string[] args)
{
var configuration = new Configuration().SetProperty(Environment.ReleaseConnections, "on_close")
.SetProperty(Environment.Dialect, typeof(SQLiteDialect).AssemblyQualifiedName)
.SetProperty(Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName)
.SetProperty(Environment.CollectionTypeFactoryClass, typeof(DefaultCollectionTypeFactory).AssemblyQualifiedName)
.SetProperty(Environment.CommandTimeout, "0");
var mapper = new ModelMapper();
mapper.AddMappings(new[] { typeof(EmployeeMapping), typeof(RoleMapping) });
var hbmMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
hbmMapping.autoimport = false;
configuration.AddMapping(hbmMapping);
// this line will fail
var factory = configuration.BuildSessionFactory();
}
}
public class Employee
{
public virtual int Id { get; set; }
public virtual List<IRole> Roles { get; set; }
}
public interface IRole
{
int Id { get; set; }
string Description { get; set; }
}
public class Role : IRole
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
}
public class EmployeeMapping : ClassMapping<Employee>
{
public EmployeeMapping()
{
Id(c => c.Id, x =>
{
x.Type(NHibernateUtil.Int32);
x.Generator(Generators.Identity);
x.Column("EmployeeId");
});
Bag(x => x.Roles, m =>
{
m.Table("EmployeeRole");
m.Key(km =>
{
km.Column("EmployeeId");
km.NotNullable(true);
km.ForeignKey("FK_Role_Employee");
});
m.Lazy(CollectionLazy.Lazy);
}, er => er.ManyToMany(m =>
{
m.Class(typeof(Role));
m.Column("RoleId");
}));
}
}
public class RoleMapping : ClassMapping<Role>
{
public RoleMapping()
{
Id(c => c.Id, x =>
{
x.Type(NHibernateUtil.Int32);
x.Generator(Generators.Identity);
x.Column("RoleId");
});
Property(x => x.Description, c =>
{
c.Length(50);
c.NotNullable(true);
});
}
}
Any help or suggestions about where we could look for details on how this has changed since v3 would be appreciated.
Turned out to be a bug in nHibernate.
A pull request has been submitted here https://github.com/nhibernate/nhibernate-core/pull/385

NHibernate Error Message: Invalid index 3 for this SqlParameterCollection with Count=3

I have a test database design like this:
The following is the pseudo-code:
//BhillHeader
public class BillHeader
{
public BillHeader()
{
BillDetails = new List<BillDetail>();
}
public virtual int BillNo { get; set; }
public virtual IList<BillDetail> BillDetails { get; set; }
public virtual decimal Amount { get; set; }
public virtual void AddDetail(BillDetail billdet)
{
BillDetails.Add(billdet);
}
}
//BillHeader Map
public class BillHeaderMap : ClassMap<BillHeader>
{
public BillHeaderMap()
{
Table("BillHeader");
LazyLoad();
Id(x => x.BillNo).GeneratedBy.Identity().Column("BillNo");
Map(x => x.Amount).Column("Amount").Not.Nullable();
HasMany(x => x.BillDetails).KeyColumn("BillNo").Cascade.All().Inverse();
}
}
//BillDetail
public class BillDetail
{
public BillDetail() { }
public virtual int BillID { get; set; }
public virtual int SeqNo { get; set; }
public virtual BillHeader BillHeader { get; set; }
public virtual decimal Amt { get; set; }
public override bool Equals(object obj)
{
var other = obj as BillDetail;
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return this.BillID == other.BillID &&
this.SeqNo == other.SeqNo;
}
public override int GetHashCode()
{
unchecked {
int hash = GetType().GetHashCode();
hash = (hash * 31) ^ SeqNo.GetHashCode();
hash = (hash * 31) ^ BillID.GetHashCode();
return hash;
}
}
}
//BillDetail Map
public class BillDetailMap : ClassMap<BillDetail>
{
public BillDetailMap()
{
Table("BillDetail");
LazyLoad();
CompositeId().KeyProperty(x => x.BillID, "BillNo").KeyProperty(x => x.SeqNo, "SeqNo");
References(x => x.BillHeader).Column("BillNo");
Map(x => x.Amt).Column("Amt").Not.Nullable();
}
}
//-----------------------------------------------------------------------------------------------------------------------------
//Program
public createBillNo()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession()) {
using (var sqlTrans = session.BeginTransaction()) {
BillHeader billNo1 = new BillHeader() { Amount = 2500.00M};
BillDetail bh11 = new BillDetail() { SeqNo = 1, Amt = 200.00M };
BillDetail bh12 = new BillDetail() { SeqNo = 2, Amt = 300.00M };
BillDetail bh13 = new BillDetail() { SeqNo = 3, Amt = 500.00M };
AddBillDetailsToBillHeader(billNo1, bh11, bh12, bh13);
session.SaveOrUpdate(billNo1);
sqlTrans.Commit();
}
}
}
private void AddBillDetailsToBillHeader(BillHeader billHeader, params BillDetail[] billDetails)
{
foreach (var billdet in billDetails) {
billHeader.AddDetail(billdet);
billdet.BillHeader = billHeader;
}
}
When I run this I'm getting the following exception:
Invalid index 3 for this SqlParameterCollection with Count=3
Please help me to resolve this issue.
most probably because column "BillNo" is mapped twice, it tries to add 2 parameter for 1 column, hence the outOfRange error. move the reference into the compositekey
CompositeId()
.KeyReference(x => x.BillHeader, "BillNo")
.KeyProperty(x => x.SeqNo, "SeqNo");
// References(x => x.).Column("BillNo"); <-- Remove

NHibernate SaveOrUpdateCopy won't insert entity with CompositeId

I have an entity with a CompositeId that won't insert new rows to the database using SaveOrUpdateCopy. The INSERT statement generated by NHibernate is populated with "?" for the value of every field. It inserts fine with SaveOrUpdate, it updates fine with either SaveOrUpdateCopy or SaveOrUpdate, and any entity without a CompositeId inserts/updates fine with SaveOrUpdateCopy. I don't want to create an if/then looking for entities with CompositeId to decide if it should use SaveOrUpdate or SaveOrUpdateCopy. Is there some trick to getting SaveOrUpdateCopy to work with entities with CompositeId?
Here's the code (names changed to protect the innocent):
public class MyEntity
{
public virtual Int32 FirstProperty { get; set; }
public virtual string SecondProperty { get; set; }
public virtual string DataText { get; set; }
public override int GetHashCode( )
{
int hashCode = 0;
hashCode = hashCode ^ FirstProperty.GetHashCode() ^
SecondProperty.GetHashCode();
return hashCode;
}
public override bool Equals( object obj )
{
MyEntity toCompare = obj as MyEntity;
if( toCompare == null )
{
return false;
}
return ( GetHashCode() != toCompare.GetHashCode() );
}
}
public MyEntityMap()
{
CompositeId()
.KeyProperty(x => x.FirstProperty, "first_property")
.KeyProperty(x => x.SecondProperty, "second_property");
Map(x => x.DataText, "data_text")
.Nullable();
Table("dbo.my_entity");
}
Database call:
public MyEntity GetMyEntity(long firstProperty, string secondProperty)
{
using (var session = sessionFactory.OpenSession())
{
var result = from entity in
session.Linq()
where entity.FirstProperty == firstProperty
&& entity.SecondProperty== secondProperty
select entity;
return result.Count() > 0 ? result.First() : null;
}
}
Database save:
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdateCopy(entity);
transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
throw;
}
}
}
Add a version property to the composite key class, see this article for an in-depth explanation.
Hi I use compositeId with FluentnHibernate but my implementation of the Equals and GetHashCode is different. This one class that have 7 fields as Key:
public class PresupuestoGastoPromocion
{
public PresupuestoGastoPromocion() { }
public virtual int Año { get; set; }
public virtual int Mes { get; set; }
public virtual int PartidaId { get; set; }
public virtual string Equipo { get; set; }
public virtual string CodFamilia { get; set; }
public virtual string GDP { get; set; }
public virtual int TipoPresupuestoId { get; set; }
public virtual float Monto { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as PresupuestoGastoPromocion;
if (t == null)
return false;
if (CodFamilia == t.CodFamilia && Año == t.Año && Mes == t.Mes && TipoPresupuestoId == t.TipoPresupuestoId && Equipo == t.Equipo && PartidaId == t.PartidaId && GDP == t.GDP)
return true;
return false;
}
public override int GetHashCode()
{
return (CodFamilia + "|" + Año + "|" + Mes + "|" + TipoPresupuestoId + "|" + Equipo + "|" + PartidaId + "|" + GDP).GetHashCode();
}
}
public class PresupuestoGastoPromocionMap : ClassMap<PresupuestoGastoPromocion>
{
public PresupuestoGastoPromocionMap()
{
Table("PresupuestoGastoPromocion");
CompositeId()
.KeyProperty(x => x.Año)
.KeyProperty(x => x.Mes)
.KeyProperty(x => x.TipoPresupuestoId)
.KeyProperty(x => x.CodFamilia, "Cod_Familia")
.KeyProperty(x => x.Equipo)
.KeyProperty(x => x.GDP)
.KeyProperty(x => x.PartidaId);
Map(x => x.Monto).Column("Monto");
}
}
I hope this will help you.

Many to Many relationship with Fluent NHibernate

I'm getting the following error: "Can't figure out what the other side of a many-to-many should be."
Team Entity:
public class Team : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Employee> Employee { get; set; }
public Team()
{
Employee = new List<Employee>();
}
}
Employee Entity:
public class Employee : IEntity
{
public int Id { get; set; }
public String LastName { get; set; }
public string FirstName { get; set; }
public IList<Team> Team { get; set; }
public string EMail { get; set; }
public Employee()
{
Team = new List<Team>();
}
}
Team mapping:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
// identity mapping
Id(p => p.Id);
// column mapping
Map(p => p.Name);
// relationship mapping
HasManyToMany<Employee>(m => m.Employee);
}
}
Employee mapping:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
// identifier mapping
Id(p => p.Id);
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// relationship mapping
HasManyToMany<Team>(m => m.Team);
}
}
Nobody has an answer?
Edit: The error occurs on the following code:
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(c=>
c.Database("Ariha")
.TrustedConnection()
.Server("localhost")
).ShowSql())
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<BookMap>()
.AddFromAssemblyOf<MagazineMap>()
.AddFromAssemblyOf<EmployeeMap>()
.AddFromAssemblyOf<TeamMap>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
edit: here the whole solution: http://rapidshare.com/files/309653409/S.O.L.I.D.Ariha.rar.html
Could you provide code that causes your error? I just tried your mappings and they seem to work fine (on fluent 1.0 RTM and NH 2.1.1 GA with an SQLite database), with a minor modification to your EmployeeMap (I have assumed the employee-team relationship is bidirectional, and as per documentation you need to mark one side as inverse).
// relationship mapping
HasManyToMany<Team>(m => m.Team).Inverse();
Of course if the employee-team relationship is not bidirectional, I would have thought you should be able to specify a different .Table(name) for each one - but I have not tested this and you seem to be getting different results anyway (hence why providing example code would be best)
I'd also add that I suspect Set semantics (instead of Bag) would be more appropriate for the Employee.Team and Team.Employee properties. (Irregardless, don't do anything that assumes order is preserved, there is no guarantee that it will be)
Suggested mapping and example:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employee { get; set; }
public Team() { Employee = new List<Employee>(); }
}
public class Employee
{
public int Id { get; set; }
public String LastName { get; set; }
public string FirstName { get; set; }
public ICollection<Team> Team { get; set; }
public string EMail { get; set; }
public Employee() { Team = new List<Team>(); }
}
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Not.LazyLoad();
// identity mapping
Id(p => p.Id);
// column mapping
Map(p => p.Name);
// relationship mapping
HasManyToMany<Employee>(m => m.Employee).AsSet();
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Not.LazyLoad();
// identifier mapping
Id(p => p.Id);
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// relationship mapping
HasManyToMany<Team>(m => m.Team).Inverse().AsSet();
}
}
[TestFixture]
public class Mapping
{
[Test]
public void PersistDepersist()
{
var fcfg = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile("testdb.sqldb"))
.Mappings(mc =>
{
mc.FluentMappings.Add(typeof (TeamMap));
mc.FluentMappings.Add(typeof (EmployeeMap));
})
.ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(false, true, false));
var sess = fcfg.BuildSessionFactory().OpenSession();
var teams = Enumerable.Range(0, 4).Select(i => new Team() {Name = "Team " + i}).ToArray();
var employees = Enumerable.Range(0, 10).Select(i => new Employee() {FirstName = "Employee " + i}).ToArray();
teams[0].Employee = new List<Employee>() {employees[0], employees[3], employees[5]};
teams[1].Employee = new List<Employee>() {employees[7], employees[2], employees[5]};
teams[3].Employee = new List<Employee>() {employees[0], employees[8], employees[9]};
foreach (var team in teams)
foreach (var employee in team.Employee)
employee.Team.Add(team);
Console.WriteLine("Dumping Generated Team/Employees:");
Dump(teams);
Dump(employees);
using (var t = sess.BeginTransaction())
{
foreach (var team in teams)
sess.Save(team);
foreach (var employee in employees)
sess.Save(employee);
t.Commit();
}
sess.Flush();
sess.Clear();
var teamsPersisted = sess.CreateCriteria(typeof (Team)).List<Team>();
var employeesPersisted = sess.CreateCriteria(typeof (Employee)).List<Employee>();
Assert.AreNotSame(teams, teamsPersisted);
Assert.AreNotSame(employees, employeesPersisted);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Dumping Depersisted Team/Employees:");
Dump(teamsPersisted);
Dump(employeesPersisted);
}
private static void Dump(IEnumerable<Team> teams)
{
foreach (var team in teams)
Console.WriteLine("Team: " + team.Name + " has members: " + string.Join(", ", team.Employee.Select(e => e.FirstName).ToArray()));
}
private static void Dump(IEnumerable<Employee> employees)
{
foreach (var employee in employees)
Console.WriteLine("Employee: " + employee.FirstName + " in teams: " + string.Join(", ", employee.Team.Select(e => e.Name).ToArray()));
}
}
Fluent NHibernate tries to determine what the other side of a many-to-many relationship is by looking at the entity names and the collection properties. I believe it's not working in your case because your collection properties aren't plural; try renaming your collections Employees and Teams respectively.
Another way is to manually set the many-to-many table name on both sides, as this will disable the prediction.