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
- Fruit
- Apple
- Peach
- Banana
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:
Hozzászóláshoz be kell jelentkezni!