Try fast search NHibernate

28 November 2008

NHibernate: CALIDAD no se logra por casualidad

En estos últimos meses he visto, más de una vez, una serie de lindos “dibujos”, con colores, que pretendían demostrar la dudosa calidad de NHibernate. Como, por mi edad, he superado, ya hace tiempo, la celosía sobre código producido, la primera vez que vi ese análisis me pareció interesante sobre todo para aplicarla a algún otro proyecto. Todas las otras veces (7 u 8 en 5 meses) que vi ese link, con comentarios tipo “good luck to NH team” o “NH team should fix this problem”, ya empezó a parecerme un poco pesado sobre todo porque se evidencia un “posible problema”, una y otra vez, sin ocuparse de mostrar una “posible solución”. Tengo toda la sensación que se está usando NHibernate como medio para publicitar un producto comercial en lugar de hacer una crítica para mejorar NHibernate; si hubiera otra intención veríamos, adjunto a la crítica, una propuesta de cambios (por este motivo no publico el link a ese producto).

Les quiero dar mi personal opinión sobre la calidad de NHibernate analizándolo desde otro punto de vista; los hechos.

“por peso”

NH2.0.0 tuvo más de un año de desarrollo con más de 100.000 líneas de código entre modificadas y nuevas respecto a la versión anterior. NH2.0.1 tuvo más de 20.000 downloads en menos de 2 meses. NH2.0.x tiene más de 1400 tests entre tests unitarios y behavior-tests. En el team nadie tiene permiso de crear aunque sea un branch-official donde se pueda romper un solo test.

NH2.1.0 es el nombre de la próxima versión (el actual trunk). Por suerte, o tal vez por confianza y calidad, NHibernate tiene bastantes usuarios del trunk. NH2.1.0 posee casi 1600 tests que operan sobre más de 1100 clases por un total de casi 180.000 líneas de código C#.

¿Qué resultado da todo esto?

A hoy 80 bugs (o posibles bugs) de varios niveles, y más de 100 issues entre pedidos de nuevas features y mejorías; que representa 80 sobre la cantidad de lo que hay atrás, y las funcionalidades que NH ofrece, se lo dejo calcular a ustedes.

No sé a cuanta gente les preocupa como está hecho NH por adentro (a parte que para juzgarlo hay que conocerlo bien) pero que hasta el trunk se pueda considerar estable no es poca cosa.

Conclusión

La CALIDAD no se logra por casualidad; la calidad de un software se logra con muchos tests, reglas férreas en el team, y manteniendo la conciencia y el anhelo de mejorarse todos los días.

27 November 2008

La encuesta se terminó

Después de un mes la encuesta “How you are testing your persistence” se terminó.

La muestra no es para nada significativa y por lo que veo de los estadísticos de Google, sobre visitas a este blog, queda evidente que son muy pocos lo que le interesa dar a conocer como trabaja.

Lo resultados fueron:

Unit tests of entity and it's mapping  16 (53%)

From DAO / Repository  13 (43%)

From Model  1 (3%)

From user Interface  0 (0%)

Por lo menos me puedo quedar tranquilo que nadie votó por “From user Interface”.

26 November 2008

Entities behavior injection

If you are working with NH you know that NH likes POCOs and you must have a default constructor without parameters. Starting from today that is the past.

The domain

image

The implementation of Invoice is:

public class Invoice : IInvoice
{
private readonly IInvoiceTotalCalculator calculator;

public Invoice(IInvoiceTotalCalculator calculator)
{
this.calculator = calculator;
Items = new List<InvoiceItem>();
}

#region IInvoice Members

public string Description { get; set; }
public decimal Tax { get; set; }
public IList<InvoiceItem> Items { get; set; }

public decimal Total
{
get { return calculator.GetTotal(this); }
}

public InvoiceItem AddItem(Product product, int quantity)
{
var result = new InvoiceItem(product, quantity);
Items.Add(result);
return result;
}

#endregion
}

Are you observing something strange ?


  • There is not a property for the Id
  • There is not a default constructor without parameter

The Invoice entity are using an injectable behavior to calculate the total amount of the invoice; the implementation is not so important…

public class SumAndTaxTotalCalculator : IInvoiceTotalCalculator
{
#region Implementation of IInvoiceTotalCalculator

public decimal GetTotal(IInvoice invoice)
{
decimal result = invoice.Tax;
foreach (InvoiceItem item in invoice.Items)
{
result += item.Product.Price * item.Quantity;
}
return result;
}

#endregion
}

The full mapping:

<class name="Invoice" proxy="IInvoice">
<
id type="guid">
<
generator class="guid"/>
</
id>
<
property name="Description"/>
<
property name="Tax"/>
<
list name="Items" cascade="all">
<
key column="InvoiceId"/>
<
list-index column="pos"/>
<
composite-element class="InvoiceItem">
<
many-to-one name="Product"/>
<
property name="Quantity"/>
</
composite-element>
</
list>
</
class>

<
class name="Product">
<
id name="Id" type="guid">
<
generator class="guid"/>
</
id>
<
property name="Description"/>
<
property name="Price"/>
</
class>

The Test

[Test]
public void CRUD()
{
Product p1;
Product p2;
using (ISession s = sessions.OpenSession())
{
using (ITransaction tx = s.BeginTransaction())
{
p1 = new Product {Description = "P1", Price = 10};
p2 = new Product {Description = "P2", Price = 20};
s.Save(p1);
s.Save(p2);
tx.Commit();
}
}

var invoice = DI.Container.Resolve<IInvoice>();
invoice.Tax = 1000;
invoice.AddItem(p1, 1);
invoice.AddItem(p2, 2);
Assert.That(invoice.Total, Is.EqualTo((decimal) (10 + 40 + 1000)));

object savedInvoice;
using (ISession s = sessions.OpenSession())
{
using (ITransaction tx = s.BeginTransaction())
{
savedInvoice = s.Save(invoice);
tx.Commit();
}
}

using (ISession s = sessions.OpenSession())
{
invoice = s.Get<Invoice>(savedInvoice);
Assert.That(invoice.Total, Is.EqualTo((decimal) (10 + 40 + 1000)));
}

using (ISession s = sessions.OpenSession())
{
invoice = (IInvoice) s.Load(typeof (Invoice), savedInvoice);
Assert.That(invoice.Total, Is.EqualTo((decimal) (10 + 40 + 1000)));
}

using (ISession s = sessions.OpenSession())
{
IList<IInvoice> l = s.CreateQuery("from Invoice").List<IInvoice>();
invoice = l[0];
Assert.That(invoice.Total, Is.EqualTo((decimal) (10 + 40 + 1000)));
}

using (ISession s = sessions.OpenSession())
{
using (ITransaction tx = s.BeginTransaction())
{
s.Delete("from Invoice");
s.Delete("from Product");
tx.Commit();
}
}
}

In the previous week I tried to pass the test without change NH’s code-base. The first result was that I found a bug in NH and probably in Hibernate3.2.6, the second result was that it is completely possible to use NH with “fat” entities, without default constructor and using an IoC framework to inject behavior to an entity. After that work I realize that some little “relax” are needed in NH-code-base (NH-1587,NH-1588,NH-1589).

How pass the test

A very simple solution, to use an IoC with NH, is write a custom implementation of IInterceptor and use the Instantiate method to create an entity instance using an IoC container. The problem with this solution is that you still need a default constructor and… well… you must use the same interceptor for all sessions.

Another possible solution, for NH2.1 (trunk), is the use of a custom <tuplizer> for EntityMode.POCO. Probably I will write another blog-post about it.

If you are using the ReflectionOptimizer (used by default) there is a simple short-cut: I can write a IBytecodeProvider implementation based on Castle.Windsor container. The BytecodeProvider is another injectable piece of NH, trough the NHibernate.Cfg.Environment, before create the configuration. The BytecodeProvider has two responsibility: provide the ProxyFactoryFactory and provide the ReflectionOptimizer.

public class BytecodeProvider : IBytecodeProvider
{
private readonly IWindsorContainer container;

public BytecodeProvider(IWindsorContainer container)
{
this.container = container;
}

#region IBytecodeProvider Members

public IReflectionOptimizer GetReflectionOptimizer(Type clazz, IGetter[] getters, ISetter[] setters)
{
return new ReflectionOptimizer(container, clazz, getters, setters);
}

public IProxyFactoryFactory ProxyFactoryFactory
{
get { return new ProxyFactoryFactory(); }
}

#endregion
}
In this case, obviously, the ProxyFactoryFactory class is NHibernate.ByteCode.Castle.ProxyFactoryFactory.

Now the ReflectionOptimizer (using the fresh NH’s trunk):

public class ReflectionOptimizer : NHibernate.Bytecode.Lightweight.ReflectionOptimizer
{
private readonly IWindsorContainer container;

public ReflectionOptimizer(IWindsorContainer container, Type mappedType, IGetter[] getters, ISetter[] setters)
: base(mappedType, getters, setters)
{
this.container = container;
}

public override object CreateInstance()
{
if (container.Kernel.HasComponent(mappedType))
{
return container.Resolve(mappedType);
}
else
{
return container.Kernel.HasComponent(mappedType.FullName)
? container.Resolve(mappedType.FullName)
: base.CreateInstance();
}
}

protected override void ThrowExceptionForNoDefaultCtor(Type type)
{
}
}

As last, a quick view to the configuration:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<
session-factory name="EntitiesWithDI">
<
property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<
property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<
property name="connection.connection_string">
Data Source=localhost\SQLEXPRESS;Initial Catalog=BlogSpot;Integrated Security=True
</property>
</
session-factory>
</
hibernate-configuration>

As you can see the configuration is minimal and, in this case, I don’t need to configure the “proxyfactory.factory_class” property because I will inject the whole BytecodeProvider.

[TestFixtureSetUp]
public void TestFixtureSetUp()
{
ConfigureWindsorContainer();
Environment.BytecodeProvider = new BytecodeProvider(container);
cfg = new Configuration();
cfg.AddAssembly("EntitiesWithDI");
cfg.Configure();
cfg.Interceptor = new WindsorInterceptor(container);
new SchemaExport(cfg).Create(false, true);
sessions = (ISessionFactoryImplementor) cfg.BuildSessionFactory();
}

The BytecodeProvider injection is the line after the configuration of Windsor container.

The configuration of the container is very simple:

protected override void ConfigureWindsorContainer()
{
container.AddComponent<IInvoiceTotalCalculator, SumAndTaxTotalCalculator>();
container.AddComponentLifeStyle(typeof (Invoice).FullName,
typeof (IInvoice), typeof (Invoice), LifestyleType.Transient);
}

Conclusions


  • The default ctor without parameter constraint was removed.
  • Use an IoC to inject behavior to an entity is possible and easy.

NOTE: Even if is possible to write an entity without the Id, the feature is not fully supported.

Code available here.



kick it on DotNetKicks.com

13 November 2008

NH with Multiple Assembly versions in GAC

I know that the title have something strange because NH don’t have nothing to do with GAC, but in NH-JIRA we have an issue with this title: “NHibernate does not support multiple versions of the same assembly for entity mapping”

Perhaps I don’t understand what that issue mean; I can’t understand what mean work we two mappings versions of the same entity… mean it work with two tables versions in the same DB at the same time ?

If the problem is not the mapping but the class implementation, NH are using, this is the solution.

Domain version 1.0.0.0

public class Person
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }

public virtual string FullName
{
get { return string.Concat(FirstName, "-", LastName); }
}
}

The mapping is:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Company.Domain"
namespace="Company.Domain">
<
class name="Person">
<
id name="Id">
<
generator class="native"/>
</
id>
<
property name="FirstName"/>
<
property name="LastName"/>
</
class>
</
hibernate-mapping>

Note: In the mapping I don’t write anything about the version of the assembly.

Sign, and compile the domain, and add it to the GAC.

GACAdded

Domain version 1.1.0.0

public class Person
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }

public virtual string FullName
{
get { return string.Concat(LastName, "*", FirstName); }
}
}

As you can see there is a different implementation of the property FullName.

Compile and add the new version to the GAC.

GACAddedBoth

The Test

Without make a completely automated test…

[Test]
public void SomebodySaidIsABug()
{
var version = typeof (Person).Assembly.GetName().Version.ToString();
Console.WriteLine("Running version {0}", version);

object savedId;
using (ISession s = sessions.OpenSession())
using (ITransaction tx = s.BeginTransaction())
{
var a = new Person {FirstName = "Pasqual", LastName = "Angulo"};
savedId = s.Save(a);
tx.Commit();
}

using (ISession s = sessions.OpenSession())
using (ITransaction tx = s.BeginTransaction())
{
var a = s.Get<Person>(savedId);
if(version == "1.0.0.0")
Assert.That(a.FullName, Is.EqualTo("Pasqual-Angulo"));
else
Assert.That(a.FullName, Is.EqualTo("Angulo*Pasqual"));
tx.Commit();
}

using (ISession s = sessions.OpenSession())
using (ITransaction tx = s.BeginTransaction())
{
s.Delete("from Person");
tx.Commit();
}
}

I’m showing the loaded version used in tests (that, in this, case represent the assembly with the responsibility of DataAccess) and changing the expected value of FullName according with the assembly version.

The assembly referenced by the test project, at design time, is not important.

The App.config of the test is:

<configSections>
<
section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/>
</
configSections>

<
hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<
session-factory name="Company">
<
property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<
property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<
property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<
property name="connection.connection_string">
Data Source=localhost\SQLEXPRESS;Initial Catalog=BlogSpot;Integrated Security=True
</property>
<
property name="show_sql">false</property>
<
property name="use_outer_join">true</property>
<
property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
<
property name="command_timeout">60</property>
<
property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>

<
mapping assembly="Company.Domain"/>

</
session-factory>
</
hibernate-configuration>
I’m loading the assembly with all mappings, trough the session-factory configuration, another time without specify the assembly strong name.
Compile the test project and close VisualStudio. Now go to the output folder (bin/Debug) and delete the Company.Domain.dll file. Then open NUnit.Gui.
The situation at this point is:
  • Two version of Company.Domain.dll in the GAC.
  • Visual Studio Closed
  • The test compiled and the Company.Domain.dll removed
  • NUnit.GUI opened
Because I'm not using the strongly name of Company.Domain.dll, running the test what I expect is that it fail
NunitNoOk0  
Perfect!! Assembly not found. Now I can start to play with the compiled configuration file (DiffAssemblyVersion.dll.config) using the NotePad.
    <runtime>
<
assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" applies-to="v2.0.50727">
<
qualifyAssembly partialName="Company.Domain"
fullName="Company.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5c41dce254553643" />
</
assemblyBinding>
</
runtime>

Now, if I reload the assembly in NUnit.GUI, the assembly will be loaded from GAC.

NunitOk10

Now I want run the test but using the 1.1.0.0 version. Using NotePad again the new config is:

<runtime>
<
assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" applies-to="v2.0.50727">
<
qualifyAssembly partialName="Company.Domain"
fullName="Company.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5c41dce254553643" />
<
dependentAssembly>
<
assemblyIdentity name="Company.Domain"
publicKeyToken="5c41dce254553643"/>
<
bindingRedirect oldVersion="1.0.0.0"
newVersion="1.1.0.0"/>
</
dependentAssembly>
</
assemblyBinding>
</
runtime>

Reload the assembly in NUnit.GUI and re-run it:

NunitOk11

Work done.

Code available here.

Technorati Tags:

12 November 2008

Mapping Source: How map a class without use XML

I don’t know how many times you heard a mad man talking about “mapping source” in NHibernate…

Map a class in NH without use XML at all ? only a crazy man can say that.

Hello! I’m that mad man.

As usual an entity implementation:

public class Animal
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
}

Is a simple class because map something else, and begin a new framework, is not the target of this post.

Now an empty method, to write an integration test, of a new class inherited from the NHibernate configuration class:

public class Configuration : NHibernate.Cfg.Configuration
{
public void Register(Type entity){}
}

The integration test, basically, include the SchemaExport, a CRUD and an HQL; what is interesting here is only the setup of the test:

protected Configuration cfg;
protected ISessionFactoryImplementor sessions;

[TestFixtureSetUp]
public void TestFixtureSetUp()
{
cfg = new Configuration();
cfg.Configure();
cfg.Register(typeof(Animal));
new SchemaExport(cfg).Create(false, true);
sessions = (ISessionFactoryImplementor)cfg.BuildSessionFactory();
}

As you can see is similar to a common setup except for cfg.Register(typeof(Animal)). The others parts of the test are available from the download link.

Now I can start the dance…

In NHibernate all classes metadata are completely decoupled from XML; this mean that SchemaExport, and everything else in NH, absolutely don’t need XML files to be used. What I must do is inject everything after call the method cfg.Configure(). The place where all metadata are available is the namespace NHibernate.Mapping. The holder of metadata is the class Configuration trough the class NHibernate.Cfg.Mappings. The provider of an instance of NHibernate.Cfg.Mappings is the Configuration itself trough the method:

/// <summary>
///
Create a new <see cref="Mappings" /> to add classes and collection
/// mappings to.
/// </summary>
public Mappings CreateMappings(Dialect.Dialect dialect)

That method stay there from loooong time ago.

As we are doing in NH-Core each “binder” must use at least two classes (to create new metadata):

  1. an instance of NHibernate.Cfg.Mappings
  2. the instance of the configured Dialect

The configuration extension

public class Configuration : NHibernate.Cfg.Configuration
{
public void Register(Type entity)
{
Dialect dialect = Dialect.GetDialect(Properties);
Mappings mappings = CreateMappings(dialect);
SetDefaultMappingsProperties(mappings);
new EntityMapper(mappings, dialect).Bind(entity);
}

private static void SetDefaultMappingsProperties(Mappings mappings)
{
mappings.SchemaName = null;
mappings.DefaultCascade = "none";
mappings.DefaultAccess = "property";
mappings.DefaultLazy = true;
mappings.IsAutoImport = true;
mappings.DefaultNamespace = null;
mappings.DefaultAssembly = null;
}
}

For each class, I’m going to register, I’m getting the configured dialect and a new instance of Mappings class. Then I’m setting some default values and at the end I’m biding the entity type (EntityMapper(mappings, dialect).Bind(entity)).

The EntityMapper

Without boring you, with the full code, the heart is here

public void Bind(Type entity)
{
var rootClass = new RootClass();
BindClass(entity, rootClass);
}

private void BindClass(Type entity, PersistentClass pclass)
{
pclass.IsLazy = true;
pclass.EntityName = entity.FullName;
pclass.ClassName = entity.AssemblyQualifiedName;
pclass.ProxyInterfaceName = entity.AssemblyQualifiedName;
string tableName = GetClassTableName(pclass);
Table table = mappings.AddTable(null, null, tableName, null, pclass.IsAbstract.GetValueOrDefault());
((ITableOwner) pclass).Table = table;
pclass.IsMutable = true;
PropertyInfo[] propInfos = entity.GetProperties();

PropertyInfo toExclude = new IdBinder(this, propInfos).Bind(pclass, table);

pclass.CreatePrimaryKey(dialect);
BindProperties(pclass, propInfos.Where(x => x != toExclude));
mappings.AddClass(pclass);

string qualifiedName = pclass.MappedClass == null ? pclass.EntityName : pclass.MappedClass.AssemblyQualifiedName;
mappings.AddImport(qualifiedName, pclass.EntityName);
if (mappings.IsAutoImport && pclass.EntityName.IndexOf('.') > 0)
{
mappings.AddImport(qualifiedName, StringHelper.Unqualify(pclass.EntityName));
}
}

Including everything the EntityMapper.cs have 198 lines.

Metadata classes used are: RootClass, PersistentClass, Table, SimpleValue, Property and Column.

Conclusions

To use NHibernate without write a single XML mapping, is possible. Create mapped classes or others artifacts (as typedef, database-objects, named-queries, stored-procedures, filters and so on) without use XML, is possible. Because I’m extending the NHibernate.Cfg.Configuration, add some other artifacts or override mapping written using XML is possible. Write a “bridge” using EntityFramework attributes instead XMLs, is possible.

Write a new framework avoiding XML at all, is possible that is completely different than “is easy”. In general a framework is to make something easy to the framework users and not to the framework developers.

Code available here.


Technorati Tags: ,

07 November 2008

La concorrenza é l’anima del commercio

Que linda frase es esa: La competencia es el alma del comercio.

Hace un tiempo largo en NHibernate implementé IProxyFactoryFactory (porting, con adaptación, desde Hibernate). Como me está pasando, últimamente, nadie entendía para que “esa cosa” es necesaria, hasta que un día un committer me escribe en privado diciéndome: “al final hay alguien que entiende para que hiciste ese trabajo” y me engancha un link. El link llevaba al blog de William C. Pierce que usó IProxyFactoryFactory junto a un tool para generar proxies que pudieran trabajar en médium-trust. Junto con la movida que hicimos para dar a la luz NH-Forge, Bill Pierce, no solo aceptó entrar en NHibernate-Contrib si no que aceptó “rever” su trabajo y cambiarle de nombre a NHibernate.ProxyGenerators (notar el plural). Hasta ese entonces el plural era, lamentablemente, solo una mera intención…

Un día, de la nada, aparece un branch en NH-Core que llevaba todo el código de NH y una implementación de un ProxyFactoryFactory para usarlo con PostSharp (come venia pensándolo hace un tiempo); el problema del branch es que todavía estaba todo incrustado a parte que no pasa la mayoría de los tests de NH. Quédense en este día porque fue el disparador…

En todo este periodo había unos asuntos que me estaban molestando no poco:

  • Pocos son lo que entienden que el hecho de declarar métodos “virtual” no es una invasión de NH.
  • NH parecía no funcionar si no existiera Castle.
  • Quien usa Spring igualmente se tiene que llevar Castle.
  • En Castle no quieren liberar release y quieren que todos trabajemos con el trunk.
  • NH tenía una dependencia a algo sin versiones.
  • La gente que usaba NH con Castle Windsor y/o ActiveRecord se volvía loca al intentar mantener todos los proyectos sincronizado.

De buen tano calentón que soy (tano=Italiano en Buenos Aires) ese día me cansé y decidí dedicar el día a “separar los tantos” y la mañana siguiente pude anunciar la anhelada separación de dependencia desde Castle.DynamicProxy2 versión x.y.z (cualquiera porque no tiene, nunca fue liberado desde casi dos años).

Bueno… lo único que faltaba era solo empezar a generar alternativas… Den una vista a este link, al fondo, y fíjense cuan larga fue esta historia; falta muy poco para que cumpla un año. Hoy puedo anunciar la primera alternativa a Castle.DynamicProxy2 en NHibernate. Nació NHibernate.ProxyGenerators.LinFuDynamicProxy.

LinFuDynamicProxy será, probablemente, la alternativa que usarán quien no usa ningún IoC-FW en su proyecto ya que es una sola DLL, pesa 23KB (para NET3.5) y tiene una leve mejora en performance respecto a Castle (no estoy completamente seguro pero me pareció midiendo los tiempos de ejecución de los tests de NH).

Para quien usamos IoC falta un paso; ¿no se imaginan cual? No sé exactamente para cuando, pero espero pronto sea disponible NHibernate.ProxyGenerators.SpringDynamicProxy.

¡A ver si se logra trabajar con algo que tenga versión! ;-)

 Technorati Tags: ,,,,