Today I found a very funny page which describes the reality of developer’s life and illustrates with animated pics.
I need to share it 🙂
http://www.lordofthejars.com/2013/02/the-reality-of-developers-life.html
Today I found a very funny page which describes the reality of developer’s life and illustrates with animated pics.
I need to share it 🙂
http://www.lordofthejars.com/2013/02/the-reality-of-developers-life.html
Yesterday I took part in database schema development with my team and a very strange and unexpected exception threw when we wanted to save data across NHibernate Entity Framework.
I spent several hours to figure out the root of the problem, so this experience may useful other developers in the future.
We are using Sql Server CE 4.0 in a decentralized synchronization system called Everlight.
The symptom:
2013-02-02 11:57:23,183 ERROR [10] [NHibernate.AdoNet.AbstractBatcher] - Could not execute command: INSERT INTO EmployeeGroup_Employees_Switch (employeeGroup_systemId, employeeGroup_deviceId, employeeGroup_id, employee_systemId, employee_deviceId, employee_id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5) System.Data.SqlServerCe.SqlCeException (0x80004005): A duplicate value cannot be inserted into a unique index. [ Table name = EmployeeGroup_Employees_Switch,Constraint name = PK__EmployeeGroup_Employees_Switch__000000000000012F ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor) at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery() at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) in p:\nhibernate-core\src\NHibernate\AdoNet\AbstractBatcher.cs:line 197 2013-02-02 11:57:23,355 ERROR [10] [NHibernate.Util.ADOExceptionReporter] - A duplicate value cannot be inserted into a unique index. [ Table name = EmployeeGroup_Employees_Switch,Constraint name = PK__EmployeeGroup_Employees_Switch__000000000000012F ] 2013-02-02 11:57:23,448 ERROR [10] [NHibernate.Event.Default.AbstractFlushingEventListener] - Could not synchronize database state with session NHibernate.Exceptions.GenericADOException: could not insert collection: [HVS.Hvs360.EntityModel.Common.EmployeeGroup.employees#1:2066501244:634953994429806654][SQL: INSERT INTO EmployeeGroup_Employees_Switch (employeeGroup_systemId, employeeGroup_deviceId, employeeGroup_id, employee_systemId, employee_deviceId, employee_id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5)] ---> System.Data.SqlServerCe.SqlCeException: A duplicate value cannot be inserted into a unique index. [ Table name = EmployeeGroup_Employees_Switch,Constraint name = PK__EmployeeGroup_Employees_Switch__000000000000012F ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor) at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery() at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) in p:\nhibernate-core\src\NHibernate\AdoNet\AbstractBatcher.cs:line 210 at NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) in p:\nhibernate-core\src\NHibernate\AdoNet\NonBatchingBatcher.cs:line 40 at NHibernate.Persister.Collection.AbstractCollectionPersister.PerformInsert(Object ownerId, IPersistentCollection collection, IExpectation expectation, Object entry, Int32 index, Boolean useBatch, Boolean callable, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Persister\Collection\AbstractCollectionPersister.cs:line 2013 at NHibernate.Persister.Collection.AbstractCollectionPersister.Recreate(IPersistentCollection collection, Object id, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Persister\Collection\AbstractCollectionPersister.cs:line 1109 --- End of inner exception stack trace --- at NHibernate.Persister.Collection.AbstractCollectionPersister.Recreate(IPersistentCollection collection, Object id, ISessionImplementor session) in p:\nhibernate-core\src\NHibernate\Persister\Collection\AbstractCollectionPersister.cs:line 1140 at NHibernate.Action.CollectionRecreateAction.Execute() in p:\nhibernate-core\src\NHibernate\Action\CollectionRecreateAction.cs:line 35 at NHibernate.Engine.ActionQueue.Execute(IExecutable executable) in p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 136 at NHibernate.Engine.ActionQueue.ExecuteActions(IList list) in p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 125 at NHibernate.Engine.ActionQueue.ExecuteActions() in p:\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 174 at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) in p:\nhibernate-core\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 241
The error message on the UI:
I have definied two entities:
I am using bidirectional Many-To-Many relations, an EmployeeGroup may has several Employees and an Employee instance may belongs to several EmployeeGroups.
The source codes of the entities are the following:
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "employeeGroups", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "EmployeeGroup_Employees_Switch")] [Key(1)] [Column(2, Name = "employee_systemId")] [Column(3, Name = "employee_deviceId")] [Column(4, Name = "employee_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(EmployeeGroup))] [Column(6, Name = "employeeGroup_systemId")] [Column(7, Name = "employeeGroup_deviceId")] [Column(8, Name = "employeeGroup_id")] private ISet<EmployeeGroup> employeeGroups = new HashSet<EmployeeGroup>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "employees", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "EmployeeGroup_Employees_Switch")] [Key(1)] [Column(2, Name = "employeeGroup_systemId")] [Column(3, Name = "employeeGroup_deviceId")] [Column(4, Name = "employeeGroup_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Employee))] [Column(6, Name = "employee_systemId")] [Column(7, Name = "employee_deviceId")] [Column(8, Name = "employee_id")] private ISet<Employee> employees = new HashSet<Employee>();
I use this code to instantiate and save entities:
IUnitOfWork uow = null; try { uow = mDomainManager.CreateUnitOfWork(); HashSet<SecurityRight> rights = null; { OperatorGroup opGroup = new OperatorGroup(); opGroup.Name = "OperatorGroup"; opGroup.OperatorGroupType = OperatorGroupTypeEnum.Operators; uow.EntitySave(opGroup); Operator op = new Operator(); op.FullName = "OperatorUser"; op.Hidden = true; op.HumanType = HumanTypeEnum.OperationTeamMember; HashSet<EmployeeGroup> opGroups = new HashSet<EmployeeGroup>(); opGroups.Add(opGroup); op.EmployeeGroups = opGroups; uow.EntitySave(op); HashSet<Employee> ops = new HashSet<Employee>(); ops.Add(op); opGroup.Employees = ops; uow.EntitySave(opGroup); } { OperatorGroup salesGroup = new OperatorGroup(); salesGroup.Name = "SalesGroup"; salesGroup.OperatorGroupType = OperatorGroupTypeEnum.Sales; uow.EntitySave(salesGroup); } uow.Commit("Operation_data_creation"); } catch (Exception ex) { if (uow != null) { uow.Disconnect(); } MessageBox.Show(this, string.Format("Failed to load data. Reason: {0}", ex.Message), "Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if (uow != null) { uow.Disconnect(); } }
The Operator inherits from Employee, the OperationGroup inherits the EmployeeGroup.
The exception comes from the database engine, when I commits the transaction. The NHibernate wants to save the relation information into the table “EmployeeGroup_Employees_Switch”. This table has six (6) colums as a primary key.
This may looks strange, but in our system each entities has a composite primary key which consists of a systemId, deviceId and id. So the switch table stores the identifier of the OperationGroup on the left side and the Operator identifier on the right side.
The primary key must be unique. NHibernate wants to save the relation into this table twice.
At first, I did not understand why. Debugging the NHibernate code I figure out that this is a normal behavior, so something wrong with the database mapping.
Finally I found the problem successfully. I forgot to define the inverse property 🙂
In my previous post which deals with Many-To-Many relations, I had definied this property well. But now, I simply forgot it.
The correct definition on the Employee entity:
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "employeeGroups", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "EmployeeGroup_Employees_Switch", Inverse = true)] [Key(1)] [Column(2, Name = "employee_systemId")] [Column(3, Name = "employee_deviceId")] [Column(4, Name = "employee_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(EmployeeGroup))] [Column(6, Name = "employeeGroup_systemId")] [Column(7, Name = "employeeGroup_deviceId")] [Column(8, Name = "employeeGroup_id")] private ISet<EmployeeGroup> employeeGroups = new HashSet<EmployeeGroup>();
As you can see the difference, I have to define the “Inverse = true” on the attribute Set. This property guarantees that the NHibernate saves the relations only once.
At first sight the exception was not hint for a mapping problem.
Good to know that 🙂
This topic talks about EF5 Many-To-One relations where the relations described with attributes. I will declare the mapping schema with code first approach.
For the presentation I have defined two entities:
Address holds the reference of Country, but Country does not know anything about Address entity. This is an Unidirectional relationship between entities.
The Country entity class code:
[Serializable] [Table("Addresses")] public class Address : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string addressInfo = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private Country country = null; public Address() : base() { } [Column("addressInfo")] public string AddressInfo { get { return addressInfo; } set { OnPropertyChanging("AddressInfo"); addressInfo = value; OnPropertyChanged("AddressInfo"); } } /// <summary> /// Gets or sets the country. /// This is a trick to define custom foreign keys for composite indentifiers /// </summary> /// <value> /// The country. /// </value> [ForeignKey("country_systemId, country_deviceId, country_id")] [Required] public virtual Country Country { get { return country; } set { OnPropertyChanging("Country"); country = value; OnPropertyChanged("Country"); } } [DebuggerHidden] [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Browsable(false)] public long country_systemId { get; set; } [DebuggerHidden] [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Browsable(false)] public long country_deviceId { get; set; } [DebuggerHidden] [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Browsable(false)] public long country_id { get; set; } }
The Address entity class code:
[Serializable] [Table("Countries")] [DataContractAttribute(IsReference = true)] public class Country : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string name = string.Empty; public Country() : base() { } [DataMemberAttribute] [DebuggerHidden] [Column("name")] public string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has three identifiers as complex primary key. The EntityId class simulates a composite key because EF5 does not support composite component key.
Here is an example how to create and save entities:
[TestMethod] public void TestManyToOne() { using (ManyToOneObjectContext context = DbContextCommon.CreateObjectContext<ManyToOneObjectContext>(CreateConnection())) { Country countryHun = context.CreateObject<Country>(); EFUtils.InitializeEntityForThisDemo(countryHun); countryHun.Name = "Hungary"; context.Countries.AddObject(countryHun); Country countryGer = context.CreateObject<Country>(); EFUtils.InitializeEntityForThisDemo(countryGer); countryGer.Name = "Germany"; context.Countries.AddObject(countryGer); Country countryEng = context.CreateObject<Country>(); EFUtils.InitializeEntityForThisDemo(countryEng); countryEng.Name = "United Kingdom"; context.Countries.AddObject(countryEng); Address add1 = context.CreateObject<Address>(); EFUtils.InitializeEntityForThisDemo(add1); add1.AddressInfo = "Szirmabesenyo"; add1.Country = countryHun; context.Addresses.AddObject(add1); Address add2 = context.CreateObject<Address>(); EFUtils.InitializeEntityForThisDemo(add2); add2.AddressInfo = "Miskolc"; add2.Country = countryHun; context.Addresses.AddObject(add2); Address add3 = context.CreateObject<Address>(); EFUtils.InitializeEntityForThisDemo(add3); add3.AddressInfo = "Berlin"; add3.Country = countryGer; context.Addresses.AddObject(add3); context.SaveChanges(); } using (ManyToOneObjectContext context = DbContextCommon.CreateObjectContext<ManyToOneObjectContext>(CreateConnection())) { var testQuery = (from addr in context.Addresses where (addr.Country != null && "Hungary".Equals(addr.Country.Name)) select addr).ToList<Address>(); // this test fails if you run this test more than once Assert.IsTrue(testQuery.Count >= 2); } }
Download the Many-To-One Unidirectional Visual Studio 2010 example project:
Last topic is the Many-To-Many Unidirectional mapping in NHibernate. I will declare the relations as the same way as done it at my previous articles: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined two entities:
Car and Driver entity too holds the references to each other. Car may belongs to several Drivers and vica versa. This is an Bidirectional relationship.
The Car entity class code:
[Serializable] [Class(Table = "Cars")] public class Car : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "drivers", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "Drivers_Cars_Switch", Inverse = true)] [Key(1)] [Column(2, Name = "car_restId")] [Column(3, Name = "car_deviceId")] [Column(4, Name = "car_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Driver))] [Column(6, Name = "driver_restId")] [Column(7, Name = "driver_deviceId")] [Column(8, Name = "driver_id")] private ISet<Driver> drivers = new HashSet<Driver>(); public Car() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual ISet<Driver> Drivers { get { return new HashSet<Driver>(drivers); } set { if (value == null) { ThrowHelper.ThrowArgumentNullException("value"); } OnPropertyChanging("Drivers"); drivers = new HashSet<Driver>(value); OnPropertyChanged("Drivers"); } } }
The Driver entity class code:
[Serializable] [Class(Table = "Drivers")] public class Driver : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "cars", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "Drivers_Cars_Switch")] [Key(1)] [Column(2, Name = "driver_restId")] [Column(3, Name = "driver_deviceId")] [Column(4, Name = "driver_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Car))] [Column(6, Name = "car_restId")] [Column(7, Name = "car_deviceId")] [Column(8, Name = "car_id")] private ISet<Car> cars = new HashSet<Car>(); public Driver() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual ISet<Car> Cars { get { return new HashSet<Car>(cars); } set { if (value == null) { ThrowHelper.ThrowArgumentNullException("value"); } OnPropertyChanging("Cars"); cars = new HashSet<Car>(value); OnPropertyChanged("Cars"); } } }
The mapping XML is the following:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.ManyToManyBiDirectional, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="Drivers" name="NHibernateDemo.DomainModel.ManyToManyBiDirectional.Driver, NHibernateDemo.DomainModel.ManyToManyBiDirectional"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <set name="cars" table="Drivers_Cars_Switch" lazy="true" cascade="none" generic="true"> <key> <column name="driver_restId" /> <column name="driver_deviceId" /> <column name="driver_id" /> </key> <many-to-many class="NHibernateDemo.DomainModel.ManyToManyBiDirectional.Car, NHibernateDemo.DomainModel.ManyToManyBiDirectional" not-found="exception"> <column name="car_restId" /> <column name="car_deviceId" /> <column name="car_id" /> </many-to-many> </set> </class> <class table="Cars" name="NHibernateDemo.DomainModel.ManyToManyBiDirectional.Car, NHibernateDemo.DomainModel.ManyToManyBiDirectional"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <set name="drivers" table="Drivers_Cars_Switch" lazy="true" inverse="true" cascade="none" generic="true"> <key> <column name="car_restId" /> <column name="car_deviceId" /> <column name="car_id" /> </key> <many-to-many class="NHibernateDemo.DomainModel.ManyToManyBiDirectional.Driver, NHibernateDemo.DomainModel.ManyToManyBiDirectional" not-found="exception"> <column name="driver_restId" /> <column name="driver_deviceId" /> <column name="driver_id" /> </many-to-many> </set> </class> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
[TestMethod] public void TestManyToManyBi() { // create the entities using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Car mazda = new Car(); EFUtils.InitializeEntityForThisDemo(mazda); mazda.Name = "Mazda"; EFUtils.SaveEntity(mazda, session); Car honda = new Car(); EFUtils.InitializeEntityForThisDemo(honda); honda.Name = "Honda"; EFUtils.SaveEntity(honda, session); Car kia = new Car(); EFUtils.InitializeEntityForThisDemo(kia); kia.Name = "Kia"; EFUtils.SaveEntity(kia, session); Car rover = new Car(); EFUtils.InitializeEntityForThisDemo(rover); rover.Name = "Rover"; EFUtils.SaveEntity(rover, session); Car audi = new Car(); EFUtils.InitializeEntityForThisDemo(audi); audi.Name = "Audi"; EFUtils.SaveEntity(audi, session); Car bmw = new Car(); EFUtils.InitializeEntityForThisDemo(bmw); bmw.Name = "Bmw"; EFUtils.SaveEntity(bmw, session); Driver john = new Driver(); EFUtils.InitializeEntityForThisDemo(john); john.Name = "John"; EFUtils.SaveEntity(john, session); Driver peter = new Driver(); EFUtils.InitializeEntityForThisDemo(peter); peter.Name = "Peter"; EFUtils.SaveEntity(peter, session); Driver zoltan = new Driver(); EFUtils.InitializeEntityForThisDemo(zoltan); zoltan.Name = "Zoltan"; EFUtils.SaveEntity(zoltan, session); // John drives the mazda, kia and rover ISet<Car> cars = new HashSet<Car>(); cars.Add(mazda); cars.Add(kia); john.Cars = cars; EFUtils.SaveEntity(john, session); // Peter drivers the honda, kia and audi cars = new HashSet<Car>(); cars.Add(honda); cars.Add(kia); cars.Add(audi); peter.Cars = cars; EFUtils.SaveEntity(john, session); // Zoltan drivers all of the cars cars = new HashSet<Car>(); cars.Add(mazda); cars.Add(honda); cars.Add(kia); cars.Add(rover); cars.Add(audi); cars.Add(bmw); zoltan.Cars = cars; EFUtils.SaveEntity(john, session); ISet<Driver> drivers = mazda.Drivers; drivers.Add(john); drivers.Add(zoltan); mazda.Drivers = drivers; EFUtils.SaveEntity(mazda, session); drivers = honda.Drivers; drivers.Add(peter); drivers.Add(zoltan); honda.Drivers = drivers; EFUtils.SaveEntity(honda, session); drivers = kia.Drivers; drivers.Add(john); drivers.Add(peter); drivers.Add(zoltan); kia.Drivers = drivers; EFUtils.SaveEntity(kia, session); drivers = rover.Drivers; drivers.Add(zoltan); rover.Drivers = drivers; EFUtils.SaveEntity(rover, session); drivers = audi.Drivers; drivers.Add(peter); drivers.Add(zoltan); audi.Drivers = drivers; EFUtils.SaveEntity(audi, session); drivers = bmw.Drivers; drivers.Add(zoltan); bmw.Drivers = drivers; EFUtils.SaveEntity(bmw, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<Driver>(); dc.Add(Restrictions.Eq("name", "Zoltan")); ICriteria criteria = dc.GetExecutableCriteria(session); IList<Driver> drivers = criteria.List<Driver>(); Assert.IsTrue(drivers != null); Assert.IsTrue(drivers.Count == 1); // there is only one driver with name "Zoltan" exists in database Assert.IsTrue(drivers[0].Cars.Count == 6); // three cars assigned dc = DetachedCriteria.For<Driver>(); dc.Add(Restrictions.Eq("name", "Peter")); criteria = dc.GetExecutableCriteria(session); drivers = criteria.List<Driver>(); Assert.IsTrue(drivers != null); Assert.IsTrue(drivers.Count == 1); // there is only one driver with name "Peter" exists in database Assert.IsTrue(drivers[0].Cars.Count == 3); // three cars assigned dc = DetachedCriteria.For<Driver>(); dc.Add(Restrictions.Eq("name", "John")); criteria = dc.GetExecutableCriteria(session); drivers = criteria.List<Driver>(); Assert.IsTrue(drivers != null); Assert.IsTrue(drivers.Count == 1); // there is only one driver with name "John" exists in database Assert.IsTrue(drivers[0].Cars.Count == 2); // two cars assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Mazda")); criteria = dc.GetExecutableCriteria(session); IList<Car> cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Mazda" exists in database Assert.IsTrue(cars[0].Drivers.Count == 2); // two drivers assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Honda")); criteria = dc.GetExecutableCriteria(session); cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Mazda" exists in database Assert.IsTrue(cars[0].Drivers.Count == 2); // two drivers assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Kia")); criteria = dc.GetExecutableCriteria(session); cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Mazda" exists in database Assert.IsTrue(cars[0].Drivers.Count == 3); // three drivers assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Rover")); criteria = dc.GetExecutableCriteria(session); cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Rover" exists in database Assert.IsTrue(cars[0].Drivers.Count == 1); // one driver assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Audi")); criteria = dc.GetExecutableCriteria(session); cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Audi" exists in database Assert.IsTrue(cars[0].Drivers.Count == 2); // two drivers assigned dc = DetachedCriteria.For<Car>(); dc.Add(Restrictions.Eq("name", "Bmw")); criteria = dc.GetExecutableCriteria(session); cars = criteria.List<Car>(); Assert.IsTrue(cars != null); Assert.IsTrue(cars.Count == 1); // there is only one car with name "Bmw" exists in database Assert.IsTrue(cars[0].Drivers.Count == 1); // one driver assigned transaction.Rollback(); } } }
Download the Many-To-Many Bidirectional Visual Studio 2010 example project:
Next topic is the Many-To-Many mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined two entities:
Order entity holds the references of the Product entities. Products may belongs to several Orders, but the Product entity instance does not know anything about the Order. This is an Unidirectional relationship.
The Product entity class code:
[Serializable] [Class(Table = "Products")] public class Product : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; public Product() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
The Order entity class code:
[Serializable] [Class(Table = "Orders")] public class Order : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "products", Cascade = "none", Generic = true, Lazy = CollectionLazy.True, Table = "Orders_Products_Switch")] [Key(1)] [Column(2, Name = "order_restId")] [Column(3, Name = "order_deviceId")] [Column(4, Name = "order_id")] [ManyToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Product))] [Column(6, Name = "product_restId")] [Column(7, Name = "product_deviceId")] [Column(8, Name = "product_id")] private ISet<Product> products = new HashSet<Product>(); public Order() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual ISet<Product> Products { get { return new HashSet<Product>(products); } set { if (value == null) { ThrowHelper.ThrowArgumentNullException("value"); } OnPropertyChanging("Products"); products = new HashSet<Product>(value); OnPropertyChanged("Products"); } } }
This is the mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.ManyToManyUniDirectional, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="Orders" name="NHibernateDemo.DomainModel.ManyToManyUniDirectional.Order, NHibernateDemo.DomainModel.ManyToManyUniDirectional"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <set name="products" table="Orders_Products_Switch" lazy="true" cascade="none" generic="true"> <key> <column name="order_restId" /> <column name="order_deviceId" /> <column name="order_id" /> </key> <many-to-many class="NHibernateDemo.DomainModel.ManyToManyUniDirectional.Product, NHibernateDemo.DomainModel.ManyToManyUniDirectional" not-found="exception"> <column name="product_restId" /> <column name="product_deviceId" /> <column name="product_id" /> </many-to-many> </set> </class> <class table="Products" name="NHibernateDemo.DomainModel.ManyToManyUniDirectional.Product, NHibernateDemo.DomainModel.ManyToManyUniDirectional"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
[TestMethod] public void TestManyToManyUni() { // create the entities using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Product breed = new Product(); EFUtils.InitializeEntityForThisDemo(breed); breed.Name = "Breed"; EFUtils.SaveEntity(breed, session); Product butter = new Product(); EFUtils.InitializeEntityForThisDemo(butter); butter.Name = "Butter"; EFUtils.SaveEntity(butter, session); Product cheese = new Product(); EFUtils.InitializeEntityForThisDemo(cheese); cheese.Name = "Cheese"; EFUtils.SaveEntity(cheese, session); Product beer = new Product(); EFUtils.InitializeEntityForThisDemo(beer); beer.Name = "Beer"; EFUtils.SaveEntity(beer, session); ISet<Product> products = new HashSet<Product>(); UniDirectional.Order orderA = new UniDirectional.Order(); EFUtils.InitializeEntityForThisDemo(orderA); orderA.Name = "John's order"; products.Add(breed); products.Add(butter); orderA.Products = products; EFUtils.SaveEntity(orderA, session); products = new HashSet<Product>(); UniDirectional.Order orderB = new UniDirectional.Order(); EFUtils.InitializeEntityForThisDemo(orderB); orderB.Name = "Tom's order"; products.Add(butter); products.Add(cheese); products.Add(beer); orderB.Products = products; EFUtils.SaveEntity(orderB, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<Product>(); ICriteria criteria = dc.GetExecutableCriteria(session); IList<Product> products = criteria.List<Product>(); Assert.IsTrue(products != null); Assert.IsTrue(products.Count == 4); // there are four products exists in database dc = DetachedCriteria.For<UniDirectional.Order>(); dc.Add(Restrictions.Eq("name", "John's order")); criteria = dc.GetExecutableCriteria(session); IList<UniDirectional.Order> orders = criteria.List<UniDirectional.Order>(); Assert.IsTrue(orders != null); Assert.IsTrue(orders.Count == 1); // there is only one order with name "John's order" exists in database Assert.IsTrue(orders[0].Products.Count == 2); // two products assigned dc = DetachedCriteria.For<UniDirectional.Order>(); dc.Add(Restrictions.Eq("name", "Tom's order")); criteria = dc.GetExecutableCriteria(session); orders = criteria.List<UniDirectional.Order>(); Assert.IsTrue(orders != null); Assert.IsTrue(orders.Count == 1); // there is only one order with name "Tom's order" exists in database Assert.IsTrue(orders[0].Products.Count == 3); // three products assigned } } }
Download the Many-To-Many Unidirectional Visual Studio 2010 example project:
Next topic is the Any mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined four entities:
House entity holds the reference of the joined entity. This is an unidirectional relationship between the entities.
The House entity class code:
[Serializable] [Class(Table = "Houses")] public class House : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Any(0, Name = "something", IdTypeType = typeof(EntityId), MetaTypeType = typeof(string), Cascade = "none")] [MetaValue(1, ClassType = typeof(Human), Value = "This_is_a_human")] [MetaValue(2, ClassType = typeof(Cat), Value = "This_is_a_cat")] [MetaValue(3, ClassType = typeof(Dog), Value = "This_is_a_dog")] [Column(4, Name = "somethingType")] [Column(5, Name = "somethingId")] private EntityBase something = null; public House() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual EntityBase Something { get { return something; } set { OnPropertyChanging("Something"); something = value; OnPropertyChanged("Something"); } } }
The Human entity class code:
[Serializable] [Class(Table = "Humans")] public class Human : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; public Human() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
The Cat entity class code:
[Serializable] [Class(Table = "Cats")] public class Cat : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; public Cat() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
The Dog entity class code:
[Serializable] [Class(Table = "Dogs")] public class Dog : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; public Dog() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
Here is the mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.Any, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="Houses" name="NHibernateDemo.DomainModel.Any.House, NHibernateDemo.DomainModel.Any"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <any id-type="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" meta-type="String" name="something" cascade="none"> <meta-value value="This_is_a_human" class="NHibernateDemo.DomainModel.Any.Human, NHibernateDemo.DomainModel.Any" /> <meta-value value="This_is_a_cat" class="NHibernateDemo.DomainModel.Any.Cat, NHibernateDemo.DomainModel.Any" /> <meta-value value="This_is_a_dog" class="NHibernateDemo.DomainModel.Any.Dog, NHibernateDemo.DomainModel.Any" /> <column name="somethingType" /> <column name="somethingId" /> </any> </class> <class table="Cats" name="NHibernateDemo.DomainModel.Any.Cat, NHibernateDemo.DomainModel.Any"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <class table="Dogs" name="NHibernateDemo.DomainModel.Any.Dog, NHibernateDemo.DomainModel.Any"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <class table="Humans" name="NHibernateDemo.DomainModel.Any.Human, NHibernateDemo.DomainModel.Any"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
[TestMethod] public void TestAnyRelations() { // create the entities using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Cat cat = new Cat(); EFUtils.InitializeEntityForThisDemo(cat); cat.Name = "Cat"; EFUtils.SaveEntity(cat, session); Dog dog = new Dog(); EFUtils.InitializeEntityForThisDemo(dog); dog.Name = "Dog"; EFUtils.SaveEntity(dog, session); Human human = new Human(); EFUtils.InitializeEntityForThisDemo(human); human.Name = "Human"; EFUtils.SaveEntity(human, session); House house = new House(); EFUtils.InitializeEntityForThisDemo(house); house.Name = "JZO"; house.Something = cat; EFUtils.SaveEntity(house, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<House>(); dc.Add(Restrictions.Eq("name", "JZO")); ICriteria criteria = dc.GetExecutableCriteria(session); IList<House> houses = criteria.List<House>(); Assert.IsTrue(houses != null); Assert.IsTrue(houses.Count == 1); // there is one house exists in database Assert.IsTrue(houses[0].Something != null); Assert.IsTrue(houses[0].Something is Cat); dc = DetachedCriteria.For<Dog>(); dc.Add(Restrictions.Eq("name", "Dog")); criteria = dc.GetExecutableCriteria(session); IList<Dog> dogs = criteria.List<Dog>(); Assert.IsTrue(dogs != null); Assert.IsTrue(dogs.Count == 1); // there is one house exists in database houses[0].Something = dogs[0]; EFUtils.SaveEntity(houses[0], session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<House>(); dc.Add(Restrictions.Eq("name", "JZO")); ICriteria criteria = dc.GetExecutableCriteria(session); IList<House> houses = criteria.List<House>(); Assert.IsTrue(houses != null); Assert.IsTrue(houses.Count == 1); // there is one house exists in database Assert.IsTrue(houses[0].Something != null); Assert.IsTrue(houses[0].Something is Dog); dc = DetachedCriteria.For<Human>(); dc.Add(Restrictions.Eq("name", "Human")); criteria = dc.GetExecutableCriteria(session); IList<Human> humans = criteria.List<Human>(); Assert.IsTrue(humans != null); Assert.IsTrue(humans.Count == 1); // there is one house exists in database houses[0].Something = humans[0]; EFUtils.SaveEntity(houses[0], session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<House>(); dc.Add(Restrictions.Eq("name", "JZO")); ICriteria criteria = dc.GetExecutableCriteria(session); IList<House> houses = criteria.List<House>(); Assert.IsTrue(houses != null); Assert.IsTrue(houses.Count == 1); // there is one house exists in database Assert.IsTrue(houses[0].Something != null); Assert.IsTrue(houses[0].Something is Human); transaction.Rollback(); } } }
Download the Any Visual Studio 2010 example project:
Next topic is the Many-To-Any mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined five entities:
ShoppingBasket contains any type of Fruit: Apple, Peach and Banana. This is an unidirectional relationship between the entities.
The ShoppingBasket entity class code:
[Serializable] [Class(Table = "ShoppingBaskets")] public class ShoppingBasket : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "fruits", Generic = true, Lazy = CollectionLazy.True, Cascade = "none", Table = "ShoppingBasket_Fruits_Switch")] [Key(1)] [Column(2, Name = "shoppingBasket_systemId")] [Column(3, Name = "shoppingBasket_deviceId")] [Column(4, Name = "shoppingBasket_id")] [ManyToAny(5, IdTypeType = typeof(EntityId), MetaTypeType = typeof(string))] [MetaValue(6, ClassType = typeof(Apple), Value = "This_is_an_apple")] [MetaValue(7, ClassType = typeof(Peach), Value = "This_is_a_peach")] [MetaValue(8, ClassType = typeof(Banana), Value = "This_is_a_banana")] [Column(9, Name = "fruitType")] [Column(10, Name = "fruitId")] private ISet<Fruit> fruits = new HashSet<Fruit>(); public ShoppingBasket() : base() { } [DebuggerHidden] public virtual ISet<Fruit> Fruits { get { return new HashSet<Fruit>(fruits); } set { if (value == null) { ThrowHelper.ThrowArgumentNullException("Fruits"); } OnPropertyChanging("Fruits"); fruits = new HashSet<Fruit>(value); OnPropertyChanged("Fruits"); } } }
The Fruit entity class code:
[Serializable] [Class(Abstract = true)] public abstract class Fruit : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string qrCode = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [ManyToOne(0, Name = "shoppingBasket", Cascade = "none")] [Column(1, Name = "shoppingBasket_systemId")] [Column(2, Name = "shoppingBasket_deviceId")] [Column(3, Name = "shoppingBasket_id")] private ShoppingBasket shoppingBasket = null; protected Fruit() : base() { } [DebuggerHidden] public virtual string QrCode { get { return qrCode; } set { OnPropertyChanging("QrCode"); qrCode = value; OnPropertyChanged("QrCode"); } } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual ShoppingBasket ShoppingBasket { get { return shoppingBasket; } set { OnPropertyChanging("ShoppingBasket"); shoppingBasket = value; OnPropertyChanged("ShoppingBasket"); } } }
The Apple entity class:
[Serializable] [UnionSubclass(Table = "Apples", ExtendsType = typeof(Fruit))] public class Apple : Fruit { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = false)] private string appleType = string.Empty; public Apple() : base() { } [DebuggerHidden] public virtual string AppleType { get { return appleType; } set { OnPropertyChanging("AppleType"); appleType = value; OnPropertyChanged("AppleType"); } } }
The Banana entity class:
[Serializable] [UnionSubclass(Table = "Bananas", ExtendsType = typeof(Fruit))] public class Banana : Fruit { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true, Precision = 10, Scale = 2)] private decimal mass = 0; public Banana() : base() { } [DebuggerHidden] public virtual decimal Mass { get { return mass; } set { OnPropertyChanging("Mass"); mass = value; OnPropertyChanged("Mass"); } } }
The Peach entity class:
[Serializable] [UnionSubclass(Table = "Peaches", ExtendsType = typeof(Fruit))] public class Peach : Fruit { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = false)] private string peachType = string.Empty; public Peach() : base() { } [DebuggerHidden] public virtual string PeachType { get { return peachType; } set { OnPropertyChanging("PeachType"); peachType = value; OnPropertyChanged("PeachType"); } } }
Here is the mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.ManyToAny, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="ShoppingBaskets" name="NHibernateDemo.DomainModel.ManyToAny.ShoppingBasket, NHibernateDemo.DomainModel.ManyToAny"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <set name="fruits" table="ShoppingBasket_Fruits_Switch" lazy="true" cascade="none" generic="true"> <key> <column name="shoppingBasket_systemId" /> <column name="shoppingBasket_deviceId" /> <column name="shoppingBasket_id" /> </key> <many-to-any id-type="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" meta-type="String"> <meta-value value="This_is_an_apple" class="NHibernateDemo.DomainModel.ManyToAny.Apple, NHibernateDemo.DomainModel.ManyToAny" /> <meta-value value="This_is_a_peach" class="NHibernateDemo.DomainModel.ManyToAny.Peach, NHibernateDemo.DomainModel.ManyToAny" /> <meta-value value="This_is_a_banana" class="NHibernateDemo.DomainModel.ManyToAny.Banana, NHibernateDemo.DomainModel.ManyToAny" /> <column name="fruitType" /> <column name="fruitId" /> </many-to-any> </set> </class> <class abstract="true" name="NHibernateDemo.DomainModel.ManyToAny.Fruit, NHibernateDemo.DomainModel.ManyToAny"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="qrCode" not-null="true" /> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <many-to-one name="shoppingBasket" cascade="none"> <column name="shoppingBasket_systemId" /> <column name="shoppingBasket_deviceId" /> <column name="shoppingBasket_id" /> </many-to-one> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <union-subclass table="Bananas" extends="NHibernateDemo.DomainModel.ManyToAny.Fruit, NHibernateDemo.DomainModel.ManyToAny" name="NHibernateDemo.DomainModel.ManyToAny.Banana, NHibernateDemo.DomainModel.ManyToAny"> <property name="mass" precision="10" scale="2" not-null="true" /> </union-subclass> <union-subclass table="Peaches" extends="NHibernateDemo.DomainModel.ManyToAny.Fruit, NHibernateDemo.DomainModel.ManyToAny" name="NHibernateDemo.DomainModel.ManyToAny.Peach, NHibernateDemo.DomainModel.ManyToAny"> <property name="peachType" not-null="false" /> </union-subclass> <union-subclass table="Apples" extends="NHibernateDemo.DomainModel.ManyToAny.Fruit, NHibernateDemo.DomainModel.ManyToAny" name="NHibernateDemo.DomainModel.ManyToAny.Apple, NHibernateDemo.DomainModel.ManyToAny"> <property name="appleType" not-null="false" /> </union-subclass> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
[TestMethod] public void TestManyToAny() { // create the entities using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Apple apple = new Apple(); EFUtils.InitializeEntityForThisDemo(apple); apple.AppleType = "Jonatan"; apple.Name = "Apple"; apple.QrCode = "123"; EFUtils.SaveEntity(apple, session); Banana banana = new Banana(); EFUtils.InitializeEntityForThisDemo(banana); banana.Name = "Banana"; banana.Mass = 1m; banana.QrCode = "679"; EFUtils.SaveEntity(banana, session); Peach peach = new Peach(); EFUtils.InitializeEntityForThisDemo(peach); peach.Name = "Peach"; peach.PeachType = "Apricot"; peach.QrCode = "4443"; EFUtils.SaveEntity(peach, session); ShoppingBasket basket = new ShoppingBasket(); EFUtils.InitializeEntityForThisDemo(basket); ISet<Fruit> fruits = new HashSet<Fruit>(); fruits.Add(apple); fruits.Add(banana); fruits.Add(peach); basket.Fruits = fruits; EFUtils.SaveEntity(basket, session); apple.ShoppingBasket = basket; banana.ShoppingBasket = basket; peach.ShoppingBasket = basket; EFUtils.SaveEntity(apple, session); EFUtils.SaveEntity(banana, session); EFUtils.SaveEntity(peach, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<ShoppingBasket>(); ICriteria criteria = dc.GetExecutableCriteria(session); IList<ShoppingBasket> baskets = criteria.List<ShoppingBasket>(); Assert.IsTrue(baskets != null); Assert.IsTrue(baskets.Count == 1); // there is only one basket exists in database Assert.IsTrue(baskets[0].Fruits.Count == 3); // three fruits assigned dc = DetachedCriteria.For<Fruit>(); dc.Add(Restrictions.Not(Restrictions.IsNull("shoppingBasket"))); criteria = dc.GetExecutableCriteria(session); IList<Fruit> fruits = criteria.List<Fruit>(); Assert.IsTrue(fruits != null); Assert.IsTrue(fruits.Count == 3); Assert.IsTrue(fruits[0].ShoppingBasket != null); Assert.IsTrue(fruits[0].ShoppingBasket.Equals(baskets[0])); Assert.IsTrue(fruits[1].ShoppingBasket != null); Assert.IsTrue(fruits[1].ShoppingBasket.Equals(baskets[0])); Assert.IsTrue(fruits[2].ShoppingBasket != null); Assert.IsTrue(fruits[2].ShoppingBasket.Equals(baskets[0])); transaction.Rollback(); } } }
Download the Many-To-Any Visual Studio 2010 example project:
Next topic is the One-To-Many mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined two entities:
Each Mother have zero or more Children, but a Child belongs to only one Mother. Each side have reference points to the other side, so this is an bidirectional relationship between the entities.
The Child entity class code:
[Serializable] [Class(Table = "Children")] public class Child : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [ManyToOne(0, Name = "mother", Cascade = "none")] [Column(1, Name = "mother_restId")] [Column(2, Name = "mother_deviceId")] [Column(3, Name = "mother_id")] private Mother mother = null; public Child() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual Mother Mother { get { return mother; } set { OnPropertyChanging("Mother"); mother = value; OnPropertyChanged("Mother"); } } }
The Mother entity class:
[Serializable] [Class(Table = "Mothers")] public class Mother : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Set(0, Name = "children", Generic = true, Lazy = CollectionLazy.True, Cascade = "none")] [Key(1)] [Column(2, Name = "child_restId")] [Column(3, Name = "child_deviceId")] [Column(4, Name = "child_id")] [OneToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Child))] private ISet children = new HashSet(); public Mother() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } /// NOTE: the Children property content is immutable. /// You should have to get the collection, modify it than re-set across the this property. /// The main reason for this solution, you cannot change the content of the set, without property /// notification. This is useful, if you bind the property to somewhere. [DebuggerHidden] public virtual ISet Children { get { return new HashSet(children); } set { if (value == null) { ThrowHelper.ThrowArgumentNullException("value"); } OnPropertyChanging("Children"); children = new HashSet(value); OnPropertyChanged("Children"); } } }
As you can see in the Children property comment there is a problem with NHibernate version 3.3.2.4000 GA. If you declare an entity which inherited from Child entity, add it to the Children set than save the mother entity and you will get an exception. This is a known issue in NHibernate, to track this problem please visit this Jira.
https://nhibernate.jira.com/browse/NH-3380
Here is the mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.OneToMany, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="Children" name="NHibernateDemo.DomainModel.OneToMany.Child, NHibernateDemo.DomainModel.OneToMany"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <many-to-one name="mother" cascade="none"> <column name="mother_restId" /> <column name="mother_deviceId" /> <column name="mother_id" /> </many-to-one> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <class table="Mothers" name="NHibernateDemo.DomainModel.OneToMany.Mother, NHibernateDemo.DomainModel.OneToMany"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> <set name="children" lazy="true" cascade="none" generic="true"> <key> <column name="child_restId" /> <column name="child_deviceId" /> <column name="child_id" /> </key> <one-to-many class="NHibernateDemo.DomainModel.OneToMany.Child, NHibernateDemo.DomainModel.OneToMany" not-found="exception" /> </set> </class> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
public void TestOneToMany() { // create the entities using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Mother victoria = new Mother(); EFUtils.InitializeEntityForThisDemo(victoria); victoria.Name = "Victoria"; EFUtils.SaveEntity(victoria, session); Child tom = new Child(); EFUtils.InitializeEntityForThisDemo(tom); tom.Name = "Tom"; EFUtils.SaveEntity(tom, session); Child sue = new Child(); EFUtils.InitializeEntityForThisDemo(sue); sue.Name = "Sue"; EFUtils.SaveEntity(sue, session); // each mother may have several children, but each children belongs to one mother ISet children = new HashSet(); children.Add(tom); children.Add(sue); victoria.Children = children; EFUtils.SaveEntity(victoria, session); tom.Mother = victoria; sue.Mother = victoria; EFUtils.SaveEntity(tom, session); EFUtils.SaveEntity(sue, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For(); ICriteria criteria = dc.GetExecutableCriteria(session); IList mothers = criteria.List(); Assert.IsTrue(mothers != null); Assert.IsTrue(mothers.Count == 1); // there is only one mother exists in database Assert.IsTrue(mothers[0].Children.Count == 2); // two children assigned dc = DetachedCriteria.For(); dc.Add(Restrictions.Not(Restrictions.IsNull("mother"))); criteria = dc.GetExecutableCriteria(session); IList children = criteria.List(); Assert.IsTrue(children != null); Assert.IsTrue(children.Count == 2); Assert.IsTrue(children[0].Mother != null); Assert.IsTrue(children[0].Mother.Equals(mothers[0])); Assert.IsTrue(children[1].Mother != null); Assert.IsTrue(children[1].Mother.Equals(mothers[0])); transaction.Rollback(); } } }
Download the One-To-Many Visual Studio 2010 example project:
Next topic is the Many-To-One mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.
For the presentation I have defined two entities:
Each Address has a Country (optionally), but the Country may belongs to several Address. Country does not reference the Address entity, so this is an Unidirectional relationship between these entities.
The Country entity class code
[Serializable] [Class(Table = "Countries")] public class Country : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true, Unique = true)] private string name = string.Empty; public Country() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } }
The Address entity class code:
[Serializable] [Class(Table = "Addresses")] public class Address : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = false)] private string addressInfo = string.Empty; // the country is mandatory, because the NotNull = true. [DebuggerBrowsable(DebuggerBrowsableState.Never)] [ManyToOne(0, Name = "country", ClassType = typeof(Country), Cascade = "none", NotNull = true)] [Column(1, Name = "country_restId")] [Column(2, Name = "country_deviceId")] [Column(3, Name = "country_id")] private Country country = null; public Address() : base() { } [DebuggerHidden] public virtual string AddressInfo { get { return addressInfo; } set { OnPropertyChanging("AddressInfo"); addressInfo = value; OnPropertyChanged("AddressInfo"); } } [DebuggerHidden] public virtual Country Country { get { return country; } set { OnPropertyChanging("Country"); country = value; OnPropertyChanged("Country"); } } }
Mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.ManyToOne, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class table="Countries" name="NHibernateDemo.DomainModel.ManyToOne.Country, NHibernateDemo.DomainModel.ManyToOne"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" unique="true" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <class table="Addresses" name="NHibernateDemo.DomainModel.ManyToOne.Address, NHibernateDemo.DomainModel.ManyToOne"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="addressInfo" not-null="false" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <many-to-one name="country" class="NHibernateDemo.DomainModel.ManyToOne.Country, NHibernateDemo.DomainModel.ManyToOne" not-null="true" cascade="none"> <column name="country_restId" /> <column name="country_deviceId" /> <column name="country_id" /> </many-to-one> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Here is an example, how to create and save entities:
[TestMethod] public void TestManyToOne() { // create countries and addresses using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Country countryHun = new Country(); EFUtils.InitializeEntityForThisDemo(countryHun); countryHun.Name = "Hungary"; EFUtils.SaveEntity(countryHun, session); Country countryGer = new Country(); EFUtils.InitializeEntityForThisDemo(countryGer); countryGer.Name = "Germany"; EFUtils.SaveEntity(countryGer, session); Country countryEng = new Country(); EFUtils.InitializeEntityForThisDemo(countryEng); countryEng.Name = "United Kingdom"; EFUtils.SaveEntity(countryEng, session); Address add1 = new Address(); EFUtils.InitializeEntityForThisDemo(add1); add1.AddressInfo = "Szirmabesenyo"; add1.Country = countryHun; EFUtils.SaveEntity(add1, session); Address add2 = new Address(); EFUtils.InitializeEntityForThisDemo(add2); add2.AddressInfo = "Miskolc"; add2.Country = countryHun; EFUtils.SaveEntity(add2, session); Address add3 = new Address(); EFUtils.InitializeEntityForThisDemo(add3); add3.AddressInfo = "Berlin"; add3.Country = countryGer; EFUtils.SaveEntity(add3, session); transaction.Commit(); } } using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<Address>(); DetachedCriteria joinDc = dc.CreateCriteria("country", "c", JoinType.InnerJoin); DetachedCriteria eqDc = joinDc.Add(Restrictions.Eq("name", "Hungary")); ICriteria criteria = dc.GetExecutableCriteria(session); IList<Address> resultList = criteria.List<Address>(); Assert.IsTrue(resultList != null); Assert.IsTrue(resultList.Count == 2); // there are two addresses in the database which belongs to "Hungary" transaction.Rollback(); } } }
Download the Many-To-One Visual Studio 2010 example project:
I using Code First and mapping documents too to define the connection between the database tables and the POCOs.
For the presentation I have definied five entities:
The abstract Person entity class code:
[Serializable] [Class(Abstract = true)] public abstract class Person : EntityBase { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private string name = string.Empty; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private int age = 0; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = false)] private string description = string.Empty; protected Person() : base() { } [DebuggerHidden] public virtual string Name { get { return name; } set { OnPropertyChanging("Name"); name = value; OnPropertyChanged("Name"); } } [DebuggerHidden] public virtual int Age { get { return age; } set { OnPropertyChanging("Age"); age = value; OnPropertyChanged("Age"); } } [DebuggerHidden] public virtual string Description { get { return description; } set { OnPropertyChanging("Description"); description = value; OnPropertyChanged("Description"); } } }
The Girl entity class code:
[Serializable] [UnionSubclass(Table = "Girls", ExtendsType = typeof(Person))] public class Girl : Person { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private bool hasLongHair = false; public Girl() : base() { } [DebuggerHidden] public virtual bool HasLongHair { get { return hasLongHair; } set { OnPropertyChanging("HasLongHair"); hasLongHair = value; OnPropertyChanged("HasLongHair"); } } }
The Woman entity class code:
[Serializable] [UnionSubclass(Table = "Women", ExtendsType = typeof(Girl))] public class Woman : Girl { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [OneToOne(Name = "husband", ClassType = typeof(Man), Cascade = "none", Constrained = false, PropertyRef = "wife")] private Man husband = null; public Woman() : base() { } [DebuggerHidden] public virtual Man Husband { get { return husband; } set { OnPropertyChanging("Husband"); husband = value; OnPropertyChanged("Husband"); } } }
The Boy entity class code:
[Serializable] [UnionSubclass(Table = "Boys", ExtendsType = typeof(Person))] public class Boy : Person { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [Property(NotNull = true)] private bool hasMatchBoxes = false; [DebuggerHidden] public virtual bool HasMatchBoxes { get { return hasMatchBoxes; } set { OnPropertyChanging("HasMatchBoxes"); hasMatchBoxes = value; OnPropertyChanged("HasMatchBoxes"); } } }
The Man entity class code:
[Serializable] [UnionSubclass(Table = "Men", ExtendsType = typeof(Boy))] public class Man : Boy { [DebuggerBrowsable(DebuggerBrowsableState.Never)] [ManyToOne(0, Name = "wife", ClassType = typeof(Woman), Cascade = "none", Unique = true)] [Column(1, Name = "wife_restId")] [Column(2, Name = "wife_deviceId")] [Column(3, Name = "wife_id")] private Woman wife = null; public Man() : base() { } [DebuggerHidden] public virtual Woman Wife { get { return wife; } set { OnPropertyChanging("Wife"); wife = value; OnPropertyChanged("Wife"); } } }
Mapping XML:
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.OneToOne, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2"> <class abstract="true" name="NHibernateDemo.DomainModel.OneToOne.Person, NHibernateDemo.DomainModel.OneToOne"> <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id"> <key-property name="systemId" column="systemId" /> <key-property name="deviceId" column="deviceId" /> <key-property name="id" column="id" /> </composite-id> <property name="name" not-null="true" /> <property name="age" not-null="true" /> <property name="description" not-null="false" /> <property name="entityCreationTime" not-null="true" /> <property name="entityModificationTime" not-null="true" /> <property name="deleted" column="isDeleted" not-null="true" /> <component name="version"> <property name="versionDeviceId" column="versionDeviceId" not-null="true" /> <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" /> </component> </class> <union-subclass table="Girls" extends="NHibernateDemo.DomainModel.OneToOne.Person, NHibernateDemo.DomainModel.OneToOne" name="NHibernateDemo.DomainModel.OneToOne.Girl, NHibernateDemo.DomainModel.OneToOne"> <property name="hasLongHair" not-null="true" /> </union-subclass> <union-subclass table="Boys" extends="NHibernateDemo.DomainModel.OneToOne.Person, NHibernateDemo.DomainModel.OneToOne" name="NHibernateDemo.DomainModel.OneToOne.Boy, NHibernateDemo.DomainModel.OneToOne"> <property name="hasMatchBoxes" not-null="true" /> </union-subclass> <union-subclass table="Women" extends="NHibernateDemo.DomainModel.OneToOne.Girl, NHibernateDemo.DomainModel.OneToOne" name="NHibernateDemo.DomainModel.OneToOne.Woman, NHibernateDemo.DomainModel.OneToOne"> <one-to-one name="husband" class="NHibernateDemo.DomainModel.OneToOne.Man, NHibernateDemo.DomainModel.OneToOne" cascade="none" constrained="false" property-ref="wife" /> </union-subclass> <union-subclass table="Men" extends="NHibernateDemo.DomainModel.OneToOne.Boy, NHibernateDemo.DomainModel.OneToOne" name="NHibernateDemo.DomainModel.OneToOne.Man, NHibernateDemo.DomainModel.OneToOne"> <many-to-one name="wife" class="NHibernateDemo.DomainModel.OneToOne.Woman, NHibernateDemo.DomainModel.OneToOne" unique="true" cascade="none"> <column name="wife_restId" /> <column name="wife_deviceId" /> <column name="wife_id" /> </many-to-one> </union-subclass> </hibernate-mapping>
All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.
Using Union Subclass to join tables, so each table has own properties. This method does not require discriminator column.
Code First attributes definied in the NHibernate.Mapping.Attributes.dll which is a free extension for NHibernate and available to download with NuGet in Visual Studio 2010 and later.
Here is an example, how to create and save entities:
[TestMethod] public void TestOneToOne() { // create a boy and a girl entity using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Boy boy = new Boy(); EFUtils.InitializeEntityForThisDemo(boy); boy.Name = "Little John"; boy.Age = 12; EFUtils.SaveEntity(boy, session); Girl girl = new Girl(); EFUtils.InitializeEntityForThisDemo(girl); girl.Name = "Mariam"; girl.Age = 10; EFUtils.SaveEntity(girl, session); transaction.Commit(); } } // create a single woman and man using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { Man boy = new Man(); EFUtils.InitializeEntityForThisDemo(boy); boy.Name = "John Doo"; boy.Age = 28; EFUtils.SaveEntity(boy, session); Woman girl = new Woman(); EFUtils.InitializeEntityForThisDemo(girl); girl.Name = "Sue"; girl.Age = 25; EFUtils.SaveEntity(girl, session); transaction.Commit(); } } // check database content and make a wedding 😉 using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<Boy>(); ICriteria criteria = dc.GetExecutableCriteria(session); IList<Boy> resultOfBoys = criteria.List<Boy>(); Assert.IsTrue(resultOfBoys != null); Assert.IsTrue(resultOfBoys.Count == 2); // there are a boy and a man in the database dc = DetachedCriteria.For<Man>(); criteria = dc.GetExecutableCriteria(session); IList<Man> resultOfMen = criteria.List<Man>(); Assert.IsTrue(resultOfMen != null); Assert.IsTrue(resultOfMen.Count == 1); // there are only one man in the database dc = DetachedCriteria.For<Girl>(); criteria = dc.GetExecutableCriteria(session); IList<Girl> resultOfGirls = criteria.List<Girl>(); Assert.IsTrue(resultOfGirls != null); Assert.IsTrue(resultOfGirls.Count == 2); // there are a girl and a woman in the database dc = DetachedCriteria.For<Woman>(); criteria = dc.GetExecutableCriteria(session); IList<Woman> resultOfWomen = criteria.List<Woman>(); Assert.IsTrue(resultOfWomen != null); Assert.IsTrue(resultOfWomen.Count == 1); // there are only one woman in the database // make a wedding 😉 Man man = resultOfMen[0]; Woman woman = resultOfWomen[0]; man.Wife = woman; woman.Husband = man; woman.Name = "Mrs. Sue Doo"; EFUtils.SaveEntity(man, session); EFUtils.SaveEntity(woman, session); transaction.Commit(); } } // check content of the database using (ISession session = sessionFactory.OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { DetachedCriteria dc = DetachedCriteria.For<Man>(); dc.Add(Restrictions.Not(Restrictions.IsNull("wife"))); ICriteria criteria = dc.GetExecutableCriteria(session); IList<Man> resultOfMen = criteria.List<Man>(); Assert.IsTrue(resultOfMen != null); Assert.IsTrue(resultOfMen.Count == 1); // there are only one married man in the database // NOTE: do not execute query on the One-To-One side, because this field is just a placeholder. // The field 'husband' does not exist in the database, so you can easily get unexpected // results on query. transaction.Rollback(); } } }
Download the One-To-One Visual Studio 2010 example project:
Hozzászóláshoz be kell jelentkezni!