NHibernate2.1新特性之Tuplizers
Tuplizers?這個單詞在英文字典里沒有解釋,和元組(tuple)這個單詞有點相似,在NHibernate中應該翻譯為元組片斷,Tuplizers只在映射中提供,所以叫元組片段映射比較合適。
我們平時一般使用Domain Entity,然后使用< class>來映射,對Domain Entity操作。在NHibernate中,對于Domain Entity的Entity Mode為POCO類型,這時對應的tuplizer知道通過其構造方法來創(chuàng)建一個POCO,再通過其屬性訪問器來訪問POCO屬性。
Tuplizers,其完整命名空間是NHibernate.Tuple.Tuplizer,它就是根據(jù)給定的NHibernate.EntityMode,來復現(xiàn)片斷數(shù)據(jù)。如果給定的片斷數(shù)據(jù)被認為其是一種數(shù)據(jù)結構,"tuplizer"就是一個知道如何創(chuàng)建這樣的數(shù)據(jù)結構,以及如何給這個數(shù)據(jù)結構賦值的東西。
在NHibernate中有NHibernate.Tuple.Entity.IEntityTuplizer和NHibernate.Tuple.Component.IComponentTuplizer兩個接口,IEntityTuplizer負責管理上面提到的實體的契約,而IComponentTuplizer則是針對組件的。
下面從NHibernate源碼中摘取一個典型的例子來說明Tuplizer的用法。
典型實例
我想映射一個接口,對這個接口按照POCO實體模型進行持久化操作。首先想到應該可以New出來這個接口,使用工廠可以產(chǎn)生出來。在初始化這個接口的時候要重寫一些NHibernate默認的POCO行為,在對這個接口賦值的時候攔截一些操作,記錄下這個接口。獲取接口時候同樣也需要攔截。
1.Domain
- public interface IUser
- {
- int Id { get; set; }
- string Name { get; set; }
- }
我們需要映射這個接口,但是NHibernate只會去映射類,我們怎么去改寫代碼讓NHibernate可以像類那樣去映射接口呢?這就是Tuplizers的功能。
2.代理標記proxy marker
由于這里是特殊需要,我對其這個代理做個標記,如果某個實體可以轉換為這個代理標記接口就說明是我重寫定義的Domain,
- /// < summary>
- /// 代理標記
- /// 對象實例是代理的一個實例
- /// < /summary>
- public interface IProxyMarker
- {
- DataProxyHandler DataHandler { get; }
- }
3.DataProxy
利用Castle的攔截器IInterceptor接口對這個代理數(shù)據(jù)進行攔截,例如在獲取這個代理數(shù)據(jù)的時候,讓NHibernate按照POCO那樣去把其數(shù)據(jù)保存到一個字典中。
- /// < summary>
- /// 利用Castle的攔截器,代理數(shù)據(jù)DataProxy
- /// < /summary>
- 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; }
- }
- 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();
- }
- }
- }
4.實體初始化
在映射文件中定義< tuplizers>映射,NHibernate提供的IInstantiator接口實現(xiàn)負責初始化實體實例,這里就是使用Castle.DynamicProxy.ProxyGenerator的public object CreateInterfaceProxyWithoutTarget(System.Type interfaceToProxy, System.Type[] additionalInterfacesToProxy, params Castle.Core.Interceptor.IInterceptor[] interceptors )方法創(chuàng)建一個接口代理。
- /// < summary>
- /// 利用NH2.1新特性Tuplizers提供的IInstantiator接口實現(xiàn)負責初始化實體/組件實例
- /// < /summary>
- public class EntityInstantiator : IInstantiator
- {
- private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();
- private readonly Type t;
- public EntityInstantiator(Type entityType)
- {
- t = entityType;
- }
- public object Instantiate()
- {
- return Instantiate(null);
- }
- public object Instantiate(object id)
- {
- return
- proxyGenerator.CreateInterfaceProxyWithoutTarget(t, new[] { typeof(IProxyMarker), t }, new DataProxyHandler(t.FullName, id));
- }
- /// < summary>
- /// 判斷是否實例化
- /// < /summary>
- /// < param name="obj">< /param>
- /// < returns>< /returns>
- 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);
- }
- }
- }
5.重寫PocoEntityTuplizer
這才是我們真正自定義的Tuplizer,在映射中使用,重寫NHibernate提供的POCO的PocoEntityTuplizer的初始化方法,返回上面實體初始化類完成的創(chuàng)建一個接口代理。
- /// < summary>
- /// 重寫PocoEntityTuplizer
- /// < /summary>
- public class EntityTuplizer : PocoEntityTuplizer
- {
- public EntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) : base(entityMetamodel, mappedEntity) { }
- protected override IInstantiator BuildInstantiator(PersistentClass persistentClass)
- {
- return new EntityInstantiator(persistentClass.MappedClass);
- }
- }
6.實體攔截
NHibernate可以利用NHibernate.IInterceptor實現(xiàn)攔截這個實體:可以去攔截我們創(chuàng)建一個System.Type代理將出現(xiàn)無法預測的值,在這里我僅僅返回上面定義的IProxyMarker標記數(shù)據(jù)的實體名稱,對于其他類型的實體則返回空值。
- /// < summary>
- /// 利用NHibernate.IInterceptor對這個實體實現(xiàn)攔截
- /// < /summary>
- public class EntityNameInterceptor : EmptyInterceptor
- {
- public override string GetEntityName(object entity)
- {
- return ExtractEntityName(entity) ?? base.GetEntityName(entity);
- }
- 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;
- }
- }
7.EntityFactory
我們創(chuàng)建一個實體工廠,所謂工廠就是New出來實體的一個制造工廠。我們可以var user = entityFactory.NewEntity< IUser>()這樣初始化一個實體。
- 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));
- }
- }
上面那些部分相當于一個前奏,為使用tuplizer做準備,我們可以在映射中使用我們自定義的Tuplizer了。
8.映射
這時需要映射這個接口了,使用< tuplizer>映射,這個映射有兩個屬性,分別為class和entity-mode。在這個例子中我在IUser中按照POCO實體模型自定義EntityTuplizer實現(xiàn)來映射。
- < class name="IUser">
- < tuplizer class="EntityTuplizer" entity-mode="poco"/>
- < id name="Id">
- < generator class="hilo"/>
- < /id>
- < property name="Name"/>
- < /class>
9.測試
測試一下我們的結果吧。分別創(chuàng)建、查詢、刪除操作吧。
[Test]
- public void UserCrud()
- {
- object savedId;
- var user = entityFactory.NewEntity< IUser>();
- user.Name = "李永京";
- using (var session = sessions.OpenSession())
- using (var tx = session.BeginTransaction())
- {
- savedId = session.Save(user);
- tx.Commit();
- }
- using (var session = sessions.OpenSession())
- using (var tx = session.BeginTransaction())
- {
- user = session.Get< IUser>(savedId);
- Assert.That(user, Is.Not.Null);
- Assert.That(user.Name, Is.EqualTo("李永京"));
- session.Delete(user);
- tx.Commit();
- }
- using (var session = sessions.OpenSession())
- using (var tx = session.BeginTransaction())
- {
- user = session.Get< IUser>(savedId);
- Assert.That(user, Is.Null);
- tx.Commit();
- }
- }
結語
由于NHibernate資料很少,所以我從源碼中找到這個例子稍微說明下,大家對Tuplizer有什么好的想法可以回復討論下咯,我想這個功能的擴展就是如果NHibernate Domain與WPF結合,我需要在所有Domain中實現(xiàn)INotifyPropertyChanged接口,就需要重新實現(xiàn)DataProxyHandler。
【編輯推薦】