Wednesday, October 2, 2013

Database Table Mapping - Fluent NHibernate

Maintaining database data and interactions is a tough and time-consuming task. Object Relational Mappers (ORMs) were devised to solve precisely that problem. They allow developers to map database tables to object-oriented classes and use modern refactoring tools to make changes to the code-base. This allows for more rapid development and streamlined maintenance.

Initially, we used Castle ActiveRecord as an abstraction layer for our database. This allowed us to use attributes to map the properties of our object entities to columns in the database tables. When we decided  to move away from ActiveRecord due to lack of development progress, we used a tool to generate straight NHibernate XML mapping files from the existing mappings. These were all well and good until they actually had to be edited. Every change to a property required us to track down the related XML file and update the mapping information.

We decided to try out Fluent Nhibernate for our mappings. Fluent used C# Linq Expressions to define the relationships between entity tables and their behavior when updating the database. The beauty of Fluent was that we could still keep the XML mapping files around while we slowly moved to the more maintainable scheme. We did this by adding the configuration code shown below.

public static Configuration ApplyMappings(Configuration configuration)
{
   
     return Fluently.Configure(configuration)
         .Mappings(cfg =>
         {
             cfg.HbmMappings.AddFromAssemblyOf<Enrollment>();
             cfg.FluentMappings.AddFromAssemblyOf<Enrollment>();
         }).BuildConfiguration();
}

Then, objects could be easily mapped in code rather than using attributes or XML. We could map collections and even specify cascade behaviors for the related objects. See the example of mapping basic properties, collections and related objects below.

public class Syllabus
{
      public virtual Guid Id { get; set; }


      public virtual string Title { get; set; }


      public virtual IList<enrollment> Enrollments { get; set; }
}

public class SyllabusMapping : FluentNHibernate.Mapping.ClassMap<Syllabus>
{
      public SyllabusMapping()
      {
           Table("Syllabus");

           Id(x => x.Id)
               .GeneratedBy.Assigned()
               .UnsavedValue(new Guid("DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF"));

           Map(x => x.Title).Column("Title");

           HasMany(x => x.Enrollments)
               .KeyColumn("Syllabus")
               .LazyLoad()
               .Inverse()
               .Cascade.Delete();
     }
}

public class Enrollment
{
     public virtual Guid Id { get; set; }


     public virtual Syllabus Syllabus { get; set; }


     public virtual ActivityResultEnum Result { get; set; }
}

public class EnrollmentMapping : FluentNHibernate.Mapping.ClassMap<Enrollment>
{
     public EnrollmentMapping()
     {
          Table("Enrollments");

          Id(x => x.Id)
               .GeneratedBy.Assigned()
               .UnsavedValue(new Guid("DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF"));

          Map(x => x.Result).Column("Result").CustomType<ActivityResultEnum>();

          References(x => x.Syllabus)
               .Column("Syllabus")
               .Access.Property()
               .LazyLoad();

     }
}

One problem we faced was that our object graph used inheritance heavily. We were a little apprehensive about how complex it would be to translate that to Fluent. Fortunately for us, the solution was relatively straightforward. It required creating a base mapping class and using a discriminator column to distinguish the types.

public class SyllabusMapping : FluentNHibernate.Mapping.ClassMap<Syllabus>
{
     public SyllabusMapping()
     {
         Table("Syllabus");

         Id(x => x.Id)
             .GeneratedBy.Assigned()
             .UnsavedValue(new Guid("DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF"));

         Map(x => x.Title).Column("Title");

         HasMany(x => x.Enrollments)
             .KeyColumn("Syllabus")
             .LazyLoad()
             .Inverse()
             .Cascade.Delete();

         DiscriminateSubClassesOnColumn<SyllabusTypeEnum>("SyllabusType", SyllabusTypeEnum.Unknown); // Allows sub-classes
     }
}


public class Section : Syllabus
{
     public virtual string SectionNumber { get; set; }
}

public class SectionMapping : FluentNHibernate.Mapping.SubclassMap<Section>
{
     public SectionMapping()
     {
         DiscriminatorValue(SyllabusTypeEnum.Section);

         Map(x => x.SectionNumber).Column("SectionNumber");
     }
}

public class TrainingPlan : Syllabus
{
     public virtual int RequirementTotal { get; set; }
}

public class TrainingPlanMapping : FluentNHibernate.Mapping.SubclassMap<TrainingPlan>
{
     public TrainingPlanMapping()
     {
         DiscriminatorValue(SyllabusTypeEnum.TrainingPlan);

         Map(x => x.RequirementTotal).Column("RequirementTotal");
     }
}

Moving forward, this will allow us to refactor code more easily and maintain the system while adding new features. We will be able to focus on new features rather than spending all our time searching for enigmatic mappings. With Fluent NHibernate, we were able to move from XML files to a robust, refactor-friendly solution. Win.

About NexPort Solutions Group
NexPort Solutions Group is a division of Darwin Global, LLC, a systems and software engineering company that provides innovative, cost-effective training solutions and support for federal, state and local government, as well as the private sector.

0 comments :

Post a Comment

 
Copyright © . NexPort Solutions Engineering Blog - Posts · Comments
Theme Template by BTDesigner · Powered by Blogger