Altaf Khatri
Altaf Khatri



RSS feed for http://www.AltafKhatri.com website. Twitter account of http://www.AltafKhatri.com website.
∗Recently Added - How to take care of Betta fighter fish and increase life span?  10/30/2013


Find me on web

 LinkedIn

 Facebook

 Twitter

How to implement NHibernate stateless session with HiLo algorithm?

In this article I will show the implementation of Nhibernate's HiLo algorithm for identity with stateless session functionality.

I have tried to cover the basic day to day need of a functionality with object hierarchy of : Parent table with one child table and parent table having one to many relationship.
e.g.

  • Customer has one credit card
  • Customer has many shipping addresses
  • Credit card has one address.


Terminlogy:
  • HiLo - HiLo is an algorithm used to generate the primary key of the table on the client side(dotnet side) rather than let SQL server generate the Id of the table. For this we don’t rely on Identity seed of the sql table. HiLo contains of two parts:
    1. Hi - Containing the first part of the primary key. This is stored in the database somewhere and NOT in the table itself.
    2. Lo - Contains the second part of the primary key that is calculated on the client side.
      HiLo are combined by Nhibernate to generate the primary key.
      The details of HiLo can be found here
  • Stateless session - Nhibernate maintains the object state in the memory. If the object is saved to the database table then the Nhibernate will go and synch the object in memory with the inserted key when we save the object/flush the object. Imagine a scenario when your need is just to insert the data in the database and forget about it, then you would ask a question why you need to sync the object in memory? A scenario where you have lots of rows to be inserted like in batch job why would you like to maintain the state when you are not going to use it any time.
    This is where stateless sessions come into play.

    Combination of HiLo with stateless session is a good combination because the inserts will be only one way as the foreign keys needed to save the dependent objects are already available. And the object will not need to be synch with database by NHibernate

    Here, I have implemented complete example with objects, its mapping with implementation of HiLo and stateless session.

Steps:

  1. Create a new console application project
  2. Take the Nuget packages for the following:
    1. Nhibernate
    2. FluentNhibernate
    3. StructureMap
  3. Create a connectionstring in the app.config file with the name CustomerDatabase. Remember to replace appropriate servername and credentials below.

    <?xml version="1.0"?>

    <configuration>

      <connectionStrings>

        <add name="CustomerDatabase" connectionString="Data Source=***SERVERNAME****;Initial Catalog=TestDB;Persist Security Info=True;User ID=***USERID****;Password=***PASSWORD***" providerName="System.Data.SqlClient"/>

      </connectionStrings>

    <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>



  4. Copy the code from below to a new file.
    1. We have already defined the data model. (Classes are Customer, Address, CustomerShippingAddresses, CreditCard).
    2. We have already mapped the objects to define relationships and HiLo identities.
    3. Now, generate the schema by uncommenting the code in the region Fluent schema update and commenting out the code in region Add and Get.

      Remember to apply below statements(also mentioned in the comment of the region for Fluent schema update) after you execute the generated script in C:/Temp/NHStateless.sql

      //ALTER TABLE NH_HiLo ADD TableKey NVARCHAR(50)
      //GO
      //INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'Customer')
      //INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'Address')
      //INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'CustomerShippingAddress')
      //INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'CreditCard')

      The statements are needed to have more control over the table identities. The control will allow you to have different primary keys for the table. You can control from what number you want to start the key in each table.
      If you don’t want to have control over it then don’t execute these statements and also replace the object mappers Identity in the appropriate classes like below and generate the schema update script:
      Id(x => x.Id) .GeneratedBy .HiLo("NH_HiLo", "NextHi", "100");
      //See we have removed the last parameter where we are saying NHIbernate about specific tables key.
  5. Comment the code in region Fluent schema update, uncomment the code in region Add and Get and run the program. You can look at the database to see the data inserted and the object hierarchy and relationships.
  6. You can see the select statement using stateful session. This fetches the deep loaded object. Play around with stateless session.

Important things:

  1. This is a console application project. Remember to create the project with Target Framework 4 and not Target Framework Client Profile. Project>Properties>TargetFramework = .Net FrameWork 4
    If you don’t follow this instruction, you will get the following error:
    The type or namespace name 'StructureMap' could not be found (are you missing a using directive or an assembly reference?)

  2. Understand how the mapping of the object is done.
  3. Since we are using stateless session, it is very important to understand the object hierarchy and dependencies. You will have to take care of inserting the parent objects first and then the child objects to avoid referential integrity error. This is because Nhibernate is not going to keep track of the object hierarchy and so it will and cannot take care of Deep insert(deep load of the object).
  4. Same goes with Select statement with stateless session. No deep loading of object.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using FluentNHibernate.Cfg;

using FluentNHibernate.Cfg.Db;

using FluentNHibernate.Conventions.Helpers;

using FluentNHibernate.Mapping;

using NHibernate;

using NHibernate.Tool.hbm2ddl;

using StructureMap;

 

namespace NHibernateHiloStatesless

{

    public class Customer

    {

        public virtual long Id { get; set; }

        public virtual string Name { get; set; }

        public virtual short CustomerType { get; set; }

        public virtual IList<CustomerShippingAddresses> ShippingAddresses { get; set; }

        public virtual CreditCard CreditCard { get; set; }

    }

 

    public class Address

    {

        public virtual long Id { get; set; }

        public virtual int AddressType { get; set; }

        public virtual string Address1 { get; set; }

        public virtual string Address2 { get; set; }

        public virtual string City { get; set; }

        public virtual short State { get; set; }

        public virtual string Zip { get; set; }

    }

 

    public class CustomerShippingAddresses

    {

        public virtual long Id { get; set; }

        public virtual Customer Customer { get; set; }

        public virtual Address ShippingAddress { get; set; }

    }

 

    public class CreditCard

    {

        public virtual int Id { get; set; }

        public virtual long CreditCardNumber { get; set; }

        public virtual int ExpirationMonth { get; set; }

        public virtual int ExpirationYear { get; set; }

        public virtual int CvvCode { get; set; }

        public virtual Address CreditCardAddress { get; set; }

    }

 

    public class CustomerMap : ClassMap<Customer>

    {

        public CustomerMap()

        {

            Table("Customer");

 

            Id(x => x.Id)

                .GeneratedBy

                .HiLo("NH_HiLo", "NextHi", "100", "TableKey = 'Customer'");

           

            Map(x => x.Name);

            Map(x => x.CustomerType);

 

            References(x => x.CreditCard);

            HasMany(x => x.ShippingAddresses);

        }

    }

 

    public class AddressMap : ClassMap<Address>

    {

        public AddressMap()

        {

            Table("Address");

 

            Id(x => x.Id)

                .GeneratedBy

                .HiLo("NH_HiLo", "NextHi", "100", "TableKey = 'Address'");

 

            Map(x => x.Address1);

            Map(x => x.Address2);

            Map(x => x.City);

            Map(x => x.State);

            Map(x => x.Zip);

            Map(x => x.AddressType);

        }

    }

 

    public class CustomerShippingAddressMap : ClassMap<CustomerShippingAddresses>

    {

        public CustomerShippingAddressMap()

        {

            Table("CustomerShippingAddress");

 

            Id(x=> x.Id)

                .GeneratedBy

                .HiLo("NH_HiLo", "NextHi", "100", "TableKey = 'CustomerShippingAddress'");

 

            References(x => x.Customer);

            References(x => x.ShippingAddress);

        }

    }

 

    public class CreditCardMap: ClassMap<CreditCard>

    {

        public CreditCardMap()

        {

            Table("CreditCard");

 

            Id(x => x.Id)

                .GeneratedBy

                .HiLo("NH_HiLo", "NextHi", "100", "TableKey = 'CreditCard'");

 

            Map(x => x.CreditCardNumber);

            Map(x => x.ExpirationMonth);

            Map(x => x.ExpirationYear);

            Map(x => x.CvvCode);

          

            References(x => x.CreditCardAddress);

        }

    }

 

 

    public class NHibernateHiloStatelessSession

    {

        public NHibernateHiloStatelessSession()

        {

            ObjectFactory.Initialize(x => x.For<ISessionFactory>()

                                              .Singleton()

                                              .Use(CreateSessionFactory()));

        }

 

        private static ISessionFactory CreateSessionFactory()

        {

            return Fluently.Configure()

                .Database(

                    MsSqlConfiguration.MsSql2008

                        .ConnectionString(c => c

                                                   .FromConnectionStringWithKey("CustomerDatabase")))

                .Mappings(m =>

                          m.FluentMappings.

                            Conventions.Setup(x => x.Add(AutoImport.Never()))

                            .AddFromAssemblyOf<NHibernateHiloStatelessSession>())

                .Cache(x => x.Not.UseSecondLevelCache())

                .BuildSessionFactory();

 

        }

 

 

        public static void Main(string[] args)

        {

            #region 1. Fluent schema update

 

            //var cfg = Fluently.Configure()

            //   .Database(MsSqlConfiguration.MsSql2008.ShowSql().ConnectionString("CustomerDatabase"))

            //   .Mappings(m => m.FluentMappings.

            //                      AddFromAssemblyOf<NHibernateHiloStatelessSession>())

            //   .BuildConfiguration();

 

            //new SchemaExport(cfg).SetOutputFile(@"C:\Temp\NHStateless.sql").Create(true, false);

 

            ////********IMPORATANT*****************************

            ////Remember to append the below statements after your schema update in the file at C:/Temp/NHStateless.sql for HiLo to work as per expectation

 

            ////ALTER TABLE NH_HiLo ADD TableKey NVARCHAR(50)

            ////GO

            ////DELETE FROM [dbo].[NH_HiLo]

            ////INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'Customer')

            ////INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'Address')

            ////INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'CustomerShippingAddress')

            ////INSERT INTO [dbo].[NH_HiLo]([NextHi],[TableKey]) VALUES (1,'CreditCard')

            #endregion

 

 

            //OR

 

 

            #region 2. Add and Get

            #region Operations

            var myStatelessSessionClass = new NHibernateHiloStatelessSession();

 

            var shippingAddresses = new List<Address>

                                        {

                                            new Address

                                                {

                                                    AddressType = 1,

                                                    Address1 = "Little Rock",

                                                    Address2 = "West Little Rock",

                                                    City = "Little Rock",

                                                    State = 3,

                                                    Zip = "72202"

                                                },

                                            new Address

                                                {

                                                    AddressType = 2,

                                                    Address1 = "Little Rock",

                                                    Address2 = "West Little Rock",

                                                    City = "Little Rock",

                                                    State = 3,

                                                    Zip = "72202"

                                                }

                                        };

 

            var creditCard = new CreditCard

            {

                CreditCardNumber = 4111111111111111,

                ExpirationMonth = 12,

                ExpirationYear = 2018,

                CvvCode = 009,

                CreditCardAddress = new Address

                {

                    AddressType = 3,

                    Address1 = "Little Rock",

                    Address2 = "West Little Rock",

                    City = "Little Rock",

                    State = 3,

                    Zip = "72202"

                }

 

            };

 

            var customer = new Customer

            {

                Name = "Altaf Khatri",

                CustomerType = 1,

                CreditCard = creditCard

            };

 

            var customerShippingAddresses = shippingAddresses

                .Select(shippingAddress =>

                    new CustomerShippingAddresses

                    {

                        Customer = customer,

                        ShippingAddress = shippingAddress

                    }).ToList();

 

            customer.ShippingAddresses = customerShippingAddresses;

 

            var sessionFactory = ObjectFactory.GetInstance<ISessionFactory>();

 

            long customerId = 0;

            try

            {

               

                var session = sessionFactory.OpenStatelessSession();

                using (var transaction = session.BeginTransaction())

                {

                    session.Insert(customer.CreditCard.CreditCardAddress);

                    session.Insert(customer.CreditCard);

                    session.Insert(customer);

                    customerId = customer.Id;

 

                    foreach(var shippingAddress in customer.ShippingAddresses)

                    {

                        session.Insert(shippingAddress.ShippingAddress);

                        session.Insert(shippingAddress);

                    }

 

                    transaction.Commit();

                }

            }

            catch

            {

            }

 

 

            #endregion

 

 

            #region Get customer

 

            var statesession = sessionFactory.OpenSession();

 

            //Play around with stateless session. You will find that proxies are not fetched.

            //var session = sessionFactory.OpenStatelessSession();

            var savedCustomer = statesession.Get<Customer>(customerId);

 

            #endregion

 

            #endregion

 

        }

 

    }

}

 

 





SiteMap  |  Contact Me