I found many examples online covering how to serialize NHibernate entities for use over a WCF channel, while still using lazy and eager loading. In my application, my database models and my SOA models needed to be exactly the same; therefore, I wanted to avoid the use of DTOs. However, none of the examples I found quite worked for me.
Examples I found included:
My custom object graph walker (I hacked this out of some integrated code so it may need some tweaking):
using System; using System.Collections.Generic; using System.Collections; using System.Reflection; using System.Data; using System.Linq; using System.Configuration; using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; using FluentNHibernate.Automapping; using Radiant.RQS.Services.Types; using Radiant.RQS.Services.Core.Conventions; using Radiant.RQS.Superbank; using NHibernate.Proxy; using NHibernate.Collection; using NHibernate.Metadata; using NHibernate.Type; namespace Radiant.RQS.Services.Core { public static class Resolver { public static List<T> ResolveList<T>(List<T> entityList, ISession session) where T : class { // create a resolved entities list for peace and sharing List<Object> resolvedEntities = new List<Object>(); // loop over elements for (int entityListIndex = 0; entityListIndex < entityList.Count; entityListIndex++) entityList[entityListIndex] = Resolve<T>(entityList[entityListIndex], session, resolvedEntities); // done return entityList; } public static T[] ResolveArray<T>(T[] entityArray, ISession session) where T : class { // create a resolved entities list for peace and sharing List<Object> resolvedEntities = new List<Object>(); // loop over elements for (int entityArrayIndex = 0; entityArrayIndex < entityArray.Length; entityArrayIndex++) entityArray[entityArrayIndex] = Resolve<T>(entityArray[entityArrayIndex], session, resolvedEntities); // done return entityArray; } public static T Resolve<T>(T entity, ISession session) where T : class { // forward to resolver return Resolve<T>(entity, session, new List<Object>()); } private static T Resolve<T>(T entity, ISession session, List<Object> resolvedEntities) where T : class
{ // CHECKS // // if the entity is null, just skip it if (entity == null) return default(T); // if we have already resolved it, return that if (resolvedEntities.Contains(entity)) return entity; // RESOLVE ENTITY // T resolvedEntity = default(T); // now lets go ahead and make sure everything is unproxied try { resolvedEntity = (T)session.GetSessionImplementation().PersistenceContext.Unproxy(entity); } catch (Exception ex) { return default(T); } // add entity to the list of resolved entities resolvedEntities.Add(resolvedEntity); // GET TYPE INFO // IClassMetadata entityMetadata = null; // get the entity type Type entityType = entity.GetType(); // get the entity meta data from the type try { entityMetadata = session.SessionFactory.GetClassMetadata(entityType); } catch (Exception ex) { return default(T); } // PERFORM PROPERTY DIVE // String propertyName; Object propertyValue; Type propertyListType; IType entityPropertyType = null; Type propertyListInternalType; // get properties for this object PropertyInfo[] propertyInfos = entityType.GetProperties(); // loop over source properties & compare foreach (PropertyInfo propertyInfo in propertyInfos) { // get property name propertyName = propertyInfo.Name; // get property type try { entityPropertyType = entityMetadata.GetPropertyType(propertyName); } catch (Exception ex) { continue; } // get property value propertyValue = propertyInfo.GetValue(entity, null); // these are not the good kind of bags :P if (entityPropertyType.IsCollectionType) { // first get the property list's internal type propertyListInternalType = propertyInfo.PropertyType.GetGenericArguments()[0]; // create new array type based on the internal type propertyListType = typeof(List<>).MakeGenericType(propertyListInternalType); // create a new property list of the internal type IList propertyList = (IList)Activator.CreateInstance(propertyListType); // set the property list in the resolved object propertyInfo.SetValue(resolvedEntity, propertyList, null); // get the enumerator for this property value IEnumerator enumerator = ((IEnumerable)propertyValue).GetEnumerator(); // loop over items to also perform resolution while (enumerator.MoveNext()) propertyList.Add(Resolve(enumerator.Current, session, resolvedEntities)); } // destroy hibernate proxies else if (entityPropertyType.IsEntityType) { // set the property of the resolved entity to the child beneath us propertyInfo.SetValue(resolvedEntity, Resolve(propertyValue, session, resolvedEntities), null); } } // return the resolved entity :) return resolvedEntity; } } }
This works FLAWLESSLY for me. When I lazy load or eager load sub-entities before calling Resolve() it perfectly serializes only what I want and does not attempt to lazy load the entire DB.
Example:
// get the computer query
var computerQuery =
from c in context.Session.Query<Types.Computer>()
where c.ID == computerID
select c;
// return computer
return Resolver.Resolve<Types.Computer>(computerQuery.SingleOrDefault(), session);
Let me know if this doesn't work for you :).
Thanks,
Alex G. (awgneo@xbetanet.com)