1

I have an NHibernate project which uses mapping config files. I'm using an SQL Server database.

I want to switch to mapping by code. My approach is to do this one class at a time, confirming that all of the tests pass with each changeover.

Mixing the two mappings is quite straighforward:

  public static ISessionFactory SessionFactory
  {
     get
     {
        if (_sessionFactory == null)
        {
           var configuration = new Configuration();

           configuration.Configure();
           configuration.AddAssembly(typeof(Entities.Player).Assembly);



           var mapper = new NHibernate.Mapping.ByCode.ModelMapper();
           // Here are the entities I've switched to mapping-by-code
           DATMedia.CMS.EntityLibrary.Mappings.ScheduleMediaItem.Map(mapper);
           DATMedia.CMS.EntityLibrary.Mappings.Schedule.Map(mapper);

           configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
           _sessionFactory = configuration.BuildSessionFactory();
        }
        return _sessionFactory;
     }
  }

However, when I changed over the Schedule mapping to being mapping-by-code, I encountered major performance issues. Calls to Session.Flush would take 12 seconds, and this is over a trivial amount of test data.

I switched back to the XML mapping, and the performance problems went away.

Has anyone else experienced the problem?

I'll include the before and after mappings for schedule, just in case there's an obvious defect:

Via the config file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
   <class name="DATMedia.CMS.EntityLibrary.Entities.Schedule, DATMedia.CMS.EntityLibrary" table="cms_Schedule">
      <id name="Id" column="Id" type="Int64">
         <generator class="identity" />
      </id>
      <property name="Date" column="Date" type="Timestamp" />
      <many-to-one name="SourceTemplate" column="SourceTemplate_Id" class="DATMedia.CMS.EntityLibrary.Entities.ScheduleTemplate, DATMedia.CMS.EntityLibrary" cascade="none" fetch="join" lazy="proxy"/>
     <!-- 
      Note that we are not using cascading deletes here.
      This will be handled by SQL Server through ON DELETE CASCADE foreign key constraints     
     -->
      <bag name="MediaItems" inverse="true" cascade="save-update" lazy="true" order-by="PlayIndex">
         <key column="Schedule_Id" />
         <one-to-many class="DATMedia.CMS.EntityLibrary.Entities.ScheduleMediaItem, DATMedia.CMS.EntityLibrary"/>
      </bag>
     <bag name="PlayerGroups" table="cms_ManyToMany_PlayerGroupSchedules_PlayerGroup_Schedule" lazy="true" cascade="save-update">
       <key column="Schedule_Id" />
       <many-to-many column="PlayerGroup_Id"
                     class="DATMedia.CMS.EntityLibrary.Entities.PlayerGroup, NHibernateManyToMany" />
     </bag>
   </class>
</hibernate-mapping>  

And the mapping by code:

    public static void Map(ModelMapper mapper)
    {
        mapper.Class<DATMedia.CMS.EntityLibrary.Entities.Schedule>(
            classMapper =>
            {
                classMapper.Table("cms_Schedule");
                classMapper.Id(x => x.Id, map =>
                    {
                        map.Column("Id");
                        map.Generator(Generators.Identity);
                    });
                classMapper.Property(
                        s => s.Date,
                        propertyMapper =>
                        {
                            propertyMapper.Column("Date");
                            propertyMapper.NotNullable(true);
                        }
                );
                classMapper.ManyToOne(
                                s => s.SourceTemplate,
                                manyToOneMapper =>
                                {
                                    manyToOneMapper.Column("SourceTemplate_Id");
                                    manyToOneMapper.Cascade(Cascade.None);
                                    manyToOneMapper.Fetch(FetchKind.Join);
                                    manyToOneMapper.Lazy(LazyRelation.Proxy);
                                }
                );
                classMapper.Bag(
                            s => s.MediaItems,
                            bagPropertyMapper =>
                            {
                                bagPropertyMapper.Key(keyMapper =>
                                                    {
                                                        keyMapper.Column("Schedule_Id");

                                                    }
                                );
                                bagPropertyMapper.Inverse(true);
                                bagPropertyMapper.Cascade(Cascade.Persist);
                                bagPropertyMapper.Lazy(CollectionLazy.Lazy);
                                bagPropertyMapper.OrderBy(smi => smi.PlayIndex);
                            }
                );
                classMapper.Bag(
                        s => s.PlayerGroups,
                        bagPropertyMapper =>
                        {
                            bagPropertyMapper.Key(keyMapper =>
                                {
                                    keyMapper.Column("Schedule_Id");
                                });

                            bagPropertyMapper.Table("cms_ManyToMany_PlayerGroupSchedules_PlayerGroup_Schedule");
                            bagPropertyMapper.Lazy(CollectionLazy.Extra);
                            bagPropertyMapper.Cascade(Cascade.Persist);
                        },
                        collectionElementRelation =>
                            {
                                collectionElementRelation.ManyToMany(manyToManyMapper =>
                                    {
                                        manyToManyMapper.Column("PlayerGroup_Id");
                                    }
                                );
                            }
                    );
            }

            );
    }


Later edit

I made the problem go away by not calling Flush within a transaction.

I've tried to create some simpler test code to replicate the problem, but have been unsuccessful (all my test code runs really fast, no matter how many times I call Flush). This could be because I converted some entitys' key generation from Identity to HiLo.

So it seems in my case, my code was creating a particular configuration which brought about a problem, and hopefully this won't come back to haunt me.

If I guessed, I would say the configuration that causes problems involves a combination of thoughtless use of long running transactions combined with Identity key generation.

Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205

1 Answers1

2

I have used mixed mappings in quite a few projects and have not experienced any problems as you describe. I cannot see why the flush would take 12 seconds.

My technique for mixed mappings is slightly different to yours and I am not 100% sure if the order you configure matters, its worth a shot. See http://puredotnetcoder.blogspot.co.uk/2011/07/mixed-mappings-with-hbm-and-mapping-by.html

I take it you have you exported ALL the mappings and double checked that they ARE the same before and after. See http://puredotnetcoder.blogspot.co.uk/2011/07/view-xml-generated-when-mapping-by-code.html

Rippo
  • 22,117
  • 14
  • 78
  • 117