Sunday, October 12, 2014

Typical NHibernate SessionFactory Auto-mapping

This auto-mapping uses NHibernate's built-in auto-mapping

The auto-mapping's customizer adapts PostgreSQL lowercase+underscore table and property naming convention to .NET's Pascal-case class and property naming convention

using NHibernate.Cfg; // .DatabaseIntegration extension method


namespace Erp.DomainMapping
{
    public static class Mapper
    {

        static NHibernate.ISessionFactory _sessionFactory = Mapper.BuildSessionFactory();


        // call this on production
        public static NHibernate.ISessionFactory SessionFactory
        {
            get { return _sessionFactory; }
        }


        public static NHibernate.ISessionFactory BuildSessionFactory(bool useUnitTest = false)
        {
            var mapper = new NHibernate.Mapping.ByCode.ConventionModelMapper();

            
            mapper.IsEntity((t, declared) => t.Namespace == "Erp.Domain.TheModels");

            mapper.BeforeMapClass += mapper_BeforeMapClass;
            mapper.BeforeMapProperty += mapper_BeforeMapProperty;
            mapper.BeforeMapManyToOne += mapper_BeforeMapManyToOne;
            mapper.BeforeMapBag += mapper_BeforeMapBag;

            var cfg = new NHibernate.Cfg.Configuration();



            // .DatabaseIntegration! Y U EXTENSION METHOD?!
            cfg.DataBaseIntegration(c =>
            {
                var cs = System.Configuration.ConfigurationManager.ConnectionStrings["TheErpConnection"].ConnectionString;


                //// SQL Server
                //c.Driver<NHibernate.Driver.SqlClientDriver>();
                //c.Dialect<NHibernate.Dialect.MsSql2008Dialect>();
                //c.ConnectionString = "Server=.;Database=TestTheDatabase;Trusted_Connection=True";

                // PostgreSQL                
                c.Driver<NHibernate.Driver.NpgsqlDriver>();
                c.Dialect<NHibernate.Dialect.PostgreSQLDialect>();
                c.ConnectionString = cs;

                if (useUnitTest)
                {
                    c.LogSqlInConsole = true;
                    c.LogFormattedSql = true;
                }
            });

            NHibernate.Cfg.MappingSchema.HbmMapping mapping =
                mapper.CompileMappingFor(typeof(Erp.Domain.TheModels.Company).Assembly.GetExportedTypes());

            cfg.AddMapping(mapping);


            // http://www.ienablemuch.com/2013/06/multilingual-and-caching-on-nhibernate.html
            //var filterDef = new NHibernate.Engine.FilterDefinition("lf", /*default condition*/ null,
            //                                           new Dictionary<string, NHibernate.Type.IType>
            //                                                           {
            //                                                               { "LanguageCultureCode", NHibernate.NHibernateUtil.String}
            //                                                           }, useManyToOne: false);
            //cfg.AddFilterDefinition(filterDef);
       


            cfg.Cache(x =>
            {
                // SysCache is not stable on unit testing
                if (!useUnitTest)
                {
                    x.Provider<NHibernate.Caches.SysCache.SysCacheProvider>();

                    // I don't know why SysCacheProvider is not stable on simultaneous unit testing, 
                    // might be SysCacheProvider is just giving one session factory, so simultaneous test see each other caches
                    // This solution doesn't work: http://stackoverflow.com/questions/700043/mstest-executing-all-my-tests-simultaneously-breaks-tests-what-to-do                    
                }
                else
                {
                    // This is more stable in unit testing
                    x.Provider<NHibernate.Cache.HashtableCacheProvider>();
                }


                // http://stackoverflow.com/questions/2365234/how-does-query-caching-improves-performance-in-nhibernate

                // Need to be explicitly turned on so the .Cacheable directive on Linq will work:                    
                x.UseQueryCache = true;
            });



            if (useUnitTest)
                cfg.SetInterceptor(new NHSQLInterceptor());



            //new NHibernate.Tool.hbm2ddl.SchemaUpdate(cfg).Execute(useStdOut: false, doUpdate: true);


            //using (var file = new System.IO.FileStream(@"c:\x\ddl.txt",
            //       System.IO.FileMode.Create,
            //       System.IO.FileAccess.ReadWrite))
            //using (var sw = new System.IO.StreamWriter(file))
            //{
            //    new SchemaUpdate(cfg)
            //        .Execute(sw.Write, false);
            //}


            var sf = cfg.BuildSessionFactory();

          


            return sf;
        }

        static void mapper_BeforeMapProperty(NHibernate.Mapping.ByCode.IModelInspector modelInspector,
            NHibernate.Mapping.ByCode.PropertyPath member, 
            NHibernate.Mapping.ByCode.IPropertyMapper propertyCustomizer)
        {
            string postgresFriendlyName = member.ToColumnName().ToLowercaseNamingConvention();
            propertyCustomizer.Column(postgresFriendlyName);
            
        }

        static void mapper_BeforeMapClass(NHibernate.Mapping.ByCode.IModelInspector modelInspector, 
            System.Type type, 
            NHibernate.Mapping.ByCode.IClassAttributesMapper classCustomizer)
        {
            
            classCustomizer.Cache(cacheMapping => cacheMapping.Usage(NHibernate.Mapping.ByCode.CacheUsage.ReadWrite));

            string className = type.Name;

            string postgresFriendlyName = className.ToLowercaseNamingConvention();
            classCustomizer.Table(postgresFriendlyName);

            System.Reflection.MemberInfo mi = type.GetMember(className + "Id",
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[0];

            classCustomizer.Id(mi,
                idMapper =>
                {
                    idMapper.Column(postgresFriendlyName + "_id");
                    idMapper.Generator(
                        NHibernate.Mapping.ByCode.Generators.Sequence,
                        generatorMapping => generatorMapping.Params(new { sequence = postgresFriendlyName + "_" + postgresFriendlyName + "_id_seq" }));

                });

       
        }



        static void mapper_BeforeMapManyToOne(
            NHibernate.Mapping.ByCode.IModelInspector modelInspector, 
            NHibernate.Mapping.ByCode.PropertyPath member, 
            NHibernate.Mapping.ByCode.IManyToOneMapper propertyCustomizer)
        {
            string postgresFriendlyName = member.ToColumnName().ToLowercaseNamingConvention() + "_id";
            propertyCustomizer.Column(postgresFriendlyName);
        }


        static void mapper_BeforeMapBag(
            NHibernate.Mapping.ByCode.IModelInspector modelInspector, 
            NHibernate.Mapping.ByCode.PropertyPath member, 
            NHibernate.Mapping.ByCode.IBagPropertiesMapper propertyCustomizer)
        {
            propertyCustomizer.Cache(cacheMapping => cacheMapping.Usage(NHibernate.Mapping.ByCode.CacheUsage.ReadWrite));
            propertyCustomizer.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Extra);

            /*
             * class Person
             * {
             *      IList<Hobby> Hobbies
             * }
             *               
             */

            string parentEntity = member.LocalMember.DeclaringType.Name.ToLowercaseNamingConvention(); // this gets the Person
            string foreignKey = parentEntity + "_id";            
            propertyCustomizer.Key(keyMapping => keyMapping.Column(foreignKey));


            // http://www.ienablemuch.com/2014/10/inverse-cascade-variations-on-nhibernate.html
            // best persistence approach: Inverse+CascadeAll 
            propertyCustomizer.Inverse(true);
            propertyCustomizer.Cascade(NHibernate.Mapping.ByCode.Cascade.All);
        }



        class NHSQLInterceptor : NHibernate.EmptyInterceptor
        {
            // http://stackoverflow.com/questions/2134565/how-to-configure-fluent-nhibernate-to-output-queries-to-trace-or-debug-instead-o
            public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
            {

                Mapper.NHibernateSQL = sql.ToString();
                return sql;
            }

        }

        public static string NHibernateSQL { get; set; }






    } // Mapper


    static class StringHelper
    {
        public static string ToLowercaseNamingConvention(this string s, bool toLowercase = true)
        {
            if (toLowercase)
            {
                var r = new System.Text.RegularExpressions.Regex(@"
                (?<=[A-Z])(?=[A-Z][a-z]) |
                 (?<=[^A-Z])(?=[A-Z]) |
                 (?<=[A-Za-z])(?=[^A-Za-z])", System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace);

                return r.Replace(s, "_").ToLower();
            }
            else
                return s;
        }
    }


}




Happy Coding!

No comments:

Post a Comment