Saturday, May 28, 2011

Fluent NHibernate's Auto Mapping impresses me a lot

After writing a fair amount of code employing Entity Framework 4.1's code-first, I'm starting to like Entity Framework's almost zero configuration approach on mapping properties to fields and object relationships. Well I guess, it's the automatic mapping of fields and the manual selection(via DbSet<>) of objects to map to tables that impress me somehow on Entity Framework's approach.


The reason why I avoided automapping in Fluent NHibernate before is I got the wrong impression that one need to put the models in a separate assembly for him/her to have a hassle-free Fluent NHibernate awesome automapping; though putting the models in separate assembly is a good discipline, there are times you just don't want to create a separate project for models, so this decision naturally led me to always map objects and properties manually (via ClassMap<>, Map(),Id(),etc). Oh well, Entity Framework's minimalist and whitelist approach(via DbSet<>, no need to put the models in a separate project) pumped me up to find the same thing in Fluent NHibernate.


That's a big wrong impression there on part of me. Fluent NHibernate also has a whitelist capability for automapping objects to tables; it also has a mechanism to *blacklist* the properties you specified. There's no whitelist mechanism for properties, if there is, things will not be so auto anymore ;-)


Boilerplate code for automapping in Fluent NHibernate:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

using NHibernate;
using NHibernate.Dialect;

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using FluentNHibernate.Conventions.Helpers;

using TestFluentAutomapping.Model;

namespace TestFluentAutomapping
{
    public static class Mapper
    {
        static ISessionFactory _sf = null;
        public static ISessionFactory GetSessionFactory()
        {
            if (_sf != null) return _sf;

            var fc = Fluently.Configure()
                    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(@"Data Source=localhost;Initial Catalog=TestDbx;User id=sa;Password=P@$$w0rd"))
                    .Mappings
                    (   m =>
                            m.AutoMappings.Add
                            (
                                AutoMap.AssemblyOf<Program>(new CustomConfiguration())
                                   .Conventions.Add(ForeignKey.EndsWith("Id"))                                                                                                
                                .Override<Band>(n => { n.HasMany(x => x.Fans).Inverse().KeyColumn("FavoriteBandId"); } )                                
                            )
                            // .ExportTo(@"C:\_Misc\NH")
                    );

           
            // Console.WriteLine( "{0}", string.Join( ";\n", fc.BuildConfiguration().GenerateSchemaCreationScript(new MsSql2008Dialect() ) ) );
            // Console.ReadLine();

            _sf = fc.BuildSessionFactory();
            return _sf;
        }


        class CustomConfiguration : DefaultAutomappingConfiguration
        {
            IList<Type> _objectsToMap = new List<Type>()
            {
                // whitelisted objects to map
                typeof(Person), typeof(Country), typeof(Band)
            };
            public override bool ShouldMap(Type type) { return _objectsToMap.Any(x => x == type); }
            public override bool IsId(FluentNHibernate.Member member) { return member.Name == member.DeclaringType.Name + "Id"; }
        }


    }
}


Objects:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestFluentAutomapping.Model
{


    public class Country
    {
        public virtual int CountryId { get; set; }

        public virtual string CountryName { get; set; }
        public virtual int Population { get; set; }
        public virtual IList<Person> People { get; set; }
    }


    public class Band
    {
        public virtual int BandId { get; set; }
        
        public virtual string BandName { get; set; }
        public virtual IList<Person> Fans { get; set; }
    }



    public class Person
    {
        public virtual int PersonId { get; set; }

        public virtual string PersonName { get; set; }
        public virtual Country Country { get; set; }
        public virtual Band FavoriteBand { get; set; }
    }


}


Using the objects that was mapped by means of automapping:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NHibernate.Linq;

using TestFluentAutomapping.Model;

namespace TestFluentAutomapping
{
    class Program
    {
        static void Main(string[] args)
        {
            var s = Mapper.GetSessionFactory().OpenSession();

            foreach (var c in s.Query<Country>())
            {
                Console.WriteLine("\n{0}'s people", c.CountryName);

                foreach(var p in c.People)
                    Console.WriteLine("* {0}", p.PersonName);
            }

            foreach (var b in s.Query<Band>())
            {
                Console.WriteLine("\n{0}'s fans", b.BandName);
                foreach (var p in b.Fans)
                    Console.WriteLine("* {0}", p.PersonName);
            }


            Console.WriteLine("\nAll people:");
            foreach (var p in s.Query<Person>())
            {                                
                Console.WriteLine("* {0}", p.PersonName);
            }
 
            Console.ReadLine();
        }
    }
}

Output:
Philippines's people
* Michael
* Yeyet

China's people
* Jolin
* Atong

Backstreet Boys's fans
* Jolin
* Yeyet

Beatles's fans
* Michael
* Atong

All people:
* Michael
* Jolin
* Atong
* Yeyet


DDL:
create table Country
(
CountryId int identity(1,1) not null primary key,
CountryName nvarchar(100) not null unique,
Population int not null
);


create table Band
(
BandId int identity(1,1) not null primary key,
BandName nvarchar(100) not null unique,
YearStarted int not null
);


create table Person
(
PersonId int identity(1,1) not null primary key,
PersonName nvarchar(100) not null unique,
CountryId int not null references Country(CountryId),
FavoriteBandId int null references Band(BandId)
);



insert into Band(BandName,YearStarted) values('Beatles', 1957);
insert into Band(BandName,YearStarted) values('Backstreet Boys', 1900);
insert into Country(CountryName, Population) values('Philippines',9);
insert into Country(CountryName, Population) values('China',2);


insert into Person(PersonName,CountryId,FavoriteBandId) values('Michael',1,1)
insert into Person(PersonName,CountryId,FavoriteBandId) values('Yeyet',1,2)
insert into Person(PersonName,CountryId,FavoriteBandId) values('Jolin',2,2)
insert into Person(PersonName,CountryId,FavoriteBandId) values('Atong',2,1)

No comments:

Post a Comment