Try fast search NHibernate

14 October 2008

Less than “Few” is GoF

Some years ago a phrase broke my mind:

“Program to an interface and not to an implementation”

As you probably know the phrase is one of the famous Gang of Four. Is the  phrase valid even for entities ?

I definitive think that the answer is yes for various reason; from dyn-proxy, for lazy-loading, to the use of DTO in View.

If we want use GoF suggestion, do we must write a lot of “artifacts” ?

Let me make an example… (fasten your seat belts)

First of all I start defining a simple entity:

public interface IPerson
{
int Id { get; set; }
string Name { get; set; }
}

Now I want persist it using NHibernate2.1.0 (the next mayor version) but without write a real implementation. What I want is write a generic implementation for the whole domain; to do it I’m going to use Castle.DynamicProxy. The responsibility of my proxy will be hold entity-data so basically I need  two things: the DataProxy and a “proxy marker” to discover if an object-instance is an instance of my proxy.

public interface IProxyMarker
{
DataProxyHandler DataHandler { get; }
}

public sealed class DataProxyHandler : IInterceptor
{
private readonly Dictionary<string, object> data = new Dictionary<string, object>(50);
private readonly string entityName;

public DataProxyHandler(string entityName, object id)
{
this.entityName = entityName;
data["Id"] = id;
}

public string EntityName
{
get { return entityName; }
}

public IDictionary<string, object> Data
{
get { return data; }
}

#region IInterceptor Members

public void Intercept(IInvocation invocation)
{
invocation.ReturnValue = null;
string methodName = invocation.Method.Name;
if ("get_DataHandler".Equals(methodName))
{
invocation.ReturnValue = this;
}
else if (methodName.StartsWith("set_"))
{
string propertyName = methodName.Substring(4);
data[propertyName] = invocation.Arguments[0];
}
else if (methodName.StartsWith("get_"))
{
string propertyName = methodName.Substring(4);
object value;
data.TryGetValue(propertyName, out value);
invocation.ReturnValue = value;
}
else if ("ToString".Equals(methodName))
{
invocation.ReturnValue = entityName + "#" + data["Id"];
}
else if ("GetHashCode".Equals(methodName))
{
invocation.ReturnValue = GetHashCode();
}
}

#endregion
}

Note: the IInterceptor is Castle.Core.Interceptor.IInterceptor.


Now I want persist the IPerson but before write the mapping I need some more artifacts related to some new features of NH2.1. I’m going to use Tuplizers.


The IInstantiator implementation:

public class EntityInstantiator : IInstantiator
{
private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
private readonly Type t;

public EntityInstantiator(Type entityType)
{
t = entityType;
}

public object Instantiate(object id)
{
return
proxyGenerator.CreateInterfaceProxyWithoutTarget(t, new[] {typeof (IProxyMarker), t},
new DataProxyHandler(t.FullName, id));
}

public object Instantiate()
{
return Instantiate(null);
}

public bool IsInstance(object obj)
{
try
{
return t.IsInstanceOfType(obj);
}
catch (Exception e)
{
throw new Exception("could not get handle to entity-name as interface : " + e);
}
}
}

Note: the ProxyGenerator is Castle.DynamicProxy.ProxyGenerator.


The override of PocoEntityTuplizer:

public class EntityTuplizer : PocoEntityTuplizer
{
public EntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity)
: base(entityMetamodel, mappedEntity) {}

protected override IInstantiator BuildInstantiator(PersistentClass persistentClass)
{
return new EntityInstantiator(persistentClass.MappedClass);
}
}

Has you know when we create a proxy the System.Type will take a value that nobody can predict; to allow NH recognize my entity I use a simple NHibernate.IInterceptor implementation:

public class EntityNameInterceptor : EmptyInterceptor
{
public override string GetEntityName(object entity)
{
string entityName = ExtractEntityName(entity) ?? base.GetEntityName(entity);
return entityName;
}

private static string ExtractEntityName(object entity)
{
// Our custom Proxy instances actually bundle
// their appropriate entity name, so we simply extract it from there
// if this represents one of our proxies; otherwise, we return null
var pm = entity as IProxyMarker;
if (pm != null)
{
var myHandler = pm.DataHandler;
return myHandler.EntityName;
}
return null;
}
}

Because I will need to have a instance of “something” in my application I need a sort of EntityFactory. Here a quick implementation:

public class EntityFactory
{
private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();

public T NewEntity<T>()
{
Type t = typeof (T);
return
(T)
proxyGenerator.CreateInterfaceProxyWithoutTarget(t, new[] {typeof (IProxyMarker), t},
new DataProxyHandler(t.FullName, 0));
}
}

Before write a persistence test I need the last little thing: the mapping.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="LessThanFew"
namespace="LessThanFew.Domain">

<
class name="IPerson">
<
tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
discriminator type="string" force="false"/>
<
property name="Name"/>
</class>
</hibernate-mapping>

Now I can write my test…

[Test]
public void PersonCrud()
{
object savedId;
var person = entityFactory.NewEntity<IPerson>();
person.Name = "Katsumoto";

using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
savedId = session.Save(person);
tx.Commit();
}

using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
person = session.Get<IPerson>(savedId);
Assert.That(person, Is.Not.Null);
Assert.That(person.Name, Is.EqualTo("Katsumoto"));
session.Delete(person);
tx.Commit();
}

using (var session = sessions.OpenSession())
using (var tx = session.BeginTransaction())
{
person = session.Get<IPerson>(savedId);
Assert.That(person, Is.Null);
tx.Commit();
}
}

The test work but… to persist my little entity I write 6 more classes than usual… yes, is true, but now I have an AOP approach of my application even for entities. Ok let me complicate the domain a little bit:

public interface IPerson
{
int Id { get; set; }
string Name { get; set; }
IAddress Address { get; set; }
ISet<IPerson> Family { get; set; }
}

public interface ICustomer : IPerson
{
ICompany Company { get; set; }
}

public interface ICompany
{
int Id { get; set; }
string Name { get; set; }
}

public interface IAddress
{
int Id { get; set; }
string Street { get; set; }
string City { get; set; }
string PostalCode { get; set; }
}

and the new “matrix”:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="LessThanFew"
namespace="LessThanFew.Domain">

<
class name="IPerson" discriminator-value="person" abstract="false">
<
tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
discriminator type="string" force="false"/>
<
property name="Name"/>

<
many-to-one name="Address" cascade="all" column="addr_id"/>

<
set name="Family" cascade="all" generic="true">
<
key column="pers_id"/>
<
one-to-many class="IPerson"/>
</
set>

<
subclass name="ICustomer" discriminator-value="customer" abstract="false">
<
tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<
many-to-one name="Company" cascade="none" column="comp_id"/>
</
subclass>
</
class>

<
class name="ICompany" abstract="false">
<
tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
property name="Name"/>
</
class>

<
class name="IAddress" abstract="false">
<
tuplizer class="LessThanFew.EntityTuplizer, LessThanFew" entity-mode="poco"/>
<
id name="Id">
<
generator class="hilo"/>
</
id>
<
property name="Street"/>
<
property name="City"/>
<
property name="PostalCode"/>
</
class>
</
hibernate-mapping>

To persist this new domain I don’t need to write nothing more than what you are seeing in the last two snippets and if I need INotifyPropertyChanged for all entities, of my new domain, what I need to change is the implementation of DataProxyHandler, by the way the intention of this post is only to show another NH2.1 feature : Tuplizers.


The code, with the “full cream” test, is available here.


The last little thing… the Italian and Spanish translation of “FEW” is POCO.


2 comments:

  1. That's something that i have not thought of even in my dreams!

    p.s. and looks very promising for some obscure scenario that i have in my mind!

    ReplyDelete
  2. how about if Person implement 2 interface?..Any suggestion how to solve this problem

    ReplyDelete