NHibernate Many-To-Any mapping, Composite Identifier, Unidirectional relationship

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.

DomainModel.ManyToAny

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.

Database.ManyToAny

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:

JZO névjegye

I'm a full-stack developer, coding in .NET/React/Javascript. Visit my site: https://jzo.hu
Kategória: NHibernate
Címke: , , , , , , ,
Közvetlen link a könyvjelzőhöz.

Hozzászólás