Right, it's true that once this is done you don't have to go back there very often ... unless you want to do some refactoring. If you would like to rename some classes, move them to a different namespace or maybe rename some of the properties then you are in a big trouble ... Visual Studio and ReSharper will help you with refactoring of your code but you have to maintain XML files on your own.
Introduction to Fluent NHibernate
Apparently someone finally got pissed off and decided to change it ... goal was to get rid of all that maddening XML files. In other words, idea was to replace this:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3: namespace="Eg" assembly="Eg">
4:
5: <class name="Customer" table="Customers">
6: <id name="ID">
7: <generator class="identity" />
8: </id>
9:
10: <property name="Name" />
11: <property name="Credit" />
12:
13: <bag name="Products" table="Products">
14: <key column="CustomerID"/>
15: <one-to-many class="Eg.Product, Eg"/>
16: </bag>
17:
18: <component name="Address" class="Eg.Address, Eg">
19: <property name="AddressLine1" />
20: <property name="AddressLine2" />
21: <property name="CityName" />
22: <property name="CountryName" />
23: </component>
24: </class>
25: </hibernate-mapping>
with that:
1: public CustomerMap : ClassMap<Customer>
2: {
3: public CustomerMap()
4: {
5: Id(x => x.ID);
6: Map(x => x.Name);
7: Map(x => x.Credit);
8: HasMany<Product>(x => x.Products)
9: .AsBag();
10: Component<Address>(x => x.Address, m =>
11: {
12: m.Map(x => x.AddressLine1);
13: m.Map(x => x.AddressLine2);
14: m.Map(x => x.CityName);
15: m.Map(x => x.CountryName);
16: });
17: }
18: }
(example from James Gregory's blog)
In this form you can refactor your code as much as you like, it won't break your mappings. Your application is much more readable and maintainable. Thanks to Fluent NHibernate (FN) you can forget about XML files.
Quick Start Guide
We are still waiting for the first official release of Fluent NHibernate (FN) so at the moment the only option to get it is to use SVN client, check out the project and build dll on your own.
Having FluentNHibernate.dll you can simply add it to your project and start using it.
To make it easier I have prepared very basic web application which has FN set up, you can download it here. It uses popular AdventureWorks database.
Steps to get FN working:
- First of all you need to configure NHibernate which includes general configuration (connection string, dialect, driver etc) and mappings. In terms of general configuration, you have two options:
- configure NHibernate without any XML files
1: // of course, in real life, connection string should be externalized
2: const string connectionString =
3: @"Data Source=MAREK-PC\SQLEXPRESS;Database=AdventureWorks;User Id=marek;Password=marek;Network Library=DBMSSOCN;Max Pool Size=400;";
4:
5: // configure nhibernate
6: Configuration config = MsSqlConfiguration.MsSql2005
7: .ConnectionString.Is(connectionString)
8: .UseReflectionOptimizer()
9: .ShowSql()
10: .ConfigureProperties(new Configuration());
11:
12: // load mappings from this assembly
13: config.AddMappingsFromAssembly(Assembly.GetExecutingAssembly());
14:
15: // build factory
16: ISessionFactory sessionfactory = config.BuildSessionFactory();
- or you can decide to keep nhibernate.cfg.xml:
1: // read hibernate.cfg.xml
2: Configuration config = new Configuration().Configure();
3:
4: // load mappings from this assembly
5: config.AddMappingsFromAssembly(Assembly.GetExecutingAssembly());
6:
7: // build factory
8: ISessionFactory sessionfactory = config.BuildSessionFactory();
- configure NHibernate without any XML files
- Mappings, in this case my goal was to keep this example as simple as possible and elaborate it in future therefore for now only one table is mapped:
- this is a POCO object representing a product:
1: public class Product
2: {
3: public virtual int Id { get; set; }
4: public virtual string Name { get; set; }
5: public virtual string ProductNumber { get; set; }
6: public virtual bool MakeFlag { get; set; }
7: public virtual bool FinishedGoodsFlag { get; set; }
8: public virtual string Color { get; set; }
9: public virtual int SafetyStockLevel { get; set; }
10: public virtual int ReorderPoint { get; set; }
11: public virtual int StandardCost { get; set; }
12: public virtual int ListPrice { get; set; }
13: public virtual String Size { get; set; }
14: public virtual int DaysToManufacture { get; set; }
15: public virtual String ProductLine { get; set; }
16: public virtual String Class { get; set; }
17: public virtual String Style { get; set; }
18: public virtual DateTime SellStartDate { get; set; }
19: public virtual DateTime SellEndDate { get; set; }
20: public virtual DateTime ModifiedDate { get; set; }
21: }
- and mapping:
1: public ProductMap()
2: {
3: // table name is different then class name
4: WithTable("Production.Product");
5:
6: Id(x => x.Id, "ProductID");
7:
8: Map(x => x.SellEndDate);
9: Map(x => x.ReorderPoint);
10:
11: Map(x => x.Name).WithLengthOf(50).Not.Nullable();
12: Map(x => x.ProductNumber).WithLengthOf(25).Not.Nullable();
13: Map(x => x.MakeFlag).Not.Nullable();
14: Map(x => x.FinishedGoodsFlag).Not.Nullable();
15: Map(x => x.Color).WithLengthOf(15);
16: Map(x => x.SafetyStockLevel).Not.Nullable();
17: Map(x => x.StandardCost).CanNotBeNull().CustomSqlTypeIs("money");
18: Map(x => x.ListPrice).CanNotBeNull().CustomSqlTypeIs("money");
19: Map(x => x.Size).WithLengthOf(5).Not.Nullable();
20: Map(x => x.DaysToManufacture).Not.Nullable();
21: Map(x => x.ProductLine).WithLengthOf(2);
22: Map(x => x.Class).WithLengthOf(2);
23: Map(x => x.Style).WithLengthOf(2);
24: Map(x => x.SellStartDate).Not.Nullable();
25: Map(x => x.ModifiedDate).Not.Nullable();
26: }
In this case table name is different then class name that is why I had to specify it but in general FN assumes that class name equals table name. The same approach is applied for properties and columns. Again you can specify that a column has different name then a corresponding property if necessary. Also it's not required to specify generator type for primary key column. FN checks property type and uses default one.
- this is a POCO object representing a product:
- Testability - significant advantage is that you don't have to remember about keeping XML files up-to-date with your code and database schema, but also FN offers number of ways to facilitate creation of unit tests and integration test. I will cover that in a next post. (edit: post regarding integration tests)
I encourage you to keep an eye on this project. In a meantime you can start playing with the Fluent NHibernate by checking the sample web application.
The AdventureWorks database you can download from Codeplex.
(EDIT: Examples in this post have been updated on 8.02.2009 to reflect changes in Fluent NHibernate API)
Related articles:
4 comments:
It's nice you liked it :) If you liked, than also Sharp Architecture might interest you. It is a project that glues Fluent NHibernate with Asp.Net MVC, IOC container Castle Windsor and Nhibernate.Validator
Hi Aleks, I have seen Sharp Architecture before ... probably another interesting project, but as you know, we don't use ASP.NET MVC, at least not yet, therefore I decided to check only Fluent NHibernate for now.
On the other hand I would like to take a look on ASP.NET MVC soon so maybe I can check Sharp Architecture in the same time ;)
nice post :) i've been watching FN for quite some time - maybe it's finally time to try it.
same here, experimenting with it and trying to get it working on my machine and struggling at the moment! Hopefully I'll get it working with this article! Thanks
Post a Comment