Fluent NHibernate Component

          Component(p => p.UserInfoOther,
                a =>
                {
                    a.Map(k => k.LoginName);
                });Fluent NHibernate – NHibernate without configuration files

NHibernate is great, (no doubt about it) but every time I was supposed to work with it, the fact that I had to manually type in all those xml configuration entries was something I really didn’t like at all.

The most important complains I personally have on NHibernate configuration files are:

  • It is error prone
  • Errors in configuration file can be hard to trace
  • It is not refactoring friendly
  • It is not C#, it is XML :-)

I believe that the most important reason why NHibernate is not more widely adopted by general DEV population is exactly the “Java XML configuration” PIA feeling you have while working with it.

Great news for all of us from that group: NHibernate is possible to be used WITHOUT configuration files!

 

Who, what and where?

image

Jeremy Miller coded initial bits of the Fluent NHibernate project and then James Gregory with couple of guys more bring the project to Google code and they started finalizing it. OSS rules!

So, if you go to http://code.google.com/p/fluent-nhibernate/ you would see names of other project members together with the simple code illustrating the goal fluent nhibernate project tries to achieve.

As with the most OSS projects, to get the source code of FNH, download it from Subversion trunk located at http://fluent-nhibernate.googlecode.com/svn/trunk/

 

Solution file consist of couple of projects:

  • LIb solution folder contains DLL you would be using in your application.
  • Test project containing a bunch of unit tests which you can use as starting point to get detail understanding on how the things works.
  • Quick start project which highlights most important concepts you need to start using NHibernate

Note: Project is initially built with NHibernate 2.0 which collides with Linq For NHibernate (using NHibernate 2.1) so I updated FNH project references to use 2.1 too 

Fluent NHibernate in action

Solution project structure

image

In this blog post, I will use the same example Jimmy Nilsson used in his Applying Domain-Driven Design and Patterns: With Examples in C# and .NET NHibernate chapter so you can compare outcome of my blog post sample it with how it looked in his book (with the configuration files in place).

Solution file of today’s example contains 3 projects:

  • BusinessLayer (“Domain”)
    Contains definitions of entities which we use in modeling business use cases.
    Important to be mentioned here is that BusinessLayer  project doesn’t have any reference to neither other projects of this solution nor the “3pty dlls” such is NHibernate.dll
  • NHConfigMappings (“Mapping project”)
    Contains ORM mapping definitions which enables nhibernate to persist to relational DB domain entities
  • NHCongigMappings.Test

Contains test fixture illustrating how mappings should be consumed and how nhibernate engine could be initialized without any xml configuration files

(Source code used in today blog post can be download from here) 

Domain project

image

As you can see, it is very simple sample where we have entity customer class containing “embedded” Address value object and referencing the list of reference persons customer contacted.

Same diagram given in code would look like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
  
namespace CustomerConfiguration
{
    public class Customer
    {
        public virtual Guid Id { get; set; }
  
        public virtual string CustomerNumber { get; set; }
  
        public virtual string Name { get; set; }
  
        public virtual Address CustomerAddress { get; set; }
  
        public virtual IList<REFERENCEPERSON> ReferencePersons { get; set; }
    }
}

Address type will look like this:

1
2
3
4
5
6
7
8
9
10
namespace CustomerConfiguration
{
    public class Address
    {
        public virtual string Street { get; set; }
        public virtual string PostalCode { get; set; }
        public virtual string Town { get; set; }
        public virtual string Country { get; set; }
    }
}

And ReferencePerson type would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
  
namespace CustomerConfiguration
{
    public class ReferencePerson
    {
        public virtual Guid Id { get; set; }
  
        public virtual string FirstName { get; set; }
  
        public virtual string SurName { get; set; }
    }
}

As you can see from the code above, NHibernate allows us fully persistent ignorant POCO software development: no special attributes, no special base class or interface…

Mapping project

Once we have domain logic defined, we need to provide information on how NHibernate should map our classes with appropriate DB entities: tables and columns.

For my example DB would look like this:

image

As you can see, although in my code I have separate Address class I don’t have it in DB. Instead I have it embedded in Customer table as last 4 columns. Also, table names in DB are in plural form while names of classes are in singular form.

Another small difference here is that in ReferencePerson class has a LastName property while DB has column SurName.

Mapping ReferencePerson

I won’t throw in here example of how that would look if I would use XML configuration file, because the whole point is to forget that ASAP :) (In case you want to see it, check it out here)

In order to define NHibernate mapping, in FNH you need to create a mapping class for each one of entities (which is something Ayende don’t get).

That mapping class should inherit from  ClassMap<T> class in order to get access to FHN members providing programmatic access over configuration settings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using CustomerConfiguration;
using FluentNHibernate.Mapping;
  
namespace NHConfigMappings
{
    public class ReferencePersonMap : ClassMap<ReferencePerson>
    {
        public ReferencePersonMap()
        {
            TableName = “ReferencePersons”;
              
            Id(x => x.Id).GeneratedBy.GuidComb()
                .WithUnsavedValue(“00000000-0000-0000-0000-000000000000”);
  
            Map(x => x.FirstName).WithLengthOf(50).CanNotBeNull();
            Map(x => x.LastName,”SurName”).WithLengthOf(50).CanNotBeNull();
        }
    }
}

Main advantage of fluent interface is that  it produces code with good readability, which I believe we can see from code snippet above.

In a class constructor, I am defining:

  1. What is the name of data table where the class will be persisted. In my example table name and class name differs so I had to define it here, but I think it is good practice to define it always, even they are implicitly the same by the default
  2. Which class property should be used as Identity Field populating table PK. In example I took I am using ReferenceMap.Id property where NHibernate generate its value using GuidComb  value as performance effective type of GUID values. At the end I defined how unsaved value is looking like so NHibernate would be able to deduct how the class should be persisted (insert or update)
  3. That the FirstName property should be mapped to the column with the same name where the column length is 50 characters and null values are not allowed
  4. That the LastName property should be mapped to the column named “SurName” with the maximum length of 50 characters without null values being allowed

In my opinion main advantages here are that the configuration code is human readable and refactoring friendly (if I ever do changes on ReferencePerson class mapping file will be broken so we have “type safe” configuration settings)

Mapping Customer

Customer class mapping definition is stored in CustomerMap (implementing the ClassMap<Customer> class looking something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using CustomerConfiguration;
using FluentNHibernate.Mapping;
  
namespace NHConfigMappings
{
    public class CustomerMap : ClassMap<Customer>
    {
        public CustomerMap()
        {
            TableName = “Customers”;
              
            Id(x => x.Id).GeneratedBy.GuidComb()
                .WithUnsavedValue”00000000-0000-0000-0000-000000000000”);
              
            Map(x => x.Name).WithLengthOf(50);
            Map(x => x.CustomerNumber).WithLengthOf(50);
              
        HasMany<ReferencePerson>(x => x.ReferencePersons)
                .Access.AsProperty()
                .AsBag().WithKeyColumn("CustomerId")
                .Cascade.All();                        
              
            Component<Address>(x => x.CustomerAddress, 
                               m =>
                                   {
                                       m.Map(x => x.Street).WithLengthOf(100);
                                       m.Map(x => x.PostalCode).WithLengthOf(6);
                                       m.Map(x => x.Town).WithLengthOf(30);
                                       m.Map(x => x.Country).WithLengthOf(50);
                                   });
        }
    }
}

In a class constructor, I am defining:

  1. The name of table to which this class map
  2. Customer.Id as primary key containing guid value
  3. Customer.Name is mapped to the Name nvarchar(50) column
  4. Customer.CustomerNumber is mapped to the CustomerNumber nvarchar(50) column
  5. Customer class contains collection of ReferencePerson instances stored in Customer.ReferencePersons property

    which should be mapped in separate table (AsBag())  where role of FK will be performed by ReferencePerson.CustomerId

    and with cascading of events (Insert of Customer will result with Insert of ReferencePerson etc)

  6. Customer contains property CustomerAddress of type Address as embed value where
    1. Address.Street is nvarchar(100) column
    2. Address.PostalCode  is nvarchar(6) column
    3. Address.Town is nvarchar(30) column
    4. Address.Country is nvarchar(50) column

Once again, take a look at the class above. I have just described something which looks like typical use case we face in real world with something which (IMHO) has very high readability and  it is easy to be written

 

Unit testing fluent nhibernate mappings

In order to present how those mappings should be used I wrote unit test project which will present:

  • how this mappings are to be used
  • how to configure nhibernate itself without the need for hibernate.cfg.xml file

For the purpose of this test I choose MS Test just for kicks (although I am mostly using MBUnit )

 

How to initialize nhibernate without hibernate.cfg.xml

Due to the fact that initializing nhibernate engine is slow, I am initializing it once per text fixture and in test fixture of this example nhibernate initialization routine in case of fluent nhibernate is a 5 step procedure:

  • Initialize type implementing IPersistenceConfigurer (MSSqlConfiguration, SQLLiteConfiguration, PostgreSQLConfiguration are currently supported)
  • use instance of that type (in my example instance of MSSqlConfiguration) to preset properties of NHibernate.Configuration instance
  • create instance of FluentHibernate.PersistenceModel type
  • use that instance to load the assembly containing mappings
  • use that instance to inject mappings to nhibernate configuration instance

At the end of configuration sequence I have  operational NHibernate configuration instance which I use to create session factory which I then store as a class field.

Once I have that SessionFactory field in place, each one of the test methods just uses it to create a session needed.

Let’s take a look at how this initialization routine could look in code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static ISessionFactory _sessionFactory;
 
    [ClassInitialize]
    public static void FixtureInit(TestContext testContext)
    {
        // initialize persistance configurer
        IPersistenceConfigurer persistenceConfigurer =
            MsSqlConfiguration
                .MsSql2005
                .ConnectionString.Is(
                “Data Source=.SQL2008;Initial Catalog=NHibernateBlog;"
        + "Integrated Security=True”)
                .ShowSql();
 
        // initialize nhibernate with persistance configurer properties
        Configuration cfg = persistenceConfigurer
            .ConfigureProperties(new Configuration());
 
        // add mappings definition to nhibernate configuration
        var persistenceModel = new PersistenceModel();
        persistenceModel.addMappingsFromAssembly
        (Assembly.Load(“NHConfigMappings”));
        persistenceModel.Configure(cfg);
 
        // set session factory field which is to be used in tests
        _sessionFactory = cfg.BuildSessionFactory();
    }

I think code above is simple enough (thanks to FNH) that it doesn’t need any extra explanations about its  concrete implementation.

Nhibernate unit test method

Now when we have that session factory up and going, we would make only a simple test where we will create a new customer and try to store it in DB.

That test code could look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[TestMethod]
public void ReferencePerson_Create_ShouldCreateRowInDb()
{
    var customer = new Customer
       {
           Name = “John Doe”,
           CustomerNumber = “12345”,
           CustomerAddress = new Address
                                 {
                                     Street = “1st Mayson Street”,
                                     PostalCode = “01754”,
                                     Town = “Maynard”,
                                     Country = “USA”
                                 },
           ReferencePersons = new List<ReferencePerson>
                                  {
                                      new ReferencePerson
                                          {
                                              FirstName = “Nikola”,
                                              LastName = “Malovic”
                                          }
                                  }
       };
 
    ISession session = _sessionFactory.OpenSession();
    Guid id=(Guid)session.Save(customer);
    session.Flush();
 
    session.Evict(customer);
      
    var customerDB= session.Get<Customer>(id);
    Assert.IsTrue(  customerDB.Id == customer.Id && customerDB.Name == customer.Name 
                    && customerDB.CustomerAddress.Street == “1st Mayson Street”
                    && customerDB.ReferencePersons[0].FirstName == “Nikola”);
 
}

At the beginning of the test method I create a new customer instance with CustomerAddress data and single ReferencePerson instance in ReferencePersons.

Then I use the sessionFactory created in test fixture set up, and create a session which then I use to persist customer instance. As a result of that persistence method call I am getting customer identity value used as PK value in DB.At the end of that persistence code I just flush the session in order to commit the changes.

Now when (hopefully) our customer is saved I need to test that and the usual way of testing save is to try to load the data from DB. That’s why I remove the customer instance from NHibernate Identity Map “cache” so I won’t get results from memory and then I am trying to retrieve data using the identity value I retrieved during persistence.

With the retrieved customer data I do just quick value check (in real world test this check would look different).

Test will pass therefore if retrieved data will match the data of customer instance created in this test.

 

Running the test

Once I run the test, I got successful test 

image

And fast look at the DB shows that data are persisted

image

Test passed!

 

Conclusion

Purpose of this blog post was to present a way of using NHibernate different then the standard xml configuration approach most of people use right now working with NHibernate.

I hope you can see for yourself that it is VERY possible to use NHibernate without a single configuration file on very DEV friendly and intuitive way.

The example I presented here is just tip of the iceberg so I strongly encourage you to sync Fluent NHibernate source code and play with quick start and test projects.

Here are also some links you can check out too:

Happy Hibernating!

原文地址:https://www.cnblogs.com/aaa6818162/p/3069135.html