The official Fatica Labs Blog! RSS 2.0
# Wednesday, July 25, 2012

I just did some new project at work with heavy and extensive usage of data access over legacy databases, and I tried the DapperDotNet micro or/m instead of NHibernate. I just point before the fact that I've already all the infrastructure to map such a legacy DB in NH by using mapping by code and leveraging a lot of conventios in the DB table/field naming, so the mapping work does not make any difference for me, a part the fact that it is not needed with dapper ( or at least, not needed in the Entity based form ) since you just map the data transfer structures. So what's missing from using NH? Lets see:

  • Inheritance I was a little worried about Dapper leak of support for any kind of inheritance concept, but really I managed to do all the requirement without it, having the best dsl for querying the database did the work.
  • Identity Map We have to keep an eye to the fact the identity map does not exist anymore using a micro/orm. This not just in subsequnt queries in the same section, but when we load associations, expecially when the associated class has a lot of data. For example I had an association with an entity containing a big bounch of xml, if I load that association in a dto, I need to manage myself to load it just when the associated id changes.
  • Lazy Collections using Dapper we have to forget such automatic features, basically there is not such a concept, but I really can live without it.
  • Db Schema Create/Update I really miss that just in unit testing. You have to craft the schema by hand in your unit test. In production in my case I have no control for the schema generation *at all* so it is not a problem anyway, but I guess the NH update / generation is not enough for a real DB deployment. You probably need a DB migration in any case.
  • Linq/Hql In fact I miss LinqToNh. Not absolutely Hql. But we have to consider that a big portion of the impedence an OR/M introduces is caused to the creation of a DSL on top of plain SQL.

Let's consider the pure benefit we have from Dapper:

  • Any kind of optimized SQL is easy to submit.
  • Calling an SP handling In/out parametrs is simple as calling a query
  • Multiple resultset are easy to handle ( The Future<> in NH )
  • Bulk operations are easy too ( you still need real bulk if you realaly want to insert big amount of data )
  • Really noticeable increase in performance, due to smart ADO.NET underlayng access and to the fact we control the SQL roundtrip ourself )

So in my opinion: we probably code a little more in the data access phase, but we have more control, there is no a separate "mapping" part, that can be not so easy to mantain, but it really worth the effort to move definitely in the Micro Orm direction.

Wednesday, July 25, 2012 11:32:20 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
C# | NHibernate | ORM

# Thursday, April 12, 2012

There are scenarios in which NHibernate performance decrease even if we do all the effort to correctly use it. This could happen if we need in some circumstances to load a lot of record ( I explicitly use record instead of ‘Entity’ ) from some relational structures, and doing this the OR/M way means overload the session with a lot of entities, that is painful in term of speed. Other cases happens when we need to  write or update something that is not properly represented in the entity model we have, maybe because the model is more “read” oriented. Other cases? I’m not able to grasp all  of course, but I’m sure that you face some if you use an OR/M ( not necessarily NH ) in your daily basis. Using NHibernate an alternative could be using FlushMode=Never in session, but you still have all the OR/M plumbing in the hydrating entity code that negatively impacts the performances. I obtained impressive results in solving such a situation, by using Dapper, a so called single file OR/M. It is a single file that provider some IDbConnection extension methods, those methods works on an already opened connection, so we can use the connection sticked to the NHibernate open session, as here below:

// don't get confused by LinqToNh Query<> this one is the Dapper query
// acting on the CONNECTION :)

session.Connection.Query<MyDto>("select Name=t.Name,Mail=t.Mail from mytable t where t.Valid=@Valid",new{Valid=true});




you obtain back a big recordset of MyDto instances in almost the same time if you wire by hand a DateReader vertical on the dto, with all the error checking.

So why don’t use it always?

Because despite the name Dapper is not an OR/M, it does not keep track of modified entities, it does not help you in paginating results or lazy load the entity graph, neither helps in porting from one SQL dialect to another.

Is this strategy used somewhere else?

You probably find interesting to read this post by Sam Saffron, this solution is used in Stackoverflow.com combined with the LinqToSql OR/M to help when the OR/M performance are not enough.

By my test I experienced a performance increase of 10x in a very hacking situation, but I can’t show the case since it is not public code. Something more scientific about performance is here.

Thursday, April 12, 2012 9:41:12 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
CodeProject | Dapper | NHibernate | ORM

# Sunday, November 27, 2011

In this post Ayende talk about when we should use NHibernate and he point that in almost read only scenario other approach can be preferred. I think he forget to mention the fact that even in such a scenario we can leverage a very reliable multi DB abstraction offered by NH that can help us if we think to target different data platforms. In order to me we should say that the point of decision to choose fro NH to another approach is the ability to create an entity model, and an entity model helpful to our objectives. This can also depends on how much we are confortable with the technology. Another interesting extension of the argument is, if we should not  use NH what can we use instead ? Well not for sure EF, since the reason of renounce to NH in a project should be the same to avoid EF. The NOSql solutions works only if we can completely avoid a relational database, and the pure crude ADO.NET is just ugly. An option could be Dapper,  a lightweight OR/M ( not exactly an OR/M, but almost ) that remove all the ugliness of ADO.NET and does not change the performance in comparison on using the manual data access approach. I did not tried it myself, but one of its users is stackoverlow, so this should be by itself a guarantee.

Sunday, November 27, 2011 8:56:37 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
CodeProject | NHibernate | ORM

# Thursday, November 17, 2011

Even if Linq To NHibernate provider allow us to write query in a strongly type manner, it is sometimes needed to works with property names literally. For example in a RIA application a service can receive a column as a string containing the name of the property to order by. Since Linq to NHibernate is a standard Linq provider, we can leverage a standard dynamic linq parser. This is achieved by using an old code by MS, known as System.Linq.Dynamic. By following the link you will find a download location that point to an almost just a sample project that eventually contains the file Dynamic.cs that contains some extension method allowing to merge literal parts in a type safe linq query.

Let’see an example:

var elist = session.Query<MyEntity>()
              .OrderBy(“Name descending”)
              .Skip(first)
              .Take(count)
              .ToList();

I supposed we have a property called Name on the class MyEntity. The OrderBy taking a string as a parameter is an extension method provided by Dynamic.cs, and in order to have it working you just need to merge the file dynamic.cs in your project and import System.Linq.Dynamic. Of course you will have extension for Where and for other linq operators too.

Thursday, November 17, 2011 1:15:19 PM (GMT Standard Time, UTC+00:00)  #    Comments [5] - Trackback
CodeProject | NHibernate

# Wednesday, November 02, 2011

Sometimes when we use mapping by code we want to see the generated HBM, just to understand if all is working as expected. I did this in some my old posts by dumping the hbm with an helper method, but I simply miss that the function exists as an extension method. Here below an example:

 var compiled = mapper.CompileMappingForAllExplicitlyAddedEntities();
#if DEBUG
            Trace.WriteLine(compiled.AsString());
#endif
            cfg.AddMapping(compiled);

 

We simply need to include the following using:

using NHibernate.Mapping.ByCode;

Here I use the AsString method that serialize in a string the compiled mapping.
Wednesday, November 02, 2011 2:39:29 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
NH Mapping By Code | NHibernate

# Thursday, October 20, 2011

Since it is boring having to specify every time the Schema a table belongs to, it would be nice having a conventional strategy to automatically wire up this information. With NHibernate mapping by code functionality this is simple. The strategy I propose is having the last part of the entity namespace being equal with the schema. So for example we have an entity Product in the schema named Production we define the entity as:

MyEntities.Production.Product

Then by using a ConventionModelMapper we can write:

public class MyModelMapper:ConventionModelMapper
    {
        public MyModelMapper()
        {
            this.BeforeMapClass += (modelInspector, type, customizer) =>
                {
                    customizer.Schema(type.Namespace.Split('.').Last());
                };
        }
        
    }

And we achieve to write less line again :)

Thursday, October 20, 2011 12:05:18 PM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
NH Mapping By Code | NHibernate

# Wednesday, September 28, 2011

This is just an example helper class to create a generic IResultTransformer that allow us to convert an NHibernate result into a string[] suitable for generating a CSV. Nothing advanced, just some basic stuff and a fluent interface syntax. Let’see the class:

class CsvTransformer<T>:IResultTransformer
    {
        List<Func<T, string>> mappers = new List<Func<T,string>>();
        Func<object,T> elemSelector = k=>(T)((object[])k)[0];
        public CsvTransformer()
        {
            
        }
        #region IResultTransformer Members

        public System.Collections.IList TransformList(System.Collections.IList collection)
        {
            List<string[]> csv = new List<string[]>();
            foreach (object elem in collection)
            {
                List<string> row = new List<string>();
                foreach (var map in mappers)
                    row.Add(map(elemSelector(elem)));
                csv.Add(row.ToArray());
            }
            return csv;
        }

        public object TransformTuple(object[] tuple, string[] aliases)
        {
            return tuple;
        }

        #endregion
        public CsvTransformer<T> Extract(Func<object, T> elemSelector)
        {
            this.elemSelector = elemSelector;
            return this;
        }
        public CsvTransformer<T> Map(Func<T, string> map)
        {
            mappers.Add(map);
            return this;
        }
    }

 

And an example usage can be:

session.QueryOver<MyEntity>()
                        .TransformUsing(newTransformers.CsvTransformer<MyEntity>()
                        .Map(k=>k.Field1)
                        .Map(k=>k.Field2)
                        .Map(k=>k.Date.HasValue?k.Date.Value.ToShortDateString():"")
                        )
                    .List<string[]>();

We can also customize how to extract data from the NHibernate returned tuple by using the .Extract method.

Easy to use and to embed as a stand alone file in our projects. What about having something similar integrating AutoMapper ?

Wednesday, September 28, 2011 3:47:56 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate

# Tuesday, September 27, 2011

NHibernate QueryOver is a new API ( from version 3.0 )  that allow to create queries the ICriteria way, but with lambda expressions instead of raw strings. With QueryOver we have the benefit of intellisense and the code is refactoring friendly. At the moment one reference can be found on this blog post. Something not so obvious to do is a query over a portion of a date property, for instance all the entities in a certain month, and so on. Let’s have an example considering this simple entity:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"  assembly="MyAssembly" namespace="MyAssembly.MyNamespace">
  <class name="Item" table="Items">
    
    <id name="Id"  type="Int64">
      <generator class="native"/>
    </id>
        
    <property type="AnsiString" name="Code" length="32"></property>
    <property type="DateTime" name="StartDate"></property>
    
  </class>
</hibernate-mapping>

And suppose we want to fetch all the entities having start date in the year 2011, with QueryOver we write:

var result = session.QueryOver<Item>()
                    .Where(
                    Restrictions.Eq(
                    Projections.SqlFunction("year"
                    , NHibernateUtil.Int32
                    , Projections.Property<Item>(item => item.StartDate)
                    ) 
                    ,2011)).List();

 

that yield the following query ( with MSSQL 2005 dialect ):

SELECT
        this_.Id as Id21_0_,
        this_.Code as Code21_0_,
        this_.StartDate as StartDate21_0_
    FROM
        Items this_
    WHERE
        datepart(year, this_.StartDate) = @p0;
    @p0 = 2011 [Type: Int32 (0)]

 

obviously we can select a projection with a portion of the date to, suppose we want to fetch the day of all item in a certain date range, we can write:

var session = NHHelper.Instance.CurrentSession;
               var result = session.QueryOver<Item>()
              .WhereRestrictionOn(k => k.StartDate).IsBetween(dlow)
              .And(dup)
              .Select(
                       Projections.SqlFunction("day"
                                   , NHibernateUtil.Int32
                                   , Projections.Property<Item>(p => p.StartDate)
                                   ))
              .List<int>();

 

obtaining the following query:

SELECT
        datepart(day,
        this_.StartDate) as y0_
    FROM
        Items this_
    WHERE
        this_.StartDate between @p0 and @p1;
    @p0 = 01/09/2011 00:00:00 [Type: DateTime (0)], @p1 = 29/09/2011 00:00:00 [Type: DateTime (0)]

 

These are really common situation when we have to fill-up gui elements, and with these tricks we can write optimal and fast queries.

Addition:

As pointed by Vahid ( Thanks a lot ! ) in the comment, with the latest version ( NH 3.2 ) things are easier since there is a shortcut for dateparts as projection extension, so the first sample became:

session.QueryOver<Item>()
                 .Where(
                    p=>p.StartDate.YearPart()==2011
                   
                    ).List();

 

and the second one:

 

session.QueryOver<Item>()
                .Where(
                   p=>p.StartDate.IsBetween(dlow).And(dup)
                   )
                   .Select(k=>k.StartDate.DayPart() )
                   .List();
surely better, and this is the way to go with newer NH versions, and producing of course the same DB queries.
Tuesday, September 27, 2011 11:48:57 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2] - Trackback
NHibernate | QueryOver

# Friday, September 09, 2011

This post is an exercise, similar to this and this previous posts about using NHibernate  mapping by code new features present form version 3.2. The source inspiring it is an old post form Ayende, showing a non trivial requirement to map.

Here the DB model:

And the wanted object model:

So there is a lot of comments about DB refactoring needing, or on needing to have the linking entity as a visible entity in the model, but:

  • I like the idea of collapsing the linking entity.
  • I suppose that the DB is untouchable, as frequently happens.

Ayende solves the trouble by the <join/> mapping having an entity spawning two tables, so Address will be represented by joining the Table Address and PeopleAddress.

This can be done very easily in Mapping by code too, lets see how:

 

ModelMapper mapper = new ModelMapper();
            mapper.Class<Person>(m =>
                {
                    m.Id(k => k.Id,g=>g.Generator(Generators.Native));
                    m.Table("People");
                    m.Property(k => k.Name);
                    m.Bag(k => k.Addresses, t => 
                            { 
                                t.Table("PeopleAddresses");
                                t.Key(c=>c.Column("PersonId"));
                                t.Inverse(true);
                                
                            }
                         ,rel=>rel.ManyToMany(many=>many.Column("AddressId"))
                        );
                }

                );

            mapper.Class<Address>(m =>
                {
                    m.Id(k => k.Id, g => g.Generator(Generators.Native));
                    m.Table("Addresses");
                    m.Property(p => p.City);

                    m.Join("PeopleAddresses", z => 
{
z.Property(p => p.IsDefault);
z.Property(p => p.ValidFrom);
z.Property(p => p.ValidTo);
z.Key(k => k.Column("PersonId"));
});

That yield  the following mapping:

<?xml version="1.0" encoding="utf-16"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="TestMappingByCode" assembly="TestMappingByCode" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Person" table="People">
    <id name="Id" type="Int32">
      <generator class="native" />
    </id>
    <property name="Name" />
    <bag name="Addresses" table="PeopleAddresses" inverse="true">
      <key column="PersonId" />
      <many-to-many class="Address" column="AddressId" />
    </bag>
  </class>
  <class name="Address" table="Addresses">
    <id name="Id" type="Int32">
      <generator class="native" />
    </id>
    <property name="City" />
    <join table="PeopleAddresses">
      <key column="PersonId" />
      <property name="IsDefault" />
      <property name="ValidFrom" />
      <property name="ValidTo" />
    </join>
  </class>
</hibernate-mapping>

 

Exactly the ones that Ayende proposed. As you can see is pretty straightforward map even a not so common situation.

Friday, September 09, 2011 11:59:53 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
NH Mapping By Code | NHibernate | ORM

# Friday, September 02, 2011

In this post we done some effort in automatically generate the mapping based on convention, but we miss a very common one: table names is usually the pluralized entity name. This is usually done by using an inflector. Thanks to Stack Overflow, I found this question about it, and choose that one, that is a single easily embeddable file. So we modify a little our AutoMapper class as below:

void AutoMapper_BeforeMapClass(IModelInspector modelInspector, Type type, IClassAttributesMapper classCustomizer)
       {
           //
           // Create the column name as "c"+EntityName+"Id"
           //
           classCustomizer.Id(k => 
                               { 
                                   k.Generator(Generators.Native); k.Column("c" + type.Name + "Id"); 
                               }
                               );
           classCustomizer.Table(Inflector.Pluralize(type.Name));
        }

 

And this is all, the generated mapping will change as:

<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:x=""
sd="http://www.w3.org/2001/XMLSchema" namespace="MappingByCode" assembly="Mappin
gByCode" xmlns="urn:nhibernate-mapping-2.2">
  <class name="SimpleEntity" table="SimpleEntities">
    <id name="Id" column="cSimpleEntityId" type="Int32">
      <generator class="native" />
    </id>
    <property name="Description">
      <column name="txtSimpleEntityDescr" sql-type="AnsiString" />
    </property>
    <many-to-one name="Referred" column="cReferredId" />
  </class>

Just for better sharing, I published this “laboratory” project here.

Friday, September 02, 2011 2:42:10 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NH Mapping By Code | NHibernate | ORM

# Thursday, September 01, 2011

Since version 3.2.0 NHibernate  has an embedded strategy for mapping by code, that basically comes from Fabio Maulo’s ConfORM. With some reading at this Fabio post,  this other one, and this one too, I wrote my own sample just to see what we can do.

Even if we can use mapping by code to map class by class the entire model, something more interesting can be done by writing some convention-based automatic mapper, that can help us even when we face legacy ( non code first ) databases with some (perverted) naming convention.

We have to consider first the ModelMapper class, this class in the NH mapping by code is the one responsible for driving the mapping generator. It provides a suite of events to intercept the actual generation of each elements in the mapping. By listening these event we can decorate the detail of the single element, for example the Id generator class, the SqlType, the column name, and so on. ModelMapper uses a ModelInspector to get the way we want to map each portion of the entity ( properties, many-to-one, collections ), or if we have a component, or a subclass and so on. We realize our AutoMapper class by deriving a ModelMapper and internally subscribing some events, and passing to it a custom ModelInspector ( we named it AutoModelInspector ).

Let’s start with a very basic model:

ultra simple model

Basically an entity that unidirectionally associates with a referred one. Let’s say we have these example database conventions:

  • Identifier column are named “c”+EntityName+”Id” and are autoincrement
  • Column description are named “txt”+EntityName+”Descr”
  • Column of type string have to be prefixed with “txt”
  • Column of type string have to be AnsiString ( for DDL generation of CHAR instead of NChar )
  • Foreign key column have to be called “c”+ForeignEntityName+”Id”

So let’s see how we wrote the custom model mapper:

class AutoMapper:ModelMapper
    {
        public AutoMapper()
            : base(new AutoModelInspector())
        {
            //subscribe required ebvents for this simple strategy ...
            this.BeforeMapClass += new RootClassMappingHandler(AutoMapper_BeforeMapClass);
            this.BeforeMapProperty += new PropertyMappingHandler(AutoMapper_BeforeMapProperty);
            this.BeforeMapManyToOne += new ManyToOneMappingHandler(AutoMapper_BeforeMapManyToOne);
            //...
            //other events....
        }
        
        void AutoMapper_BeforeMapManyToOne(IModelInspector modelInspector, PropertyPath member, IManyToOneMapper propertyCustomizer)
        {
            //
            // name the column for many to one as
            // "c"+foreignEntityName+"id"
            //
            var pi = member.LocalMember as PropertyInfo;
            if (null != pi)
            {
                propertyCustomizer.Column(k => k.Name("c"+pi.PropertyType.Name+"Id"));
            }
        }

        void AutoMapper_BeforeMapProperty(IModelInspector modelInspector, PropertyPath member, IPropertyMapper propertyCustomizer)
        {
            //
            // Treat description as a special case: "txt"+EntityName+"Descr"
            // but for all property of type string prefix with "txt"
            //
            if (member.LocalMember.Name == "Description")
            {
                propertyCustomizer.Column(k =>
                    {
                        k.Name("txt" + member.GetContainerEntity(modelInspector).Name + "Descr");
                        k.SqlType("AnsiString");
                    }
                    );
            }
            else
            {
                var pi = member.LocalMember as PropertyInfo;
                
                if (null != pi && pi.PropertyType == typeof(string))
                {
                    propertyCustomizer.Column(k =>
                    {
                        k.Name("txt" + member.LocalMember.Name);
                        k.SqlType("AnsiString");
                    }
                   );
                }
            }
        }
       
        void AutoMapper_BeforeMapClass(IModelInspector modelInspector, Type type, IClassAttributesMapper classCustomizer)
        {
            //
            // Create the column name as "c"+EntityName+"Id"
            //
            classCustomizer.Id(k => { k.Generator(Generators.Native); k.Column("c" + type.Name + "Id"); });
        }

        
    }

 

The event handlers apply the convention we said before. As we see we pass a special model inspector in the constructor, that is implemented as below:

class AutoModelInspector:IModelInspector
    {
        #region IModelInspector Members

       
        public IEnumerable<string> GetPropertiesSplits(Type type)
        {
            return new string[0];
        }

        public bool IsAny(System.Reflection.MemberInfo member)
        {
            return false;
        }

        
        public bool IsComponent(Type type)
        {
            return false;
        }

       
        public bool IsEntity(Type type)
        {
            return true;
        }

       
        
        public bool IsManyToOne(System.Reflection.MemberInfo member)
        {
            //property referring other entity is considered many-to-ones...
            var pi = member as PropertyInfo;
            if (null != pi)
            {
                return pi.PropertyType.FullName.IndexOf("MappingByCode") != -1;
            }
            return false;
        }

        public bool IsMemberOfComposedId(System.Reflection.MemberInfo member)
        {
            return false;
        }

        public bool IsMemberOfNaturalId(System.Reflection.MemberInfo member)
        {
            return false;
        }

      
        public bool IsPersistentId(System.Reflection.MemberInfo member)
        {
            return member.Name == "Id";
        }

        public bool IsPersistentProperty(System.Reflection.MemberInfo member)
        {
            return member.Name != "Id";
        }

        public bool IsProperty(System.Reflection.MemberInfo member)
        {
            if (member.Name != "Id") // property named id have to be mapped as keys...
            {
                var pi = member as PropertyInfo;
                if (null != pi)
                {
                    // just simple stading that if a property is an entity we have 
                    // a many-to-one relation type, so property is false
                    if (pi.PropertyType.FullName.IndexOf("MappingByCode") == -1)
                        return true;
                }

            }
            return false;
                
        }

        public bool IsRootEntity(Type type)
        {
            return type.BaseType == typeof(object);
        }

       
        
        public bool IsTablePerClassSplit(Type type, object splitGroupId, System.Reflection.MemberInfo member)
        {
            return false;
        }

       
        public bool IsVersion(System.Reflection.MemberInfo member)
        {
            return false;
        }

        #endregion
    }

 

As we say there is a bounch of IsXXXXX function, that are called for each portion of the class in order to know what to do with it. Our implementation is absolutely incomplete ( not implemented function omitted ), but it feet the simple requirement we stated. Then we can see how we actually realize the mapping:

static void Main(string[] args)
       {
           AutoMapper mapper = new AutoMapper();
          
           //this line simple rely on the fact
           //all and just the entities are exported...
           var map = mapper.CompileMappingFor(Assembly.GetExecutingAssembly().GetExportedTypes());

           //dump the mapping on the console
           XmlSerializer ser = new XmlSerializer(map.GetType());
           ser.Serialize(Console.Out, map);
       }

Simple, isn’t ?

The resulting map, as dumped on the console is:

image

That fulfill the actually simple requirements. So is just a matter of recognize the convention and the exceptions, and let’s go auto-mapping!

Thursday, September 01, 2011 2:58:04 PM (GMT Daylight Time, UTC+01:00)  #    Comments [2] - Trackback
NHibernate | ORM | NH Mapping By Code

# Friday, April 01, 2011

Just a little post about this argument, since as the author of (t4) Hbm2net and Db2hbm I think I can say my opinion from a critic point of view. As an user I can say that, as an OR/M NHibernate is a great platform, almost any circumstances are treated in deep, and even the infamous “legacy db from hell” are gently handled. So this some of my thought:

ONE -  Mapping is not always monkey coding.

This comes from the experience from Db2hbm. The tool can be great if you ( as me, as everybody at the start/half part of the learning curve ) have to start with a big database, and you don’t want to do all the job by hand. Db2hbm has a strategy called mapping exception, you can use it to customize the mapping, but the product is actually monkey code, and you need to abandon the automatic lifecycle in favor of custom mapping. This is bad in term of code generation, since the difference between a code generator that works almost 90% the time and one that works 100% of the time is a lot more than 10% Smile.

TWO – Writing class is monkey coding. 

This comes from hbm2net. I worked personally on the old version in the NHContrib and produced a version with a T4 template. I never modified the template and always had a working class for my mapping, even with exotic associations. This is really pleasant, since we already did a boring job in writing the hbm, creating the class from it would be just painful. So, in my experience, there is nothing more in the class that is not already said in the hbm.

THREE – Xml is not the devil

There is nothing painful in writing XML if we enable the intellisense for it. It is easier to grab all the mapping details by writing your own mapping, and at soon you have a a good practice, you can leverage some nh mapping sugar, to deal with really powerful mapping constructions. With the meta tag hbm can collect even more information to feed more code generators.

FOUR – Fluent NHibernate

Fluent NHibernate is the ( probably most famous ) way to avoid writing XML with NHibernate. Even of there is an AutoMapping feature, that does avoid wrtite any code, you eventually ends writing such things:

public class CatMap : ClassMap<Cat>
{
  public CatMap()
  {
    Id(x => x.Id);
    Map(x => x.Name)
      .Length(16)
      .Not.Nullable();
    Map(x => x.Sex);
    References(x => x.Mate);
    HasMany(x => x.Kittens);
  }
}
So if it is monking code writing the class for HBM, here you monkey twice by writing two class for say the same thing. 

FIVE – ConfOrm

CodeConform is the newest, and maybe the most innovative no xml mapping strategy. Fabio Maulo inspired himself probably in this post. The main idea in Conform in order to me is the pattern strategy that can help write complex mapping without really having to touch any xml file. The xml layer is avoided even internally, so loading a session factory is sensibly faster.  Since Fabio is the lead developer of NHibernate, some of these concept will probably appear on the next NH version.

Conclusion – What I would like to see

I like code first OR/M. But in real life this happened to me 0 times. So having legacy DB ( well, DB first approach, that is the same ) is quite common for me, and I think for a lot of people. Write a mapping in code is not an option, or does not make sense in order to replace hbm, we eventually fall in duplicate the concepts. ConfOrm by design avoid composite key, that are really common in legacy schemas. We need something to easily grab the pattern the DB designer would mean and easily port it to our NH solution.

Friday, April 01, 2011 10:25:13 PM (GMT Daylight Time, UTC+01:00)  #    Comments [2] - Trackback
NHibernate

# Wednesday, December 22, 2010

The hbm2net and db2hbm tool are now updated to the latest NH version. The fastest location for download are:

The solution on sourceforge now is updated to VS 2010.

Wednesday, December 22, 2010 10:39:01 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
db2hbm | hbm2net | NHibernate

# Saturday, October 16, 2010
Continua da parte 5

In questa parte ci fermiamo un attimo ad analizzare meglio come e quando attivare la sessione (ISession) e quali siano le modalità più corrette per farlo. Vediamo per grandi linee cosa fa Session:

  1. Consente di recuperare salvare ed eliminare le entità.
  2. Mantiene una mappa delle entità organizzata come reference/ID Database
  3. Gestisce una cache ( first level cache ) che, tramite la mappa di cui sopra, ci evita di andare sul database per recuperare un entità che abbiamo già in memoria
  4. Tiene traccia delle entità modificate
  5. Gestisce le interazioni con il driver ADO.NET sottostante, comprese l’apertura chiusura delle connessioni.
  6. Ci consente di aprire e chiudere le transazioni.

Il punto 2 merita un commento: tra le difformità concettuali tra database e oggetti .NET vi è l’identità dell’oggetto. Nel database la relazione di identità è in ultima analisi garantita dall’ID della riga, mentre in .NET l’identità di due oggetti è in ultima analisi riconducibile al confronto fra due reference. NHibernate vuole evitare che ci siano queste ambiguità, e asserisce che ci debba essere una sola reference che punti ad un determinato oggetto persistent: anche se creiamo un oggetto clone di un oggetto persistent e cerchiamo, per esempio, di cancellarlo o di modificarlo, questo clone non sarà accettato da NHIbernate.

In pratica, quando ci serve di interagire con gli oggetti persistent, abbiamo necessita di lavorare nell’ambito di una sessione. Questo introduce spesso degli errori quando cerchiamo il momento opportuno per aprire la sessione:

Open Session: Pratiche da evitare
  1. Evitare di aprire la session per ogni singola interazione che si vuole avere con lo strato di persistenza. ( Session-per-operation Antipattern )
  2. Evitare di utilizzare una sola session per l’intera durata dell’ applicazione. ( Session-per-application Antipattern )

Già lo abbiamo detto, ma è meglio ricordarlo visto che siamo in zona di cose da evitare: se la session ha comunque una certa agilità di uso, la SessionFacory deve essere aperta una volta sola all’interno di un app domain. Non che ci sia qualcosa che non funziona, ma semplicemente la session factory è lenta a caricarsi.

Se si usa la prima strategia, ci si scontra violentemente con il fatto che NH non vuole “mixare” entità appartenenti a più sessioni,  si perde qualsiasi beneficio della cache di primo livello, e gli oggetti lazy tendono a produrre il famigerato errore “NHibernate.LazyInitializationException: illegal access to loading collection”, dovuto al fatto che la sessione che ha creato l’ogegtto ormai l’abbiamo chiusa, e NH non può andare a fare il recupero degli oggetti della collection.

La seconda strategia se possibile è anche peggiore: lavoriamo con dati in “stallo”, cioè possibilmente non aggiornati con i rispettivi oggetti persistiti, la session si appesantisce ( NH diventa sempre più lento se gli oggetti trattati da una session sono troppi ), non si assicura più una corretta politica di gestione della connessione al DB,che deve essere aperta  per il tempo strettamente necessario e quindi chiusa.

La soluzione consigliata è la Session per Business Transaction. Una business transaction è, tanto per fare un esempio: “leggo  la prima pagina dei clienti”, “Inserisco un nuovo ordine”, “cerco tutti i prodotti in magazzino che iniziano per XXX”, ecc. Quindi operazioni autocontenute, ma non triviali. Nelle applicazioni ci possono essere delle leggere differenze tra rich client e Web application, in queste ultime la realizzazione di questo pattern può convergere nella Session Per Request, che consiste nell’aprire una session quando si verifica l’evento Begin request, e di chiuderla al succesivo End. Questo approccio in generale è pressoche equivalente al Session Per business transaction, perchè di fatto in un applicativo web un ciclo request/response coincide con una business transaction.

Possiamo utilizzare per questo scopo il pattern Unit of Work. Nei sorgenti di esempio ne ho messo una versione semplice, che utilizza le ContextSession di NH. In pratica le context session ci consentono di condividere una sessione tra più funzioni del nostro applicativo, in relazione ad un contesto che può essere, per esempio il Thread corrente, oppure l’HttpContext della chiamata al web server, o qualunque altra cosa, configurabilmente. Vediamo i sorgenti che così è più chiaro:

   1: public class NHUow:IDisposable
   2: {
   3:     private NHUow()
   4:     {
   5:         if (CurrentSessionContext.HasBind(sessionFactory))
   6:             throw new InvalidOperationException("Nested unit of work not allowed.");
   7:         CurrentSessionContext.Bind(sessionFactory.OpenSession());
   8:     }
   9:     
  10:     static object lockObject = new object();
  11:     public static NHUow Open()
  12:     {
  13:         lock (lockObject)
  14:         {
  15:             if (sessionFactory == null)
  16:                 CreateSessionFactory();
  17:         }
  18:         return new NHUow();
  19:     }
  20:     public static ISession CurrentSession
  21:     {
  22:         get { return sessionFactory.GetCurrentSession(); }
  23:     }
  24:  
  25:     private static ISessionFactory sessionFactory;
  26:     private static void CreateSessionFactory()
  27:     {
  28:         Configuration cfg = CreateConfiguration();
  29:         sessionFactory = cfg.BuildSessionFactory();
  30:     }
  31:  
  32:     private static Configuration CreateConfiguration()
  33:     {
  34:         Configuration cfg = new Configuration();
  35:         cfg.Configure();
  36:         // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
  37:         // contiene customer
  38:         cfg.AddAssembly(typeof(Customer).Assembly);
  39:         return cfg;
  40:     }
  41:  
  42:  
  43:     #region IDisposable Members
  44:  
  45:     public void Dispose()
  46:     {
  47:         if (!CurrentSessionContext.HasBind(sessionFactory) )
  48:         {
  49:             throw new InvalidOperationException("Invalid current session");
  50:         }
  51:         var session = sessionFactory.GetCurrentSession();
  52:         CurrentSessionContext.Unbind(sessionFactory);
  53:         if (!session.IsOpen)
  54:         {
  55:             throw new InvalidOperationException("Session closed before UOW end");
  56:         }
  57:        
  58:         if ( null != session.Transaction && session.Transaction.IsActive )
  59:         {
  60:             if (session.Transaction.WasCommitted == false && session.Transaction.WasRolledBack == false)
  61:                 session.Transaction.Rollback();
  62:         }
  63:         session.Close();
  64:     }
  65:  
  66:     #endregion
  67: }

L’oggetto implementa IDisposable, e ci permette di utilizzaro in questo modo:

   1: using (var uow = NHUow.Open())
   2: {
   3:     var transaction = NHUow.CurrentSession.BeginTransaction();
   4:     Customer c = new Customer();
   5:     c.Name = "FELIX";
   6:     c.AddressLine1 = c.AddressLine2 = "";
   7:     c.City = "XX";
   8:     c.ZipCode = "12060";
   9:     NHUow.CurrentSession.Save(c);
  10:     transaction.Commit();
  11: }

In pratica apro una unità di lavoro con using new… e, usciti dallo scope la uow si chiude. All’interno di questo scope, tutto il nostro codice potrà accedere alla session corrente usango la funzione statica NHUow.CurrentSession. I più attenti noteranno che la configurazione del session factory è fissa: questo è per semplificare la clase UOW, per renderla generale basta intervenire aggiungendo un SessionFactoryFactory, che UOW utilizzera per recuperare il session factory esterno.

Bene, se a questo punto lanciassimo lo unit test di prova, otterremmo la seguente eccezione:

No CurrentSessionContext configured (set the property current_session_context_class)!

Questo perchè il meccanismo di mantenimento delle sessioni di contesto di NH è configurabile, e noi non abbiamo specificato nulla. Come dice il messaggio di errore, bisogna aggiungere nel file di config la linea current_session_context_class ( linea 11 ):

   1: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   2:   <session-factory name="NHibernate.Test">
   3:     <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
   4:     <property name="connection.connection_string">Server=.\SQLEXPRESS;initial catalog=NHFROMSCRATCH;Integrated Security=SSPI</property>
   5:     <property name="adonet.batch_size">10</property>
   6:     <property name="show_sql">true</property>
   7:     <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
   8:     <property name="use_outer_join">true</property>
   9:     <property name="command_timeout">60</property>
  10:     <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
  11:     <property name="current_session_context_class">thread_static</property>
  12:     <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
  13:   </session-factory>
  14: </hibernate-configuration>

Notate che non ho inserito un nome di classe, questo perchè ci sono degli shortcut per i context manager più usati. Tanto per elecare i vari possibili context manager:

  • NHibernate.Context.WebSessionContext – la sessione corrente è contenuta in HttpContext. L’alias è web.
  • NHibernate.Context.CallSessionContext - la sessione corrente è contenuta CallContext. L’alias è call.
  • NHibernate.Context.ThreadStaticSessionContext – la sessione corrente è nel context del thread. L’alias è thread_static.

Ovviamente, sempre nella filosofia della massima estendibilità, possiamo scrivere il nostro context manager.

Nei sorgenti di esempio, oltre alla classe Uow, c’è una classe di Unit Test NHFromScratch.Tests.Uow, che ne mostra l’utilizzo.

 

Torna alla parte 5
Download Sorgenti
Saturday, October 16, 2010 5:48:51 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# Monday, September 27, 2010
Continua da parte 4

In questa parte vedremo un altro tipo di collezione, e di come in generale possiamo collezionare oggetti che non sono entità, cioè oggetti che non gestiscono per conto proprio una identità, e che ha senso che esistano solo se collegati all’entità contenitrice. Per fare questo aggiungiamo al nostro modello l’entità Prodotto, che ci servirà comunque per altre cose, e supponiamo di avere un semplicissimo magazzino, organizzato a celle, contenenti la giacenza del prodotto in questione. Diciamo anche che per identificare la cella ci basta avere una stringa. Nella realtà un magazzino sarà molto più complesso, ma questa semplificazione è un pretesto per mostrare la strategia di collezione MAP, ed in una realtà molto piccola potrebbe anche avere un senso così come fatto.

Aggiungiamo quindi il mapping della entità Prodotto, con annessa la mappa delle locazioni di magazzino e relative quantità:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   3:                                                       assembly="NHFromScratch.Entities">
   4:   <class name="Product" table="Products">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="Code" type="AnsiString" />
   9:     <property name="Description" type="AnsiString"/>
  10:     <map name="Warehouse">
  11:       <key column="Product"/>
  12:       <index column="Cell" type="AnsiString"/>
  13:       <element column="Quantity" type="Int32"></element>  
  14:     </map>
  15:     <!-- details comes here -->
  16:   </class>
  17:   
  18: </hibernate-mapping>
  19:  

Vediamo come è definita <map/>, o meglio, un modo di definire una mappa, in questo caso una mappa indicizzata da una stringa che identifica la cella di magazzino, ed un numerico che indica la quantità del prodotto presente in tale cella. Il tag key è il riferimento, nella tabella che contiene la map, alla entità madre ( in questo caso Product ), poi occorre un indice, in questo caso è la “Cell” del magazzino, e il valore, in questo caso la Quantity del prodotto. Lanciando lo unit test di creazione del database otteniamo, per la parte che ci interessa, il seguente schema:

image

Se andiamo ad indagare meglio la DDL notiamo, nella parte di creazione della tabella warehouse, il seguente script:

clip_image001

La chiave primaria della tabella è quindi costituita dalla FK alla tabella parent ( Product ) combinata con la componente “Index” – in questo caso Cell –. No potremo avere, per un prodotto, due informazioni relative alla medesima cella, o locazione di magazzino.

Supponiamo che per localizzare la posizione in magazzino siano necessarie invece due informazioni, per esempio la riga e la colonna, e che oltre che la quantità ci interessi anche sapere la data a cui questa quantità è stata stoccata, possiamo modificare il mapping come di seguito:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   3:                                                       assembly="NHFromScratch.Entities">
   4:   <class name="Product" table="Products">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="Code" type="AnsiString" />
   9:     <property name="Description" type="AnsiString"/>
  10:     <map name="Warehouse">
  11:       <key column="Product"/>
  12:       <composite-index class ="WarehouseLocation">
  13:         <key-property name="CellRow" type="AnsiString" />
  14:         <key-property name="CellCol" type="AnsiString" />
  15:       </composite-index>
  16:       <composite-element class="StockInformation">
  17:         <property name="StockDate" type="DateTime"/>
  18:         <property name="Quantity" type="Int32"/>
  19:       </composite-element>
  20:     </map>
  21:     <!-- details comes here -->
  22:   </class>
  23:   
  24: </hibernate-mapping>

In pratica abbiamo sostituito “<index>” con “<composite-index>” e “<element>” con “<composite-element>”. Ovviamente possiamo avere anche scenari in cui ad essere composito è solo l’indice, o solo l’element. Appena lanciata la compilazione il nostro hbm2net genera qualche file in più:

clip_image001[5]

In pratica abbiamo una classe per rappresentare la “chiave” ( WarehouseLocation ) nella mappa e una per rappresentare l’”element” ( StockInformation ). La classe utilizzata per la chiave deve poter agire come tale, e pertanto deve effettuare opportuni ovveride dei metodi EqualsGetHashCode, pena comportamenti impredicibile del codice. Utilizzando hbm2net questi gener ail codice opportuno:

   1: public override bool Equals(object other)
   2: {
   3:     if (this == other)
   4:         return true;
   5:     WarehouseLocation rhs = other as WarehouseLocation;
   6:     if (rhs == null) 
   7:         return false; // null or not a WarehouseLocation
   8:     return _cellRow.Equals(rhs._cellRow) && _cellCol.Equals(rhs._cellCol);
   9: }
  10:  
  11: public override int GetHashCode()
  12: {
  13:     return _cellRow.GetHashCode() ^ _cellCol.GetHashCode();
  14: }

Aggiungendo queste due classi al progetto possiamo rigenerare il database, che risulta così modificato:

clip_image001[7]

Il diagramma classi diventa, per la parte product, il seguente:

clip_image001[9]

E’ interessante notare che dal punto di vista OR/M StockInformation e WarehouseInformation *non* sono entità. In questa parte del corso si è quindi totalmente smentità la presunta corrispondenza 1:1 tra entità e tabelle.

 

Ora scriviamo il solito unit test per vedere come si comporta NH durante il CRUD su questa collection. Non torneremo a ripetere il discorso “lazy” in quanto analogo a quello visto nella parte 4 per <bag/>.

Il codice dello unit test che lanceremo è il seguente:

   1: [Test]
   2: public void TestCrud()
   3: {
   4:    Console.WriteLine("Creating a product");
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    using (ITransaction trns = session.BeginTransaction())
   7:    {
   8:        Product p = new Product();
   9:        p.Code = "P00000";
  10:        p.Description = " a sample product";
  11:        Console.WriteLine("Adding some Warehouse information");
  12:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  13:        p.Warehouse.Add(new KeyValuePair<WarehouseLocation, StockInformation>(l1, new StockInformation()));
  14:        p.Warehouse[l1].Quantity = 12;
  15:        p.Warehouse[l1].StockDate = DateTime.Now;
  16:        var l2 = new WarehouseLocation() { CellCol = "A", CellRow = "2" };
  17:        p.Warehouse.Add(new KeyValuePair<WarehouseLocation, StockInformation>(l2, new StockInformation()));
  18:        p.Warehouse[l2].Quantity = 42;
  19:        p.Warehouse[l2].StockDate = DateTime.Now.AddDays(-7);
  20:        Console.WriteLine("Persisting the Product");
  21:        session.SaveOrUpdate(p);
  22:        trns.Commit();
  23:    }
  24:    Console.WriteLine("Modifying warehouse information of a product");
  25:    using (ISession session = sessionFactory.OpenSession())
  26:    using (ITransaction trns = session.BeginTransaction())
  27:    {
  28:        Console.WriteLine("Retrieving product by code");
  29:        var p = session.CreateCriteria<Product>()
  30:            .Add(Expression.Eq("Code", "P00000"))
  31:            .UniqueResult<Product>();
  32:        
  33:        Console.WriteLine("Modify stored quantity on cell A1");
  34:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  35:        
  36:        p.Warehouse[l1].Quantity -=3;
  37:        p.Warehouse[l1].StockDate = DateTime.Now;
  38:        
  39:        Console.WriteLine("Persisting the Product");
  40:        session.SaveOrUpdate(p);
  41:        trns.Commit();
  42:    }
  43:    Console.WriteLine("deleting warehouse information of a product");
  44:    using (ISession session = sessionFactory.OpenSession())
  45:    using (ITransaction trns = session.BeginTransaction())
  46:    {
  47:        Console.WriteLine("Retrieving product by code");
  48:        var p = session.CreateCriteria<Product>()
  49:            .Add(Expression.Eq("Code", "P00000"))
  50:            .UniqueResult<Product>();
  51:  
  52:        Console.WriteLine("Modify stored quantity on cell A1");
  53:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  54:  
  55:        p.Warehouse.Remove(l1);
  56:  
  57:        Console.WriteLine("Persisting the Product");
  58:        session.SaveOrUpdate(p);
  59:        trns.Commit();
  60:    }
  61: }

Che produce il seguente risultato. Si sono evidenziati i punti in cui si vede come NH accede agli elementi della collezione:

image

Come si vede, map è molto efficiente ad accedere in update/delete a singoli elementi della collezione. Per contro, visto che la struttura è rappresentata in memoria tramite un dictionary, non è possibile ordinarla ( anche se si facesse la query con un order-by, il funzionamento interno del dictionary potrebbe mutare questo ordine ).

Download Sorgenti
Parte 4
Parte 6
Monday, September 27, 2010 5:09:49 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# Saturday, September 18, 2010

continua da Parte3: <many-to-one/>

Sempre continuando a lavorare sul sistema ordini minimale che stiamo analizzando, ci potrebbe tornare utile sapere quali ordini sono associati ad un particolare Customer. Lasciando così come è il modello, possiamo già farlo, sia tramite HQL che tramite ICriteria. Ricordiamo che questo è il corso NHibernate da *ZERO* per cui non si usa LINQ to NH, ne qualsiasi altra API esterna ad NHIbernate, non perchè queste siano o meno efficaci, solo perchè si vuole analizzare il comportamento base. Molto semplicemente facciamo i due nuovi unit test, il primo con HQL:

   1: [Test]
   2: public void QueryCustomerOrdersWithHQL()
   3: {
   4:    CreateSomeOrdersAndCustomers();
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    {
   7:        var query = session.CreateQuery("from Order o join fetch o.Customer where o.Customer.Name=:customer")
   8:            .SetParameter("customer","Customer A");
   9:        foreach (NHFromScratch.Entities.Order order in query.List<NHFromScratch.Entities.Order>())
  10:        {
  11:            Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  12:        }
  13:    }
  14: }

Che lanciato produce il seguente output a console:

 

   1: NHibernate: select order0_.Id as Id3_0_, customer1_.Id as Id2_1_, order0_.OrderCode as OrderCode3_0_, order0_.OrderDate as OrderDate3_0_, order0_.DueDate as DueDate3_0_, order0_.CustomerId as CustomerId3_0_, customer1_.Name as Name2_1_, customer1_.AddressLine1 as AddressL3_2_1_, customer1_.AddressLine2 as AddressL4_2_1_, customer1_.City as City2_1_, customer1_.ZipCode as ZipCode2_1_, customer1_.Active as Active2_1_ from Orders order0_ inner join Customers customer1_ on order0_.CustomerId=customer1_.Id, Customers customer2_ where order0_.CustomerId=customer2_.Id and customer2_.Name=@p0;@p0 = 'Customer A'
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

Notiamo che nell’ HQL abbiamo comunque specificato il join fetch sull’associazione o.Customer: questo sempre per evitare l’antipattern select N+1.

Lo stesso risultato lo possiamo ottenere con la API ICriteria, ecco lo unit test:

   1: [Test]
   2: public void QueryCustomerOrdersWithCriteria()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     {
   7:         var criteria = session.CreateCriteria<NHFromScratch.Entities.Order>()
   8:             .CreateCriteria("Customer")
   9:             .Add(Expression.Eq("Name","Customer A")
  10:             );
  11:  
  12:         foreach (var order in criteria.List<NHFromScratch.Entities.Order>())
  13:         {
  14:             Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  15:         }
  16:     }
  17: }

Questo produce l’output di trace seguente:

   1: NHibernate: SELECT this_.Id as Id9_1_, this_.OrderCode as OrderCode9_1_, this_.OrderDate as OrderDate9_1_, this_.DueDate as DueDate9_1_, this_.CustomerId as CustomerId9_1_, customer1_.Id as Id8_0_, customer1_.Name as Name8_0_, customer1_.AddressLine1 as AddressL3_8_0_, customer1_.AddressLine2 as AddressL4_8_0_, customer1_.City as City8_0_, customer1_.ZipCode as ZipCode8_0_, customer1_.Active as Active8_0_ FROM Orders this_ inner join Customers customer1_ on this_.CustomerId=customer1_.Id WHERE customer1_.Name = @p0;@p0 = 'Customer A'
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

Quindi il problema potrebbe dirsi già risolto… sfortunatamente in un modo non troppo object oriented. Meglio sarebbe avere una collezione di ordini facilmente accessibile dall’oggetto customer, qualcosa tipo customer.Orders. Per fare questo ci viene incontro il più semplice tag di associazione per esprimere una collezione di entità collegate:

<bag/>

Questo rende dal punto di vista object oriented ciò che nel database esiste già in forma implicita. Se nell’algebra relazionale una FK esprime di per sè una relazione bidirezionale, nell’ambito oggetti questa relazione va esplicitata. Per cui diventa necessario modificare il mapping, con la prima semplice aggiunta nella classe customer di un <bag/> di Ordini:

   1: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   2:                                                       assembly="NHFromScratch.Entities">
   3:   <class name="Customer" table="Customers">
   4:     <id name="Id" type="Int32">
   5:       <generator class="native"/>
   6:     </id>
   7:     <property name="Name" type="String" not-null="true"/>
   8:     <property name="AddressLine1" type="String" not-null="true"/>
   9:     <property name="AddressLine2" type="String" />
  10:     <property name="City" type="String" not-null="true"/>
  11:     <property name="ZipCode" type="String" not-null="true"/>
  12:     <property name="Active" type="Boolean" not-null="true"/>
  13:     <bag name="Orders">
  14:       <key column="CustomerId"/>
  15:       <one-to-many class="Order"/>
  16:     </bag> 
  17:   </class>
  18:   
  19: </hibernate-mapping>

Facendo un rebuild della solution, otteniamo un diagramma di classi per le nostre entità come il seguente:

image

Che di fatto ci mostra la relazione bidirezionale tra il cliente e i suoi ordini. Vediamo con un altro unit test come possiamo lavorare con un customer ed ottenere gli ordini a lui collegati:

   1: [Test]
   2: public void QueryCustomerOrdersWithBag()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     {
   7:         var customerA = session.CreateCriteria<Customer>()
   8:             .Add(Expression.Eq("Name", "Customer A")
   9:             ).UniqueResult<Customer>();
  10:         Console.WriteLine("Obtained customer instance");
  11:         foreach (var order in customerA.Orders)
  12:         {
  13:             Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  14:         }
  15:     }
  16: }

Questo produce il seguente output a console:

   1: Obtained customer instance
   2: NHibernate: SELECT orders0_.CustomerId as CustomerId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.OrderCode as OrderCode3_0_, orders0_.OrderDate as OrderDate3_0_, orders0_.DueDate as DueDate3_0_, orders0_.CustomerId as CustomerId3_0_ FROM Orders orders0_ WHERE orders0_.CustomerId=@p0;@p0 = 1
   3: Order:ORD00000 Customer:Customer A
   4: Order:ORD00001 Customer:Customer A

Come si vede, la collezione è stata recuperata dallo storage solo quando necessaria. Anche sulla collection abbiamo un comportamento “lazy”, che è opportuno in molti casi, ma non sempre. Possiamo anche qui modificare il comportamento del lazy specificandolo nel mapping:

 
   1: <bag name="Orders" fetch="join">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 
Facendo rigirare il test di prima otteniamo un output leggermente diverso:
   1: Obtained customer instance
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

Nessuna query: In effetti durante il caricamento del customer è stata risolta anche l’associazione alla collection di ordini, la query è stata difatti la seguente:

   1: SELECT this_.Id ... orders2_.CustomerId as CustomerId3_... FROM Customers this_ left outer join Orders orders2_ on this_.Id=orders2_.CustomerId WHERE this_.Name = @p0;@p0 = 'Customer A'

In tutti i casi il bag che ci arriva è una lista potenzialmente disordinata. Possiamo chiedere ad NH di fare, o meglio di chiedere al DB di fare, un ordinamento per una particolare proprietà dell’entity collezionata. Nel nostro esempio potremmo volere un ordinamento per data di ordine, aggiungendo al mapping questo attributo:

   1: <bag name="Orders" fetch="join" order-by="OrderDate" >

ed ottenendo quindi la interrogazione qui di seguito:

1: SELECTFROM Customers this_ left outer join Orders orders2_ on this_.Id=orders2_.CustomerId WHERE this_.Name = @p0

ORDER BY orders2_.OrderDate;

@p0 = 'Customer A'

Con l’opportuna clausola id ordinamento. Vediamo ora cosa succede facendo dei test con HQL. Il primo:

   1: [Test]
   2: public void QueryCustomerOrdersWithBagUsingHQL()
   3: {
   4:    CreateSomeOrdersAndCustomers();
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    {
   7:        IQuery query = session.CreateQuery("from Customer c where c.Name=:name")
   8:            .SetParameter("name", "Customer A");
   9:        
  10:        var list = query.List<Customer>();
  11:        Assert.AreEqual(1, list.Count);
  12:        var customerA = list[0];
  13:        foreach (var order in customerA.Orders)
  14:        {
  15:            Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  16:        }
  17:    }
  18: }
 
Vediamo che il numero di query ritorna ad un comportamento lazy:
 
   1: select customer0_.Id as Id2_, customer0_.Name as Name2_, customer0_.AddressLine1 as AddressL3_2_, customer0_.AddressLine2 as AddressL4_2_, customer0_.City as City2_, customer0_.ZipCode as ZipCode2_, customer0_.Active as Active2_ from Customers customer0_ where customer0_.Name=@p0;@p0 = 'Customer A'
   2: SELECT orders0_.CustomerId as CustomerId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.OrderCode as OrderCode3_0_, orders0_.OrderDate as OrderDate3_0_, orders0_.DueDate as DueDate3_0_, orders0_.CustomerId as CustomerId3_0_ FROM Orders orders0_ WHERE orders0_.CustomerId=@p0 ORDER BY orders0_.OrderDate;@p0 = 1
   3: Order:ORD00000 Customer:Customer A
   4: Order:ORD00001 Customer:Customer A

Tuttavia anche qui possiamo specificare in HQL la politica di fetch, modificando l’HQL in: “from Customer c join fetch c.Orders where c.Name=:name”. Sfortunatamente il test fallisce miseramente:

image

Questo è un comportamento “strano” di NH. In effetti c’è un solo Customer nei nostri test che soddisfa la relazione richiesta, tuttavia effettuando la join i record restituiti sono più di uno ( in effetti n, dove n è il numero degli ordini ). Va detto che NH non crea istanze diverse degli oggetti, ma di fatto mette nella lista di output delle reference alla stessa entità.

Si può ovviare a questo problema con un “ResultTransformer”, ovvero un post-processor dei risultati di NH. Possiamo costruire dei post processor ( IResultTransformer ) per manipolare in modo che riteniamo opportuno i risultati di un ICriteria o di un HQL. NH fornisce dei transformer di utilità, uno dei quali ci torna utile in questo frangente:

   1: IQuery query = session.CreateQuery("from Customer c join fetch c.Orders where c.Name=:name")
   2:     .SetParameter("name", "Customer A")
   3:     .SetResultTransformer(Transformers.DistinctRootEntity)
   4:     ;

Questa trasformazione fa collassare le entità “identiche” in una sola, ed ecco che le righe ritornate sono quelle aspettate, e la query diventa una sola, avendo specificato il comportamento non lazy.

Cosa succede se proviamo a rimuovere degli ordini dal cliente? ( attenzione, nella realtà questo non accadrà quasi mai: ci saranno probabilmente delle strategie di soft-delete, qui stiamo facendo degli esempi ) Ecco un unit test per provare questo scenario:

   1: [Test]
   2: public void RemoveOrderForCustomers()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     using(ITransaction trns = session.BeginTransaction() )
   7:     {
   8:         var customerA = session.CreateCriteria<Customer>()
   9:            .Add(Expression.Eq("Name", "Customer A")
  10:            ).UniqueResult<Customer>();
  11:         Console.WriteLine("Obtained customer instance");
  12:         customerA.Orders.RemoveAt(0);
  13:         trns.Commit();
  14:     }
  15: }

 

Se lanciamo questo test, otteniamo il seguente, famigerato errore:

   1: NHFromScratch.Tests.TestOrders.RemoveOrderForCustomers:
   2: NHibernate.Exceptions.GenericADOException : could not delete collection rows: [NHFromScratch.Entities.Customer.Orders#1][SQL: UPDATE Orders SET CustomerId = null WHERE CustomerId = @p0 AND Id = @p1]
   3:   ---->; System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'CustomerId', table 'NHFROMSCRATCH.dbo.Orders'; column does not allow nulls. UPDATE fails.
   4: The statement has been terminated.

In pratica NH cerca di “sganciare” l’ordine liberandolo dal Customer, ma noi abbiamo specificato che Customer non è nullabile, quindi il ragionamento è corretto. L’unico modo è dire che l’owner di questa associazione è Customer, inserendo inverse=”true” intendendo in questo modo che questo “lato” dell’associazione non è il responsabile della collezione, in questo modo:

   1: <bag name="Orders" fetch="join" order-by="OrderDate" inverse="true">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

Sfortunatamente il test continua a non funzionare, stavolta lamenta che nulla è stato cancellato:

image

Quello che possiamo fare è forzare la cancellazione con il paramtero “cascade”:

   1: <bag name="Orders" fetch="join" order-by="OrderDate" inverse="true" cascade="all-delete-orphan">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

ed ecco finalmente il test funzionante:

image

Il cascade ha effetti simili anche per le operazioni di inserimento di nuovi ordini: essi verranno salvati automaticamente al salvataggio dell’entità Customer.

Prima di concludere questa parte vale la pena ancora dare un occhiata ad un comportamento di cui è utile essere a conoscenza per problematiche di ottimizzazione. Supponiamo di voler sapere quanti ordini ha un Customer, e di avere la associazione in modalità lazy:

   1: [Test]
   2:    public void OrdersCount()
   3:    {
   4:        CreateSomeOrdersAndCustomers();
   5:        using (ISession session = sessionFactory.OpenSession())
   6:        {
   7:            var customerA = session.CreateCriteria<Customer>()
   8:               .Add(Expression.Eq("Name", "Customer A")
   9:               ).UniqueResult<Customer>();
  10:            Console.WriteLine("Obtained customer instance");
  11:            Assert.AreEqual(2, customerA.Orders.Count);
  12:        }
  13:       
  14: }
Se andiamo a vedere le query, notiamo che per contare gli elementi della collezione Orders, NH la riempie. Questo potrebbe andare a discapito delle performance se ci interessa il comportamento lazy della collection, ecco quindi che ci viene incontro l’attributo lazy=”extra”:
Ecco il nuovo mapping:
   1: <bag name="Orders" lazy="extra" order-by="OrderDate" inverse="true" cascade="all-delete-orphan">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

ed ecco la pulitissima query che viene eseguita:

NHibernate: SELECT count(Id) FROM Orders WHERE CustomerId=@p0;@p0 = 1

Bene, anche questa parte è finita, non tutte le possibili combinazioni di attributi per <bag/> sono state trattate, ma è stato posto l’accento su alcuni problemi classici, il minimo per partire e sperimentare per proprio conto. Ultime due cose:

  • Fino ad ora la equivalenza classe-tabella si è sempre manifestata, ma non sarà sempre così.
  • Guardiamo alle query che genera NH inizialmente per capire cosa succede, ma sempre di più dobbiamo imparare a dare per scontato il comportamento e guardare le query per motivi di ottimizzazione.
Download Sorgenti
Parte 3
Parte 5
Saturday, September 18, 2010 10:24:26 PM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
NHibernate | NHibernate Tutorial

# Saturday, September 11, 2010

continua da Parte2: Creare un progetto

Quando una entità del nostro modello ha una proprietà del tipo di una altra entità del dominio, cioè ha un riferimento ad una altra entità, possiamo esplicitare questo concetto nel file di mapping con il tag <many-to-one/>. Continuando ad arricchire il modello iniziato nella Parte2, decidiamo di inserire l’entità ordine, e poichè un ordine ha necessariamente un cliente, potremo vedere come esplicitare questo concetto. Nella vita l’entità ordine sarà più complessa, ma per il momento ci accontentiamo di vedere la relazione tra l’ordine e chi lo ha fatto.

Aggiungiamo un file di mapping così fatto:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   3:                                                       assembly="NHFromScratch.Entities">
   4:   <class name="Order" table="Orders">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="OrderCode" type="AnsiString" />
   9:     <property name="OrderDate" type="DateTime"/>
  10:     <property name="DueDate" type="DateTime"/>
  11:     <many-to-one name="Customer" class="Customer" column="CustomerId"/>
  12:     <!-- details comes here -->
  13:   </class>
  14:   
  15: </hibernate-mapping>

Come si vede abbiamo aggiunto un paio di proprietà di tipo data, ma importante novità c’è il tag <many-to-one/> che ci dice in questa minimale definizione:

  • Che nome ha la proprietà con l’attributo “name”
  • Qual’è l’entità esterna,cioè il suo tipo ( l atabella non ci interessa più )
  • Opzionalmente il nome della colonna ( in pratica il nome della colonna che sarà una foreign-key nella tabell aorder VS la tabella customer ) con l’attributo “column”.

Se non esplicitassi column, il nome sarebbe lo stesso della proprietà. Per inciso: column applica anche alle proprietà semplici, con la medesima semantica.

Bene, ora ricordiamoci di mettere “embedded resource” al nuovo file hbm, che chiameremo fantasiosamente Order.hbm.xml, lanciamo la compilazione per ottenere una prima versione del file .cs order.generated.cs, che aggiungeremo al progetto e compileremo.

s1

Fatto questo possiamo lanciare gli unit test, e ricreare il database:

s2

Il test “vira al verde” e vediamo come la tabella order compaia nello script DDL, e come la foreign key che punta al Customer sia inserita opportunamente nella tabella order. I più attenti poi abvranno notato come la proprietà code sia stata definita come VARCHAR, mentre le altre colonne stringa nella tabella customer siano invece NVARCHAR, questo è provocato dalla scelta di utilizzare come tipo per la stringa OrderCode AnsiString anzichè string. Questo non ha nessun effetto nel codice C#, ma cambia la DDL del database, quindi scegliete la più opportuna. Altra cosa importante, la FK è nullabile, questo non è molto bello poichè abbiamo detto che obbligatoriamente un Order deve avere un Customer associato: basta usare l’attributo not-null = “true” aggiunto al tag <many-to-one/>.

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" not-null="true"/>

Questo modificherà la DDL come qui di seguito:

   1: create table Orders (
   2:        Id INT IDENTITY NOT NULL,
   3:       OrderCode VARCHAR(255) null,
   4:       OrderDate DATETIME null,
   5:       DueDate DATETIME null,
   6:       CustomerId INT not null,
   7:       primary key (Id)
   8:    )

 

Alla fine il nostro nuovo DB avrà questo schema:

Senza nome

A questo punto possiamo di creare qualche test, per provare il modello come funziona. Diciamo che vogliamo creare dei test ripetibili, quindi la nostra TestFixture ricreerà tutte le volte un DB da zero, così ogni test sarà indipendente. Il primo test interessante potrebbe essere la creazione di un ordine, con contestualmente un nuovo cliente. Questa volta vedremo meglio il dettaglio delle varie operazioni che andremo a fare. Ecco il codice del test che proveremo a fare:

 

 

   1: public void CreateAnOrderWithANewCustomer()
   2: {
   3:     using(ISession session = sessionFactory.OpenSession() )
   4:     using (ITransaction transaction = session.BeginTransaction())
   5:     {
   6:         Order order = new Order();
   7:         order.Customer = new Customer()
   8:         {
   9:             Active = true
  10:             ,
  11:             AddressLine1 = "xxxxxxx xxx xxxx"
  12:             ,
  13:             AddressLine2 = "xx xxxxxxx xxxxx"
  14:             ,
  15:             City = "Milano"
  16:             ,
  17:             Name = "Customer Name"
  18:             ,
  19:             ZipCode = "00000"
  20:         };
  21:         order.OrderCode="ORD00000";
  22:         order.OrderDate = DateTime.Now;
  23:         order.DueDate = order.OrderDate.Value.AddMonths(2);
  24:         session.SaveOrUpdate(order);
  25:         transaction.Commit();
  26:     }
  27: }

Come prima cosa creiamo una “Sessione”, qualcosa che assomiglia ad una connessione al DB, ma è qualcosa di più, è un unità di lavoro. All’interno di un unityà di lavoro le entità diventano degne di questo nome ed NHibernate ne traccia le modifiche e si occupa di garantirne l’unicità a parità di identificativo DB, e tante altre cose. Diciamo qualche buona norma sulla session:

  • Costa poco creare una session ( diversamente dal Session Factory )
  • Cercate di far durare poco le sessioni da un punto di vista temporale
  • Limitate il numero di entità con cui lavora una sessione
  • La session non è multithread.

Immediatamente dopo abbiamo aperto una transazione, perchè andremo alla fine a persistere qualcosa di nuovo nel database, e vogliamo fare tutte le operazioni nella nostra unità di lavoro ( UOW = unit of work ) in modo consistente ed atomico. In una sola UOW è possibile avere più transazioni.

Lanciamo il test e vediamno cosa succede nel text output di NUnit, per vedere cosa fa NH sul DB. Sfortunatamente il test “vira al rosso”:

s3

 

Questo è il famigerato errore:

“NHFromScratch.Tests.TestOrders.CreateAnOrderWithANewCustomer:
NHibernate.PropertyValueException : not-null property references a null or transient valueNHFromScratch.Entities.Order.Customer”

Significa in pratica che stiamo cercando di salvare un entità ( Order ) che ha una referenza ad un’altra entità, ma quest’ultima non è persistente ( cioè transitoria=Transient ). Per risolvere questo problema abbiamo due strade:

Salvare l’entità riferita ( se quest’ultima è ancora transitoria ) prima di salvare l’entità ordine:

   1: session.SaveOrUpdate(order.Customer);
   2: session.SaveOrUpdate(order);
   3: transaction.Commit();

Questo fa diventare il test “verde”, e queste sono le query che sono inviate al DB:

   1: ***** NHFromScratch.Tests.TestOrders.CreateAnOrderWithANewCustomer
   2: NHibernate: INSERT INTO Customers (Name, AddressLine1, AddressLine2, City, ZipCode, Active) VALUES (@p0, @p1, @p2, @p3, @p4, @p5); select SCOPE_IDENTITY();@p0 = 'Customer Name', @p1 = 'xxxxxxx xxx xxxx', @p2 = 'xx xxxxxxx xxxxx', @p3 = 'Milano', @p4 = '00000', @p5 = True
   3: NHibernate: INSERT INTO Orders (OrderCode, OrderDate, DueDate, CustomerId) VALUES (@p0, @p1, @p2, @p3); select SCOPE_IDENTITY();@p0 = 'ORD00000', @p1 = 11/09/2010 20.48.24, @p2 = 11/11/2010 20.48.24, @p3 = 1

L’altro modo è quello di agire sull’attributo “cascade” del tag <many-to-one/>, per esempio modificandolo così:

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" not-null="true" cascade="save-update"/>

Nel test possiamo dinuovo limitarci a salvare Order, ci pensa NH, grazie all’attributo “cascade” a fare tutte le considerazioni per mantenere consistente l’operazione sul database.

Adesso ci spostiamo alla parte lettura, creiamo un test con un paio di clienti e qualche ordine, e proviamo a fare delle estrazioni di entità già persistite. Supponiamo di creare una base di entità, in maniera del tutto analoga a quella sopra, creando due clienti, Customer A e Custome B, rispettivamente con 2 ed 1 ordine ciascuno. Il primo test prevede il recupero delle entità persistite tramite la creazione di un ICriteria. Ecco il codice del test:

   1: [Test]
   2: public void QueryWithICRiteria()
   3: {
   4:    CreateSomeOrdersAndCustomers();
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    {
   7:        var criteria = session.CreateCriteria<Order>();
   8:        foreach (Order order in criteria.List<Order>())
   9:        {
  10:            Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  11:        }
  12:    }
  13: }

Come si vede c’è un bel foreach che stampa dei dettagli dell’ordine e del cliente ad esso associato. Se però vediamo le query che fa NH scopriamo un problemino:

nplusone

In pratica, la List forza l’esecuzione della prima query, ma tutte le volte che andiamo ad indagare la proprietà “Customer” ecco che viene sparata una nuova query al DB, per recuperare l’entità riferita. Malgrado NH sia così bravo da non effettuare la query per lo stesso customer 2 volte, quello che accade non è comunque un comportamento ideale. Questo accade perchè l’associazione many-to-one è per default “lazy”: questo può essere utile in tantissimi casi, ma se, come nel nostro caso, sappiamo in anticipo che vogliamo avere tutte le associazioni, stiamo ricadendo nell’ anti patter Select N+1. Fortunatamente possiamo evitarlo, in primo modo agendo sulle proprietà di ICriteria:

   1: var criteria = session.CreateCriteria<Order>()
   2: .SetFetchMode("Customer",FetchMode.Eager)
   3: ;

Cambiare il modo di fetch dell associativa cambia drammaticamente le query eseguite, che si riducono ad una:

   1: SELECT this_.Id as Id3_1_, ... FROM Orders this_ inner join Customers customer2_ on this_.CustomerId=customer2_.Id

Questa è un ottimizzazione che dobbiamo sempre cercare di fare. Potevamo ottenere lo stesso risultato solo aggiungendo l’attributo “fetch” nel tag <many-to-one/>:

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" fetch="join" not-null="true" cascade="save-update"/>

Ovviamente, in uno scenario in cui volessimo cambiare il fetch-mode all’inverso, potremmo impostare via codice il SetFetchMode a select.

Lasciamo ora il fetch mode a join e proviamo a fare la stessa query con HQL:

   1: [Test]
   2: public void QueryWithHql()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     {
   7:         var query = session.CreateQuery("from Order");
   8:         foreach (Order order in query.List<Order>())
   9:         {
  10:             Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  11:         }
  12:     }
  13: }

Scopriremo che HQL ignora il tipo di fetch specificato dall’attributo fetch di <many-to-one/> ! Questo perchè HQL prevede la possibilità di specificare il fetch mode letteralmente:

   1: var query = session.CreateQuery("from Order o join fetch o.Customer");

Questa modifica ci porta dinuovo all’unica query per tutte e tre le istanze.

Con questo abbiamo finito la definizione di <many-to-one/> e, visto che questo è un corso da *zero* non abbiamo visto uno per uno tutti gli attributi di many-to-one, ma una quantità bastante a iniziare ad usarlo in modo efficace.

Diciamo in ultimo 2 cose:

  1. Mappare una FK di db con il corrispondente tipo chiave è un grave antipattern.
  2. Una FK nel database lega implicitamente 2 tabelle, qui abbiamo specificato uno dei lati dell’associazione, l’altro lato lo vedremo nella prossima parte.

 

Download Sorgenti
Parte 2

Parte 4

Saturday, September 11, 2010 10:36:29 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate | NHibernate Tutorial

# Friday, September 03, 2010

Meglio, più in generale, non funziona più l’intellisense per qualsiasi documento XML per cui abbiamo uno ( o più.. ) XSD. Questo accade perchè Visual Studio trova più sorgenti per lo schema dichiarato nell’ XML, nel caso NHibernate abbiamo la seguente dichiarazione in testa:

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="....." namespace="....." >

Se abbiamo nella solution, o nella directory %Program Files%/Microsoft Visual Studio XXX/Xml/Schemas  uno o più file che definiscono lo stesso namespace, in questo caso

<xs:schema targetNamespace="urn:nhibernate-mapping-2.2" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="urn:nhibernate-mapping-2.2" elementFormDefault="qualified" attributeFormDefault="unqualified">

Visual Studio trova un ambiguità per cui smette di fornire l’intellisense. La situazione si può risolvere facilmente senza disperare:

Basta cliccare sul documento XML da visual studio e dalla finestra proprietà editare la voce Schemas come mostrato qui sotto:

xmlschemas

Così facendo si apre una finestra in cui è possibile abilitare/disabilitare gli schema che visual studio intende usare:

xmlschema2

Deselezionando uno degli schemi ambigui tutto torna a funzionare. Ovviamente questo funziona per qualsiasi ambiguità di schema, non solo per NHibernate.

Friday, September 03, 2010 10:28:31 AM (GMT Daylight Time, UTC+01:00)  #    Comments [3] - Trackback
NHibernate | Programmin

# Wednesday, September 01, 2010
continua da Parte1: Cosa ci serve

In questa parte costruiamo un progetto con NHibernate che ci consentirà di persistere una prima entità. In effetti la parte OR/M sarà tenuta il più semplice possibile, niente reference o collezioni o componenti: l’obiettivo è creare uno scheletro funzionante e testabile. Ovviamente tutto quanto detto servirà da spunto, alcune cose potranno sembrare ovvie, altre meno, fate come credete meglio, ma poichè questo è un corso “da zero” voglio soffermarmi su tutti i punti.

Decidiamo di mettere le entità in una Class Library ( dll ). Questa è una scelta che probabilmente sarà conveniente fare anche per una soluzione vera da mandare in produzione. Creiamo quindi una nuova solution con Visual Studio. Distinguiamo il nome della soluzione con NHFromScratch.All, ed aggiungiamo all' interno un nuovo progetto di tipo class library, che andiamo a chiamare NHFromScratch.Entities.

t1

All’ interno della cartella radice della solution, creiamo una sottocartella “Lib”, atta a contenere le dipendenze della soluzione ( NHibernate e non ), ad esempio in aggiunta alle dipendenze di NH vedete la nunit.framework.dll, dipendenza che ci servirà a breve quando vorremo testare le nostre entità.

t2

Bene, il passo successivo è creare il mapping per la prima ed unica entità che vogliamo gestire. Ci sono una moltitudine di strategie per creare un mapping per NH, quella che presento qua è una soluzione “plain vanilla”, senza nessun fronzolo e a costo zero: con la pratica ognuno sceglie il metodo che meglio crede, ma per imparare è mia opinione che scrivere il file XML sia di aiuto. Aggiungiamo quindi una sottocartella “Mapping” nella class library appena creata, e in questo folder creiamo un nuovo file con il nome della entità ( Customer nel nostro caso ) con l’estensione .hbm.xml. Se abbiamo abilitato l’intellisense, come fortemente consigliato nella parte 1 l’unico tag che dobbiamo ricordare a memoria è <hibernate-mapping>, tutti gli altri saranno esposti via intellisense dopo che il namespace sarà correttamente specificato, come mostrato qui sotto:

 t4

Cosa importantissima da ricordare, ricordiamo di impostare “Embedded Resource” come build action sul file di mapping, altrimenti… la configurazione di NHibernate non funzionerà. Ad essere precisi questo si potrebbe evitare, ma per farlo dovremmo distribuire i file di mapping insieme all’applicazione, ed è un’opzione che francamente non mi piace e nemmeno ho mai visto praticare.

Fatto questo non rimane che scrivere difatto il mapping. In questo primo test useremo solo tre elementi di mapping: class,key e property, praticamente il minimo indispensabile per avere un entità al lavoro. Approfondiremo i vari aspetti nelle prossime parti.

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   3:                                                       assembly="NHFromScratch.Entities">
   4:   <class name="Customer" table="Customers">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="Name" type="String" not-null="true"/>
   9:     <property name="AddressLine1" type="String" not-null="true"/>
  10:     <property name="AddressLine2" type="String" />
  11:     <property name="City" type="String" not-null="true"/>
  12:     <property name="ZipCode" type="String" not-null="true"/>
  13:     <property name="Active" type="Boolean" not-null="true"/>
  14:   </class>
  15:   
  16: </hibernate-mapping>
  17:  
  18:  
  19:  

Facciamo tuttavia due parole per dire a grandi linee cosa contiene il mapping della entità Customer. Innanzitutto è specificato il namespace e l’assembly che contiene l’entità mappata. Questo non è obbligatorio, ma ci consente di scrivere in maniera più leggibile il nome della classe più avanti. Il tag class indica appunto la classe che intendiamo utilizzare come entity. Ricordiamo che spesso non esiste un rapporto 1:1 tra tabelle ed entità, ms in questo semplice esempio così accadrà. Il tag class ci permette di definire il nome della classe, e a quale tabella è associata su DB. Il tag più sotto <id> è necessario: NHibernate vuole sapere come il database “distingue” le entità. Ci sono svariati modi per fare questo, e ll modo viene scelto dal sotto-tag generator: in questo esempio abbiamo specificato un generator “native”, che significa id univoco assegnato dal DB ( identity, auto-incrementale ) comunque esso si chiami nel DB target. Successivamente vengono le proprietà, con i loro bei nomi e tipi dato. Noterete che non ho specificato nessun nome per la corrispondente colonna sul DB: semplicemente se  il nome è uguale nessuno ci obbliga a farlo, non ripetiamo noi stessi. Anzi, diciamo anche che il tipo sarebbe un surplus se scrivessimo a mano la classe sottostante: NHibernate usa molto inferire le informazioni ovunque sia possibile. Noi però la classe non abbiamo voglia di farla a mano, per cui ci serviamo di hbm2net ( vedi la parte 1 ). Per scatenare questo tool, usiamo un pre build step di Visual Studio, come quello qui sotto:

t6

in pratica diciamo ad hbm2net di lavorare su tutti i file .hbm.xml che trova nella sottocartella mapping della solution, di mettere gli output ( ovvero le classi ) nella cartella del progetto. Sebbene hbm2net sia un editor template based che può generare un po’ qualsiasi artefatto partendo dall hbm, senza parametri si limita a generare le classi corrispondenti al mapping. Lanciando la build nell’output dovreste vedere il progress anche di hbm2net:

t7

Finita la build, vi trovate il file autogenerato per la entity su file system. Aggiungetelo al progetto.

t8

Date un’occhiata alla classe autogenerata, date un occhiata anche al mapping: se avete provato qualche altro tool per NH noterete che il lavoro fatto finora sembra molto semplificato. Stiamo scrivendo solo lo stretto indispensabile, quello che non ci serve lo lasciamo assolutamente da parte, perchè questo è NHibernate da ZERO.

t9

Bene, per completare la solution aggiungiamo la reference ad NHibernate: NHibernate e Iesi.Collection sono gli unici assembly che dobbiamo referenziare a tempo di build.

E’ finalmente l’ora di andare in test. Aggiungiamo alla solution un progetto di tipo Class Library, in cui inseriremo gli unit test.

t10

La solution di test, in modo simile ad un progetto che deve andare in produzione, necessita delle reference non statiche di NHibernate. Noi le abbiamo nella cartella Lib della solution, dobbiamo spostarli a fianco della dll di produzione. Possiamo scegliere di farlo con un post-build step, come illustrato qui sotto.

t11

Anche le reference di questo progetto sono un po’ più impegnative: ci serve Iesi.Collection & NHibernate come al solito, ma anche il nostro assembly con le entità, log4net per il logging, ed in più, ovvviamente, nunit.framework, va da sè.

t12

A me personalmente piace la barra verde di NUnit, per cui testiamo il progetto con NUnit, mettendo NUnit.exe come “start external program”.

t13

A questo punto creiamo il nostro unit test, con lo scopo di configurare NH, un po’ di log, e vedere se riusciamo a creare il Database. Sì, in questo tutorial il database non lo facciamo a mano, perchè anche se questo è NHibernate da ZERO, vogliamo scrivere il meno codice possibile.

Il codice dello unit test è qui di seguito:

   1: namespace NHFromScratch.Tests
   2: {
   3:     [TestFixture]
   4:     public class TestCustomer
   5:     {
   6:         static TestCustomer()
   7:         {
   8:             ConfigureLogForNet();
   9:         }
  10:  
  11:         ISessionFactory sessionFactory;
  12:         [SetUp]
  13:         public void Setup()
  14:         {
  15:             
  16:             CreateSessionFactory();
  17:         }
  18:  
  19:         [Test]
  20:         public void CanCreatedatabse()
  21:         {
  22:             SchemaExport export = new SchemaExport(CreateConfiguration());
  23:             export.Execute(true, true, false);
  24:         }
  25:  
  26:         [Test]
  27:         public void CanPersistCustomer()
  28:         {
  29:             Console.WriteLine("\n****** SAVE A CUSTOMER ********");
  30:             //save a customer
  31:             using (var session = sessionFactory.OpenSession())
  32:             using(var transaction = session.BeginTransaction())
  33:             {
  34:  
  35:                 Customer c = new Customer()
  36:                 {
  37:                     Active = true
  38:                      ,
  39:                     AddressLine1 = "xxxx"
  40:                      ,
  41:                     City = "Cuneo"
  42:                      ,
  43:                     Name = "Bill Gates"
  44:                      ,
  45:                     ZipCode = "12060"
  46:                 };
  47:                 session.Save(c);
  48:                 transaction.Commit();
  49:             }
  50:             Console.WriteLine("\n****** RETRIEVE A CUSTOMER ********");
  51:             //retrieve a customer
  52:             using (var session = sessionFactory.OpenSession())
  53:             
  54:             {
  55:  
  56:                 var customer = session.CreateCriteria<Customer>()
  57:                     .Add(Expression.Eq("Name", "Bill Gates"))
  58:                     .UniqueResult();
  59:  
  60:                 Assert.NotNull(customer);
  61:             }
  62:  
  63:             Console.WriteLine("\n****** MODIFY A CUSTOMER ********");
  64:             //modify a customer
  65:             using (var session = sessionFactory.OpenSession())
  66:             using (var transaction = session.BeginTransaction())
  67:             {
  68:  
  69:                 var customer = session.CreateCriteria<Customer>()
  70:                     .Add(Expression.Eq("Name", "Bill Gates"))
  71:                     .UniqueResult<Customer>();
  72:                 customer.ZipCode = "0000";
  73:                 Assert.NotNull(customer);
  74:                 
  75:                 transaction.Commit();
  76:             }
  77:  
  78:  
  79:             Console.WriteLine("\n****** VERIFY CUSTOMER IS MODIFIED ********");
  80:             //verify mod
  81:             using (var session = sessionFactory.OpenSession())
  82:             {
  83:  
  84:                 var customer = session.CreateCriteria<Customer>()
  85:                     .Add(Expression.Eq("Name", "Bill Gates"))
  86:                     .UniqueResult<Customer>();
  87:                 
  88:                 Assert.NotNull(customer);
  89:                 Assert.AreEqual("0000", customer.ZipCode);
  90:             }
  91:  
  92:  
  93:             Console.WriteLine("\n****** DELETE A CUSTOMER ********");
  94:             //delete a customer
  95:             using (var session = sessionFactory.OpenSession())
  96:             using (var transaction = session.BeginTransaction())
  97:             {
  98:  
  99:                 var customer = session.CreateCriteria<Customer>()
 100:                     .Add(Expression.Eq("Name", "Bill Gates"))
 101:                     .UniqueResult();
 102:                 session.Delete(customer);
 103:                 transaction.Commit();
 104:             }
 105:             Console.WriteLine("\n****** VERIFY CUSTOMER IS DELETED ********");
 106:             //verify delete
 107:             using (var session = sessionFactory.OpenSession())
 108:             
 109:             {
 110:  
 111:                 var customer = session.CreateCriteria<Customer>()
 112:                     .Add(Expression.Eq("Name", "Bill Gates"))
 113:                     .UniqueResult();
 114:                 Assert.IsNull(customer);
 115:             }
 116:         }
 117:  
 118:  
 119:  
 120:         private static void ConfigureLogForNet()
 121:         {
 122:             TraceAppender app = new TraceAppender();
 123:             app.Layout = new SimpleLayout();
 124:             //BasicConfigurator.Configure( app);
 125:         }
 126:  
 127:         private void CreateSessionFactory()
 128:         {
 129:             Configuration cfg = CreateConfiguration();
 130:             sessionFactory = cfg.BuildSessionFactory();
 131:         }
 132:  
 133:         private static Configuration CreateConfiguration()
 134:         {
 135:             Configuration cfg = new Configuration();
 136:             cfg.Configure();
 137:             // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
 138:             // contiene customer
 139:             cfg.AddAssembly(typeof(Customer).Assembly);
 140:             return cfg;
 141:         }
 142:  
 143:  
 144:     }
 145: }

Fatto questo lanciamo il test “CanCreateDatabase” e, come probabilmente sospettiamo, andiamo in rosso con il logger che dice qualcosa come qua sotto:

t14

In effetti, ci siamo “dimenticati” di configurare NHibernate, per cui a che DB potrebbe mai puntare ? Come vedete avere il log abilitato lascia pochi dubbi rispetto a quale sia l’errore. Tra l’altro manca persino il database…

Quindi creiamo prima un database vuoto, non serve fare nessuna tabella, lasciamo che sia NHibernate a fare il lavoro sporco

t16

 

Nell’esempio ho creato un database con nome NHFROMSCRATCH sull’SQLEXPRESS locale. Poi tornando alla  , quando si vedeva il contenuto dello ZIP, rocorderete la cartella Configuration_Templates: in questa cartella ci sono appunto dei template di configurazione, in cui basta sostituire un po’ di cose per essere pronti a lavorare con il db scelto:

t15

Nel nostro caso scegliamo MSSQL. Il contenuto del file, dopo le modifiche del caso è questo:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2:  
   3:  
   4: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   5:   <session-factory name="NHibernate.Test">
   6:     <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
   7:     <property name="connection.connection_string">Server=.\SQLEXPRESS;initial catalog=NHFROMSCRATCH;Integrated Security=SSPI</property>
   8:     <property name="adonet.batch_size">10</property>
   9:     <property name="show_sql">true</property>
  10:     <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
  11:     <property name="use_outer_join">true</property>
  12:     <property name="command_timeout">60</property>
  13:     <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
  14:     <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
  15:   </session-factory>
  16: </hibernate-configuration>

 

Come si vede, le modifiche sostanziali sono nella proprietà connection string, e in show_sql, che ho messo a true, per visualizzare le query mentre NH le esegue: un trucco che ci permetterà non tanto di vedere come NHibernate fa le query, ma piuttosto quante ne fa, per fare performance tuning. Questa sessione di configurazione potrà essere messa nel file di configurazione dell’applicazione, o vicino al file binario che la usa in un file di nome hibernate.cfg.xml, soluzione scelta nell’esempio.

t17

Ricordiamoci che, dopo aver inserito il file hibernate.cfg.xml nel progetto, dobbiamo impostare il flag “copy if newer” cosicchè verrà copiato automaticamente nella cartella di uscita.

vediamo ora come è fatto il test “CanCreateDatabase”:

   1: [Test]
   2: public void CanCreatedatabse()
   3: {
   4:    SchemaExport export = new SchemaExport(CreateConfiguration());
   5:    export.Execute(true, true, false);
   6: }

 

Usiamo la classe di Helper SchemaExport: questa classe, partendo dalla Configurazione corrente, si occupa di effettuare la creazione dello schema del database. Ora lanciando il test questi si comporta meglio, e nella porzione console è possibile  vedere la DDL che è stata inviata al database.

 

t18

Tramite il flag della funzione Export, è possibile sottometere lo script al sistema database sottostante, nel nostro caso otterremo su DB l’unica tabella del nostro minuscolo modello di test:

t19

Quindi facciamo un breve recap fino a questo punto: con un solo file ( il mapping appunto ) abbiamo creato un database e il codice c#. Tutto questo grazie ad hbm2net, che è un code generator open source recuoperabile da NHContrib. Con un po’ di pazienza è possibil emodificare e/o scrivere template aggiuntivi per hbm2net per geenrare altri tipidi artefacts, ad esempio DTO o altro, ma questo sarà visto più avanti. In pratica possiamo vedere hbm2net come un generatore di codice programmabile, che di default produce il codice delle entity partendo dai file hbm.

t18b

 

Ora possiamo scrivere il codice di prova per il modello. Mettendo a “true” il valore di show_sql nella configurazione, NH manda in standard output le query, per cui in debug è possibile ispezionare cosa succede dietro le quinte. Il nostro semplice unit test crea/modifica/cancella una semplice istanza della entità customer, ecco il codice dello unit test che fa tutto questo:

   1: namespace NHFromScratch.Tests
   2: {
   3:     [TestFixture]
   4:     public class TestCustomer
   5:     {
   6:         static TestCustomer()
   7:         {
   8:             ConfigureLogForNet();
   9:         }
  10:  
  11:         ISessionFactory sessionFactory;
  12:         [SetUp]
  13:         public void Setup()
  14:         {
  15:             
  16:             CreateSessionFactory();
  17:         }
  18:  
  19:         [Test]
  20:         public void CanCreatedatabse()
  21:         {
  22:             SchemaExport export = new SchemaExport(CreateConfiguration());
  23:             export.Execute(true, true, false);
  24:         }
  25:  
  26:         [Test]
  27:         public void CanPersistCustomer()
  28:         {
  29:             Console.WriteLine("\n****** SAVE A CUSTOMER ********");
  30:             //save a customer
  31:             using (var session = sessionFactory.OpenSession())
  32:             using(var transaction = session.BeginTransaction())
  33:             {
  34:  
  35:                 Customer c = new Customer()
  36:                 {
  37:                     Active = true
  38:                      ,
  39:                     AddressLine1 = "xxxx"
  40:                      ,
  41:                     City = "Cuneo"
  42:                      ,
  43:                     Name = "Bill Gates"
  44:                      ,
  45:                     ZipCode = "12060"
  46:                 };
  47:                 session.Save(c);
  48:                 transaction.Commit();
  49:             }
  50:             Console.WriteLine("\n****** RETRIEVE A CUSTOMER ********");
  51:             //retrieve a customer
  52:             using (var session = sessionFactory.OpenSession())
  53:             
  54:             {
  55:  
  56:                 var customer = session.CreateCriteria<Customer>()
  57:                     .Add(Expression.Eq("Name", "Bill Gates"))
  58:                     .UniqueResult();
  59:  
  60:                 Assert.NotNull(customer);
  61:             }
  62:  
  63:             Console.WriteLine("\n****** MODIFY A CUSTOMER ********");
  64:             //modify a customer
  65:             using (var session = sessionFactory.OpenSession())
  66:             using (var transaction = session.BeginTransaction())
  67:             {
  68:  
  69:                 var customer = session.CreateCriteria<Customer>()
  70:                     .Add(Expression.Eq("Name", "Bill Gates"))
  71:                     .UniqueResult<Customer>();
  72:                 customer.ZipCode = "0000";
  73:                 Assert.NotNull(customer);
  74:                 
  75:                 transaction.Commit();
  76:             }
  77:  
  78:  
  79:             Console.WriteLine("\n****** VERIFY CUSTOMER IS MODIFIED ********");
  80:             //verify mod
  81:             using (var session = sessionFactory.OpenSession())
  82:             {
  83:  
  84:                 var customer = session.CreateCriteria<Customer>()
  85:                     .Add(Expression.Eq("Name", "Bill Gates"))
  86:                     .UniqueResult<Customer>();
  87:                 
  88:                 Assert.NotNull(customer);
  89:                 Assert.AreEqual("0000", customer.ZipCode);
  90:             }
  91:  
  92:  
  93:             Console.WriteLine("\n****** DELETE A CUSTOMER ********");
  94:             //delete a customer
  95:             using (var session = sessionFactory.OpenSession())
  96:             using (var transaction = session.BeginTransaction())
  97:             {
  98:  
  99:                 var customer = session.CreateCriteria<Customer>()
 100:                     .Add(Expression.Eq("Name", "Bill Gates"))
 101:                     .UniqueResult();
 102:                 session.Delete(customer);
 103:                 transaction.Commit();
 104:             }
 105:             Console.WriteLine("\n****** VERIFY CUSTOMER IS DELETED ********");
 106:             //verify delete
 107:             using (var session = sessionFactory.OpenSession())
 108:             
 109:             {
 110:  
 111:                 var customer = session.CreateCriteria<Customer>()
 112:                     .Add(Expression.Eq("Name", "Bill Gates"))
 113:                     .UniqueResult();
 114:                 Assert.IsNull(customer);
 115:             }
 116:         }
 117:  
 118:  
 119:  
 120:         private static void ConfigureLogForNet()
 121:         {
 122:             TraceAppender app = new TraceAppender();
 123:             app.Layout = new SimpleLayout();
 124:             //BasicConfigurator.Configure( app);
 125:         }
 126:  
 127:         private void CreateSessionFactory()
 128:         {
 129:             Configuration cfg = CreateConfiguration();
 130:             sessionFactory = cfg.BuildSessionFactory();
 131:         }
 132:  
 133:         private static Configuration CreateConfiguration()
 134:         {
 135:             Configuration cfg = new Configuration();
 136:             cfg.Configure();
 137:             // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
 138:             // contiene customer
 139:             cfg.AddAssembly(typeof(Customer).Assembly);
 140:             return cfg;
 141:         }
 142:  
 143:  
 144:     }
 145: }

Ed ecco uno screenshots di come NH opera dietro le quinte:

t20

Nella prossima parte vedremo meglio i dettagli del file di mapping e il tag <many-to-one> insieme a <bag>.

 

 

Download Sorgenti

Parte 1

Parte 3

Wednesday, September 01, 2010 8:11:59 PM (GMT Daylight Time, UTC+01:00)  #    Comments [2] - Trackback
NHibernate | NHibernate Tutorial

# Saturday, August 28, 2010
Vai alla parte 2: Creare un progetto

Mi è ventuta l’idea di produrre una serie di post che consentano a chi vuole iniziare a lavorare con NHibernate, o almeno a provare per vedere se questo OR/M è la scelta giusta, di creare in modo ordinato un ambiente di test pulito, sapendo bene cosa serve, evitando il percorso “pasticciato” che spesso si fa all’inizio.

Bene, come partenza bisogna scaricare NHibernate. Il posto giusto dove farlo è il progetto su sourceforge, e il modo giusto di farlo è procurare i file binary necessari. Scaricare i sorgenti per il momento può essere tranquillamente tralasciato. Puntando al link di download, vi trovate qualcosa del genere:

s1

Come vedete ci sono più versioni, ed anche una ultima release, che in questo caso è la 3.0.0 Alpha2. Io ho cerchiato la versione 2.1.2 GA che è quella che useremo per questa serie di articoli, ma ho anche cerchiato GA: “general availability”. Dunque, perchè non ho scaricato l’ultima versione ? Cosa significa GA ? NHibernate ha una politica di rilascio ben consolidata, e marca con GA le versioni “da produzione”. Quindi, se non siete a conoscenza di qualche funzionalità particolare che c’è solo nella alpha di turno, se non siete dell’idea di vedervi cambiate sotto il naso funzioni e comportamenti, scaricate le versioni GA più recenti che trovate. Non esistono al momento “Setup” di NHibernate, mi risulta che ci fosse qualcosa in passato, comunque anche se trovaste qualcosa, vi suggerisco di evitarlo: NH consente di fare un private deploy, e avere traccia di quello che serve ci sarà utile in fase di consegna dell’applicativo sulle macchine di produzione. Scarichiamo quindi il file NHIbernate-2.1.2.GA-bin.zip. Scaricato il file il layout del contenuto è quello schematizzato qui sotto:

s2

Nella root, oltre ad un po’ di paccottiglia varia, troviamo due folder importanti che sono quelli che ci servono per mettere NHibernate all’opera: Required_Bins, and Required_For_LazyLoading.Tutte le dll contenute nel file required bin devono essere rilasciate insieme all’applicazione ( oltre a servire ovviamente  in fase di sviluppo :) ), mentre dalla cartella Required_For_LazyLoading si può scegliere quale folder distribuire in base a quale proxy generator si sceglie. Questo è un tema che si vedrà più avanti, per adesso basta sapere che NHibernate può derivare internamente alcune delle nostre classi quando decidiamo di avere delle entità “Lazy”, e questo è ottenuto tramite delle librerie esterne di generazione di classi “proxy”. Quale proxy generator usare dipende dalla configurazione, e quindi dalla stessa configurazione dipende quale folder distribuire. Negli esempi useremo LinFu. Diciamo che questa scelta è di scarso impatto sulla vita del vostro progetto, salvo in situazioni particolari, per cui per il momento possiamo andare avanti.

Importantissimi in fase di sviluppo sono i file nhibernate-mapping.xsd e nhibernate-configuration.xsd. Questi due file vi consentono di avere l’intellisense abilitato per i file di mapping e per la configurazione, e quindi sono assolutamente indispensabili. Malgrado siano finiti nella cartella required_bin, sono invece praticamente inutili in fase di distribuzione. Per attivare l’intellisense occorre copiare i due file suddetti nella cartella apposita di visual studio:

s3

Attenzione: può succedere che imporvvisamente Visual Studio smetta di fornire l'intellisense: tipicamente questo accade perchè ci sono più versioni dello stesso schema XML associate. Date un occhiata a questo post che spiega come risolvere il problema.

Inoltre, per sviluppare e per seguire gli esempi in questo tutorial occorrerà NUnit. Se ce lo avete già usate quello, altrimenti recuperate l’ultima versione dal sito ufficiale. Io di solito scarico il binario, non il setup, e lo scompatto sotto il folder programmi. Anche NUnit funziona con il semplice deploy Xcopy.

Ultima cosa prima di partire, lavorando con NHibernate ci sono alcune attività ripetitive che sarebbe bello evitare: consiglio di scaricare anche il tool hbm2net che ci consentirà di generare automaticamente le classi per nel corso del tutorial. Il tool si trova nel progetto NHContrib. Scaricate e scompattate lo zip in un folder, per esempio in C:\hbm2net. Il tool è un compilatore linea di comando, e sarà lanciato automaticamente da Visual Studio, ma perchè ciò avvenga occorre aggiungere la cartella in cui avete messo hbm2net nella variabile di ambiente PATH. per fare questo raggiungete le “proprietà del Sistema”:

s5

e poi aggiungere il path suddetto alla variabile PATH:

s6

 

Nella Parte2 vediamo come organizzare un progetto con NH.

Saturday, August 28, 2010 12:57:29 PM (GMT Daylight Time, UTC+01:00)  #    Comments [7] - Trackback
NHibernate | NHibernate Tutorial

# Wednesday, August 11, 2010

For maintenance reasons sometime the original NHForge location is not available. In such a case you can download the file containing the binary release of the tools in the original NHContrib project download location:

nhcontribdownload

Wednesday, August 11, 2010 9:10:41 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
db2hbm | hbm2net | NHibernate

# Saturday, July 31, 2010

 José Romaniello is doing progress in hql intellisense for Visual Studio 2010. He manage to integrate some NHWorkbench codebase into it, to have some intellisense working. Even if the project is still in alpha, I suggest you to download and install to give it a try.

Here below a screenshot of the tool at work:

 

image

Nice, isn’t it ?

As an addition I was added to the project by Josè, and I would be happy to add some contribution to this important project for the NHibernate ecosystem.

Saturday, July 31, 2010 3:27:17 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
HQL language Service | NHibernate

# Wednesday, July 14, 2010

Apprendo da questo post di Fabio Maulo una interessante feature di NHibernate 3 implementata nel nuovo subset di API: QueryOver. Il post di Fabio approfondisce il pattern Query Object, e quello che salta all’occhio è la pulizia con cui si riesca finalmemte a farte un paging “ad arte”. Per fare l’accesso paginato in generale occorre avere il count delle righe che il sistema vorrebbe tornarci, e questo constringeva in passato a fare delle implementazioni un po’ sporche in cui si era costretti a specificare la query di selezione e quella di count. QueryOver propone la funzione query.ToRowCountQuery(); – dove query è appunto una query di QueryOver, e la funzione ci restituisce la query di conteggio in modo pulito e trasparente. Assolutamente molto utile. Io non sono uno da trunk, ma una funzione del genere mi fa venire voglia di passare subito a NH3, ancor prima che venga rilasciata la GA.

Wednesday, July 14, 2010 8:50:20 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate

# Monday, June 14, 2010

bug The right place for bug reporting or requiring additional features is

here

Since the project is new, it is normal to have something to fix. The problem is that my testing would just be not sufficient, so your help will really be appreciated. Thanks!

Monday, June 14, 2010 4:31:59 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Code GEneration | NHibernate | NHWorkBench

# Saturday, June 05, 2010

With a little delay I found people complaining about the schema file missing from the db2hbm deployed package. Now the file is included in the download package, and yanch provided a shortcut in the doc to download the file from sourceforge. Thanks!

Well, a few word about db2hbm and Oracle ( and any other database but MSSQL now ). I used the schema information provided by NH as long as possible, but these information does not provides the required details in order to discover completely the foreign keys, and foreign keys are necessary for creating associations. Not really an NH problem, actually NH leverages ADO.NET for schema inquiry, but ADO.NET seems to miss the foreign key part. So the only solution to have db2hbm working for all database is to provide a custom foreign key crawler for the DB.

Saturday, June 05, 2010 2:32:40 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Code GEneration | NHibernate

# Monday, May 31, 2010

First of all I renamed the project on Sourceforge. Now it is more sensible NHibernate Workbench.

nhwsourceforge

In the SVN repository there is now a tag to the version 1.0.0.11, and the trunk claim to be the version 1.0.0.2.00.

Now I’m planning to allow to use NH Workbench attached to a running application: this should help us to play with application compiled without mappings ( ie ConfOrm and Fluent NH ). Then I would like to improve the “Probe” class letting it be more versatile and modifiable by the user, probably using some sort of script engine: I’m thinking to use IronPython, but any suggestion are welcome. Just to clarify: the probe class serves to insolate NHWorkbench from the NH version used by the project under test. We basically runs the test in a separate app domain, but we need the “probe” type to be unbounded to any NH specific version. This is done by using reflection, but it would be easier to be done in a script. The same engine will be useful to write some NHibernate testing: instead of use just HQL, we will be able to submit some portion of code on the fly and see what happen. The other step is to allow the user writing a mapping on the fly and imemdiately see what happen ( by using hbm2net behind the scenes ). Ok, it’s a lot of work, I’ve no idea the order this will be done, let me know if you have any idea and preference.

Monday, May 31, 2010 11:35:43 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
Code GEneration | HQL Intellisense | NHibernate | NHWorkBench

# Monday, May 24, 2010

Sometimes I receive some notification on where source code for both db2hbm and hbm2net are located. The best way is to check-out the source of the NHContrib project: https://nhcontrib.svn.sourceforge.net/svnroot/nhcontrib

hbm2net is still in alfa, but I frequently use it in my projects as a class generator. It really lack some documentation: it is really a powerful artifact generator, and by writing proper T4 templates any artifact can be generated. Db2hbm is working ok for MSSQL, but there is not yet an implementation for oracle and other DB, even if Ricardo Peres provided me some interesting code to work on.

Hope this help who’s looking for these tools source code.

Monday, May 24, 2010 8:08:30 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Code GEneration | NHibernate

# Saturday, May 22, 2010

Just looking around for some related NHibernate projects, I found this open source profiler. For people who start to use NHibernate and before to buy something more accurate as NHProf, it is really useful having a log easy to read to discover performance or bad usage issues.

Saturday, May 22, 2010 9:27:01 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate

As you probably guess, the Fatica.Labs.HqlEditor evolved as a component used by NH Workbench, that is basically a tool ispired by the old and wise NHQA by Ayende. There is, in comparison, some new ideas and some missing required functions. Anyway I decided to publish a first drop because it already help me on my day job. If you find the project useful please consider visits the following links:

And, if you want to join the project, please let me know.

Saturday, May 22, 2010 6:55:49 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
HQL Intellisense | NHibernate | NHWorkBench

# Wednesday, May 19, 2010

In the spirit of “Release early. Release often. And listen to your customers” ( cit. ), even if not so early in term of time since the preview, I decided to release a first drop of the “HQL Intellisense thing” I’m working on. The current version is just able to load an existing mapping assembly, a configuration, help us to write an hql query, submit it to NH and see some results. Here an overall screenshot:

s1

To use it you need to download the bits, and then “create a project” a project is, in the NH Workbench world, a bounch of file representing what we are working on ( and actually is a project in the MSBUILD world. To use the tool now we need at least a working NH configuration file ( your app.config or web config ) and one or more mapping assembly(ies). You add the files to the project by right clicking the project tree:s2

After you added the file you can save the project, so it can be reopened when needed. Please note that the mapping assembly has to be opened from a location containing all the required dependencies ( usually the application folder, or the bin folder ).

After the project is created, you need to compile it before starting to write the queries:

s3

You can compile the project by clicking the button on the toolbar as shown in the picture Fig3

 

 

 

Compiling the project should produce a report in the log area:

s4

If you find the report too verbose, you can uncheck some of the button in the log toolbar. After a successful compilation, we can open a query (hql) document:

s5

This will open a pane in the document area in which we can write HQL queries with some intellisense/auto-completion. Plaese note that, for have the entity completion, after the “from” keyword we need to press ctrl+space to see the completion combo.

 

 

 

 

 

 

s6

Here an example HQL document. After a valid query is done we can submit it to NH and see the result:

 

The “play” button is enabled only if a valid query ( no errors ) is written in the document. The first and count places are useful to limit the query results.

s7 By pressing the play button, you will be able to se the query results ( if any ):

s8

Next steps:

  • Solve the bugs till now
  • Add supports for hbm2net, so user can write mapping and immediately see it at works.

Enjoy !

Wednesday, May 19, 2010 4:30:52 PM (GMT Daylight Time, UTC+01:00)  #    Comments [10] - Trackback
Code GEneration | NHibernate

There was a bug in the many-to-many strategy, causing a null reference exception. The bug was solved, and a current snapshot of db2hbm can be found here as usual.

db2hbm

Wednesday, May 19, 2010 10:13:46 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Code GEneration | NHibernate

# Thursday, May 13, 2010

clip_image001

 

Fabio Maulo propone un sondaggio per la versione di NH in uscita. Dovrà compilare su Fx 3.5 o 4.0 ?

Personalmente preferirei ancora un uscita in 3.5. Votate!

Thursday, May 13, 2010 7:53:23 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate

# Thursday, April 29, 2010

s9cThere is some interesting news with HqlEditor: now it is able to translate the query using the proper parser ( Antlr or Classic ) depending on the NH version used to build the assembly in test. An error message is shown when the syntax is parsed as invalid.

 

 

 

 

 

 

 

A first table containing the entities is present too, here below a screenshot:

s10a

As you can probably notice, the data grid is not a real grid. This is because is not so easy to present an NH graph without converting it in some sort of DTO, but I don’t want to add anything but the query process itself. So I decided to use a sort of JSON serializer to present each object in a textual fashion. In the toolbar there is the query limit too: this act by adding a SetFirstResult(), SetMaxResults() function call in the query creation. Using a count=0 forces the system to avoid limiting the query: if the data you retrieve is a big bounch you will probably experience some delay, but should be useful in the case the driver does not support limits.

Thursday, April 29, 2010 5:09:29 PM (GMT Daylight Time, UTC+01:00)  #    Comments [1] - Trackback
HQL Intellisense | NHibernate | NHWorkBench

# Tuesday, April 27, 2010

Per abilitare il logging delle query con NHibernate occorre:

  1. Avere nella bin dell applicativo ( ie: nella /bin per le applicazioni web, a fianco dell’ esequibile per le applicazioni stand-alone ) la dll di log4net.
  2. Aggiungere nel file di configurazione la sessione di config per log4net:
  3. <configuration>
      <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>

      </configSections>
      <log4net>
        <appender name="console" type="log4net.Appender.ConsoleAppender">
          <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%-5p - %m%n" />
          </layout>
        </appender>
        <logger name="NHibernate.SQL" additivity="false">
          <level value="ALL"/>
          <appender-ref ref="console" />
        </logger>
        <root>
          <priority value="WARN" />
          <appender-ref ref="console" />
        </root>
      </log4net>
    </configuration>

    Con questa configurazione si usa il console appender, per un’applicativo web potrebbe essere meglio usare un TraceAppender o un altro appender di proprio gusto ;-)

     

  4. Assicurarsi di chiamare, almeno una voltanell’applicazione log4net.XmlConfigurator.Configure()
  5. Aggiungere questa proprietà nella configurazione di NH:                                         <property name="show_sql">true</property>

Se NON vogliamo mettere nulla di log4net nella configurazione:

Possiamo configurare log4net dall’ applicativo, con un paio di linee di codice:

   1:              TraceAppender app = new TraceAppender();
   2:              app.Layout = new SimpleLayout();
   3:              LoggerMatchFilter filter = new LoggerMatchFilter();
   4:              filter.LoggerToMatch="NHibernate.SQL";
   5:              filter.AcceptOnMatch = true;
   6:              filter.ActivateOptions();
   7:              app.AddFilter(filter); // L'ordine di questo filtro
   8:              app.AddFilter(new DenyAllFilter()); // e di quest'altro E' importante
   9:              app.ActivateOptions();
  10:              BasicConfigurator.Configure( app);
 
 
 

In questo caso si possono saltare gli step da 1 a 3. Questo codice deve essere chiamato una volta nell’applicazione in fase di startup, per un’applicazione web, potrebbe andare bene l’evento di startup dell'applicazione in global.asax. Con l’appender e i filtri configurati si ottiene nell'area trace di Visual Studio ( Output-tab Debug)  l’output delle sole query generate da NH ( senza gli altri logger, se servono  si possono togliere i filtri )

Tuesday, April 27, 2010 4:43:20 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate | log4net

# Sunday, April 25, 2010

Mauricio Scheffer has just released a web based console for editing HQL. He based the intellisense on my project. I’m happy to see some of my effort reused somewhere! So thanks to Mauricio.

Sunday, April 25, 2010 10:29:44 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
HQL Intellisense | NHibernate

# Thursday, April 22, 2010

There is some interesting progress with my project Fatica.Labs.HqlEditor. I just want to share some screenshot:

s5

Well, it is growing to be a real tool, and in my idea would became a sort of test bed in which the user can add or modify mapping, try the queries, change the config, export a database script, reverse engineering and so on. Actually all the low level tool to achieve that are available.

Ok, let’s explain the layout:

  1. The document area, here we have mapping/config/hql all with intellisense. In the screenshot the code completion for an Hql is shown. In future maybe I will be able to insert a T4 editor for the hbm2net templates.
  2. The project area: here we have a bounch of file that are representing our testing project: mapping, configurations, assemblies and so on. I have use the MSbuild object as a backend for the project, because in the near future I would like to use it to really build some artifacts using hbm2net and db2hbm.
  3. Here is the SQL preview of the query in editing. Now the view is showing an error because the query is incomplete.
  4. The funny log, a graphical appender for log4net :-)

Some more words about the project itself: the testing environment is hosted in a separate appdomain, this will allow us to:

  • Modify the mapping runtime generating new version of the assembly
  • Testing production assemblies built with legacy nh versions ( well, not so legacy, starting from 2.xxx )

Let’s have another screenshot, showing a real SQL preview:

s7

Next step is to produce the query results in some sort of usable representation ( I need to push the data across two app domain ) so I would probably use some JSON serialization and then display the JSON raw data with some readable formatting.

You can see a little demo video here.

The project is not yet released, please treat it as a CTP ;) anyway, the svn repository is here:

https://faticalabshqled.svn.sourceforge.net/svnroot/faticalabshqled

Thursday, April 22, 2010 5:41:58 PM (GMT Daylight Time, UTC+01:00)  #    Comments [2] - Trackback
Code GEneration | HQL Intellisense | NHibernate | NHWorkBench

# Tuesday, April 20, 2010

Fabio Maulo sta proponendo una nuova astrategia di mapping per NHibernate: ConfORM. In pratica si tratta di una strategia code only, quindi nessun file di XML, ma tutto via codice con l’approccio fluent interface. Stranamente la community si è un po’ stupita di vedere un ennesima strategia, e tutti stanno lì a chiedersi il perchè. Bene, il perchè è che è una nuova possibilità di scelta, ed avere molte scelte è un plus degli ambienti open source. In un suo post Fabio disegna uno scenario completo della ricca rosa di partecipanti al problema mapping. Difatto ConfORM è l’unica API che sfonda completamente lo strato hbm, e va direttamente alla radice. Tutto questo si traduce immediatamente in un incremento di performance, ed in una migliore linearità progettuale: meno strati è meglio, anzi, meno strati inutili è meglio. Francamente, dopo una piccola esperienza acquisita nel problema mapping con la scrittura del tool NHModeller, ho deciso di tornare ed imparare la strategia HBM. Dopo un po’ non è così male, ma la prossima volta che faccio un progetto mio provo ad usarlo ( in azienda non se ne parla nemmeno: già il mapping XML è visto come una stregoneria, e c’è chi legifera che mappare le foreign Key come long sia una buona idea anzichè un antipattern ;-) ).

In conclusione: chissenefrega se un nuovo sistema sembra essere il duplicato di un altro:Vincerà il migliore, ed in ogni caso il migliore secondo gli utilizzatori :-)

Tuesday, April 20, 2010 7:22:46 PM (GMT Daylight Time, UTC+01:00)  #    Comments [6] - Trackback
ConfORM | NHibernate

# Tuesday, March 30, 2010

If anybody still uses HQL with NHibernate, it would probably find useful some editing with intellisense.

Using the SharpDevelop Text Editor, the ANTLR grammar from the NHibernate sources, and some hacking in the java Eclipse Plugin code, the results is this:

 

Ctrl+Space completion 1: entity alias

s1

Ctrl+Space completion 2: Entity names

s2

Dot completion: property completion

 

s3

 

Ctrl+Space completion: all keywords and functions

 

s4

Here you can find the related sourceforge project.

Tuesday, March 30, 2010 4:44:42 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
HQL Intellisense | NHibernate

# Saturday, October 17, 2009

Esiste, o meglio esisteva un tool di code generation, per creare automaticamente le classi partendo dai file di mapping di NHibernate. Questo tool (hbm2net, presente in NHContrib ) è stato un po' dimenticato, per cui ho deciso di provare a riesumarlo, e di ammodernarlo un po' dandogli la possibilità di utilizzare il Text Template Transformation Toolkit (T4). Ho previsto un template interno per la semplice generazione delle classi di mapping, ma potenzialmente è possibile generare con facilità qualsiasi altro codice provvedendo un template esterno, ad esempio mascherine di UI, layer WCF etc etc.

La versione attuale è una pre-alfa, serve solo a dare un'idea, e a vedere se ci sono delle dipendenze in deploy di difficile gestione, non è ben chiaro a me se T4 sia presente  in tutte le installazioni di Visual Studio.

Se volete provare il tool  potete scaricarlo da qui. Per utilizzare il templating T4 dovete utilizzare la seguente linea di comand:

hbm2net --config=t4config.xml *.hbm.xml

è importante utilizzare il config indicato, altrimenti hbm2net defaulta sul render di NVelocity.  Verra creata una cartella generated con i file sorgenti corrisondenti agli hbm.

Potete scaricare hbm2net da qui.

Per provare al volo è incluso nello zip anche un file di mapping simple1.hbm.xml.

Fatemi avere dei feedback!

 

Saturday, October 17, 2009 3:50:00 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
NHibernate

My Stack Overflow
Contacts

Send mail to the author(s) E-mail

Tags
profile for Felice Pollano at Stack Overflow, Q&A for professional and enthusiast programmers
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2019
Felice Pollano
Sign In
Statistics
Total Posts: 157
This Year: 0
This Month: 0
This Week: 0
Comments: 127
This blog visits
All Content © 2019, Felice Pollano
DasBlog theme 'Business' created by Christoph De Baene (delarou) and modified by Felice Pollano