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)