Try fast search NHibernate

21 October 2008

The power of ORM

In the last two years we saw many developers becoming from “DataSet world” to “ORM world” (thanks to Microsoft and it’s EntityFramework).

One of the most important challenge of ORM is the abstraction of your domain (entity classes of your business) from its persistent representation (tables in the DB).

To understand how NHibernate solve the ORM challenge I want start from an existing domain. The domain of this post:

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

public class Reptile: Animal
{
public virtual float BodyTemperature { get; set; }
}

public class Human : Animal
{
public virtual string Name { get; set; }
public virtual string NickName { get; set; }
public virtual DateTime Birthdate { get; set; }
}

public class Family<T> where T: Animal
{
public virtual int Id { get; private set; }
public virtual T Father { get; set; }
public virtual T Mother { get; set; }
public virtual ISet<T> Childs { get; set; }
}

The mapping of the previous post represent the domain in five tables:


OldDomain
 

Without touch my previous domain, and my previous test, I want represent it in two tables:


NewDomain
The new mapping is:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="EntityNameInAction"
namespace="EntityNameInAction"
default-access="backfield">

<
class name="Animal" abstract="true">
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
discriminator column="kind"/>
<
property name="Description"/>

<
subclass name="Reptile">
<
property name="BodyTemperature"/>
</
subclass>

<
subclass name="Human">
<
property name="Name"/>
<
property name="NickName"/>
<
property name="Birthdate" type="Date"/>
</
subclass>
</
class>

<
class name="Family`1[[Reptile]]" entity-name="ReptilesFamily"
table="Families" discriminator-value="Reptile" where="familyKind = 'Reptile'">
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
discriminator column="familyKind"/>
<
many-to-one name="Father" class="Reptile" cascade="all"/>
<
many-to-one name="Mother" class="Reptile" cascade="all"/>
<
set name="Childs" generic="true" cascade="all">
<
key column="familyId"/>
<
one-to-many class="Reptile"/>
</
set>
</
class>

<
class name="Family`1[[Human]]" entity-name="HumanFamily"
table="Families" discriminator-value="Human" where="familyKind = 'Human'">
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
discriminator column="familyKind"/>
<
many-to-one name="Father" class="Human" cascade="all"/>
<
many-to-one name="Mother" class="Human" cascade="all"/>
<
set name="Childs" generic="true" cascade="all">
<
key column="familyId"/>
<
one-to-many class="Human"/>
</
set>
</
class>
</
hibernate-mapping>
The difference in the Animal hierarchy mapping is trivial and well known by NH users. What is more interesting is the work to persist Family<T> classes. Because I’m using the same table (Families) I need something to discriminate the tow concrete types Family<Reptile> and Family<Human>. In this case I can’t say that Family<Reptile> is a subclass of something else (in my domain I don’t have a superclass for Family<T>) by the way I can use a simple trick. The use of <discriminator>, discriminator-value and where tags is the trick.
Goal achived by NH: same domain, same test, two different persistent representations.
Code available here.

18 October 2008

El placer de un Bug Fix

Después de tantas líneas de código escritas en NHibernate (según las estadísticas de ohloh van más de 130000 solo en el core de NH) aún encuentro placer en arreglar bugs.

El placer se incrementa cuando, rastreando un bug, me encuentro con comentarios bastante detallados en puntos críticos donde, por varios motivos, había tomado una determinada decisión sobre la implementación de una funcionalidad de Hibernate3.2. En el caso de NHibernate, que es un porting, estas cosas son las que denomino: licencias poéticas. La misma definición la uso cuando alguien toma un patrón y lo implementa como le parece.

En NH encontré y arreglé unas cuantas licencias poéticas y, despacito, despacito, me permití también tomarme mis licencias poéticas (hay veces que el TDD te lleva a eso).

Realmente es un placer cuando aparece un bug que me lleva a arreglar una licencia poética y más aún si también me permite transformar un TODO en DONE… ni hablar si eso arregla dos bugs de un solo fix.

17 October 2008

How Test your mappings: the Ghostbuster

In NHibernate, when you have the FulshMode configured to AutoFlush, a session.Flush() is thrown when NH detect a dirty entity instance and you are querying an intersected QuerySpace (the QuerySpace is represented by all tables affected in a query).

Example:

    <class name="Animal">
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
property name="Description"/>

<
joined-subclass name="Reptile">
<
key column="animalId"/>
<
property name="BodyTemperature"/>
</
joined-subclass>

</
class>

If you have the above domain, and you are going to query the Reptile class in a opened session with a dirty instance of Animal, a session.Flush() will be thrown.

After a session.Get<Animal>(anId) we can be pretty sure that there is no dirty entities in the session, sure ?

Don’t be so sure! The real answer is: It depend.

For example try this domain:

public enum Sex
{
Unspecified,
Male,
Female
}
public class Person
{
public virtual int Id { get; set; }
public virtual Sex Sex { get; set; }
}

with this mapping:

    <class name="Person">
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
property name="Sex" type="int"/>
</
class>

In the mapping I define the property Sex of type int but in the class the type is Sex; even if you don’t receive an exception, because an int is convertible to Sex and viceversa, your persistence will have a unexpected  behavior. NH will detect a modification, of your entity, “immediately” after session.Get because it having an int in the entity snap-shot (retrieved from DB) and a Sex in the actual state. The example are showing a very simple case of “ghosts” in your application. In a big environment, with a complex domain, find “ghosts” it is not so easy.

The Ghostbusters

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

Few words about the TestFixtureSetUp:


  • if you are testing your domain persistence you can run the “ghostbuster” in each test.
  • if you are testing yours DAOs and you have an implementation of ObjectMother or TestDataBuilder you can use it in the implementation of PopulateDb() method.
  • If you don’t have tests you can leave the PopulateDb() method empty and configure NH to an existing copy of your DB.

[Test, Explicit]
public void UnexpectedUpdateDeleteOnFetch()
{
PersistingMappings(null);
}

[Test, Explicit]
public void UnexpectedUpdateDeleteOnFetchSpecific()
{
var entitiesFilter = new[]
{
"Person"
};
PersistingMappings(entitiesFilter);
}

In my experience the above two tests are needed. The first sound like “close your eyes and pray” the second allow you to analyze some specific entities.

To don’t break the test, on each unexpected DB-hit, I’ll use the power of log4net in the whole fixture.

To intercept unexpected Flush a possible, easy and quickly, way is an implementation of IInterceptor.

private class NoUpdateInterceptor : EmptyInterceptor
{
private readonly IList<string> invalidUpdates;

public NoUpdateInterceptor(IList<string> invalidUpdates)
{
this.invalidUpdates = invalidUpdates;
}

public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)
{
string msg = " FlushDirty :" + entity.GetType().FullName;
log.Debug(msg);
invalidUpdates.Add(msg);
return false;
}

public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
{
string msg = " Save :" + entity.GetType().FullName;
log.Debug(msg);
invalidUpdates.Add(msg);
return false;
}

public override void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types)
{
string msg = " Delete :" + entity.GetType().FullName;
log.Debug(msg);
invalidUpdates.Add(msg);
}
}

As you can see I’m interested in : unexpected Flush of dirty instance, unexpected Saves and unexpected Deletes.

The PersistingMappings is my “driver” to test each entity. The responsibility of the method is iterate each persistent class known by the SessionFactory (or the selected in UnexpectedUpdateDeleteOnFetchSpecific methods), run the test of each entity and reports all issues found.

private void PersistingMappings(ICollection<string> entitiesFilter)
{
var invalidUpdates = new List<string>();
var nop = new NoUpdateInterceptor(invalidUpdates);

IEnumerable<string> entitiesToCheck;
if (entitiesFilter == null)
{
entitiesToCheck = cfg.ClassMappings.Select(x => x.EntityName);
}
else
{
entitiesToCheck = from persistentClass in cfg.ClassMappings
where entitiesFilter.Contains(persistentClass.EntityName)
select persistentClass.EntityName;
}

foreach (var entityName in entitiesToCheck)
{
EntityPersistenceTest(invalidUpdates, entityName, nop);
}

if (invalidUpdates.Count > 0)
{
if (logError.IsDebugEnabled)
{
logError.Debug(" ");
logError.Debug("------ INVALID UPDATES -------");
invalidUpdates.ForEach(x => logError.Debug(x));
logError.Debug("------------------------------");
}
}
Assert.AreEqual(0, invalidUpdates.Count, "Has unexpected updates.");
}

To check each persistent entity I’m using the Configuration.ClassMappings collection and extracting the EntityName from the PersistentClass. The use of EntityName don’t mean that I’m using the tag entity-name (as you can see in the mapping above).

The real “ghostbuster” is:

private void EntityPersistenceTest(ICollection<string> invalidUpdates,
string entityName, IInterceptor nop)
{
const string queryTemplate = "select e.{0} from {1} e";
string msg = "s--------" + entityName;
log.Debug(msg);

using (var s = sessions.OpenSession(nop))
using (var tx = s.BeginTransaction())
{
IList entityIds = null;
try
{
string queryString = string.Format(queryTemplate, DefaultIdName, entityName);
entityIds = s.CreateQuery(queryString).SetMaxResults(1).List();
}
catch (Exception e)
{
log.Debug("Possible METEORITE:" + e.Message);
}

if (entityIds != null)
{
if (entityIds.Count == 0 || entityIds[0] == null)
{
log.Debug("No instances");
}
else
{
if (entityIds.Count > 1)
{
msg = ">Has " + entityIds.Count + " subclasses";
log.Debug(msg);
}
object entityId = entityIds[0];
try
{
s.Get(entityName, entityId);
try
{
s.Flush();
}
catch (Exception ex)
{
string emsg = string.Format("EXCEPTION - Flushing entity [#{0}]: {1}", entityId, ex.Message);
log.Debug(emsg);
invalidUpdates.Add(emsg);
}
}
catch (Exception ex)
{
string emsg = string.Format("EXCEPTION - Getting [#{0}]: {1}", entityId, ex.Message);
invalidUpdates.Add(emsg);
log.Debug(emsg);
}
}
tx.Rollback();
}
}
msg = "e--------" + entityName;
log.Debug(msg);
}

The core of the test is:

s.Get(entityName, entityId);
s.Flush();

If I Get an entity, from a clear fresh session, without touch the state what I’m expect is that the follow Flush don’t  make absolutely nothing but… you know… perhaps there is an ugly “ghost”. Each try-catch are checking some special situation.

And now lets go to run the “ghostbuster” in your application. Code available here.


Technorati Tags: ,,