Question

I have some web methods that return my objects back as serialized XML. It is only serializing the NHibernate-mapped properties of the object... anyone have some insight? It seems to be that the web methods are actually serializing the NHibernate proxies instead of my classes. I've tried using [XMLInclude] and [XMLElement], but the properties are still not serializing. I have a really horrible hackish way of getting around this, but I wondered if there was a better way!

Something like this:

<?xml version="1.0" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="StoryManager" assembly="StoryManager">
  <class name="Graphic" table="graphics" lazy="false">
    <id name="Id" column="id" type="int" unsaved-value="0" >
      <generator class="identity"/>
    </id>

    <property name="Assigned" />
    <property name="Due" />
    <property name="Completed" />
    <property name="UglyHack" insert="false" update="false" />


    <many-to-one name="Parent" class="Story" column="story_id"/>

  </class>
</hibernate-mapping>

public class Graphic
{
    private int m_id;
    public virtual int Id
    {
        get { return m_id; }
        set { m_id = value; }
    }

    private DateTime? m_assigned;
    public virtual DateTime? Assigned
    {
        get { return m_assigned; }
        set { m_assigned = value; }
    }

    private DateTime? m_due;
    public virtual DateTime? Due
    {
        get { return m_due; }
        set { m_due = value; }
    }

    private DateTime? m_completed;
    public virtual DateTime? Completed
    {
        get { return m_completed; }
        set { m_completed = value; }
    }

    public bool UglyHack
    {
        get { return m_due < m_completed; } // return something besides a real mapped variable
        set {} // trick NHibernate into thinking it's doing something
    }
}

This is obviously no way to write code. If I don't have the "fake" mapping in there (UglyHack property), that property won't be serialized. For now I'm looking into using (Data) Transfer Objects, and may be on to something using reflection...

Was it helpful?

Solution

The best way to serialize the NH mapped object is to not serialize it :).

If you're sending it across the wire you should really create a DTO for it. If you don't want to create that object you can set [XmlIgnore] on properties you don't want serialized.

If you want all properties, you have to load them ALL from the database - for some an eager load will be enough for others(where too many joins will start duplicating data) you'll have to access that property in any way you want to trigger the load.

Edit:

And I'd like to add another thing - sending your domain entities over the wire is always a bad idea. In my case I learned it the hard way - I expose some entities over a WebService - and now almost any change(rename a property, remove a property ..etc ) to my domain kills the app using the WS - plus a whole bunch of properties have [XmlIgnore] on them ( don't forget about circular dependencies).

We'll do a rewrite soon enough - but rest assure that's not something I'll ever do again. :)

Edit 2

You could use AutoMapper for transferring the data from your entity to the DTO. They have some examples on the site.

OTHER TIPS

if its a WCF service, you could use a IDataContractSurrogate

 public class HibernateDataContractSurrogate : IDataContractSurrogate
{
    public HibernateDataContractSurrogate()
    {
    }

    public Type GetDataContractType(Type type)
    {
        // Serialize proxies as the base type
        if (typeof(INHibernateProxy).IsAssignableFrom(type))
        {
            type = type.GetType().BaseType;
        }

        // Serialize persistent collections as the collection interface type
        if (typeof(IPersistentCollection).IsAssignableFrom(type))
        {
            foreach (Type collInterface in type.GetInterfaces())
            {
                if (collInterface.IsGenericType)
                {
                    type = collInterface;
                    break;
                }
                else if (!collInterface.Equals(typeof(IPersistentCollection)))
                {
                    type = collInterface;
                }
            }
        }

        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // Serialize proxies as the base type
        if (obj is INHibernateProxy)
        {
            // Getting the implementation of the proxy forces an initialization of the proxied object (if not yet initialized)
            try
            {
                var newobject = ((INHibernateProxy)obj).HibernateLazyInitializer.GetImplementation();
                obj = newobject;
            }
            catch (Exception)
            {
               // Type test = NHibernateProxyHelper.GetClassWithoutInitializingProxy(obj);
                obj = null;
            }
        }

        // Serialize persistent collections as the collection interface type
        if (obj is IPersistentCollection)
        {
            IPersistentCollection persistentCollection = (IPersistentCollection)obj;
            persistentCollection.ForceInitialization();
            //obj = persistentCollection.Entries(); // This returns the "wrapped" collection
            obj = persistentCollection.Entries(null); // This returns the "wrapped" collection
        }

        return obj;
    }



    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
}

Implementaion in the host:

foreach (ServiceEndpoint ep in host.Description.Endpoints)
        {
            foreach (OperationDescription op in ep.Contract.Operations)
            {
                var dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.DataContractSurrogate = new HibernateDataContractSurrogate();
                }
                else
                {
                    dataContractBehavior = new DataContractSerializerOperationBehavior(op);
                    dataContractBehavior.DataContractSurrogate = new HibernateDataContractSurrogate();
                    op.Behaviors.Add(dataContractBehavior);
                }
            }
        }

Agree with sirrocco, I've had the most awful time trying to serialize NHibernate entities through WCF, and eventually went with a DTO solution done generically through reflection.

Edit: The entire solution is too big to post here, and is customized for my needs ofcourse, so I'll post a few relevant sections:

[DataContract]
public class DataTransferObject
{
    private Dictionary<string, object> propertyValues = new Dictionary<string, object>();
    private Dictionary<string, object> fieldValues = new Dictionary<string, object>();
    private Dictionary<string, object> relatedEntitiesValues = new Dictionary<string, object>();
    private Dictionary<string, object> primaryKey = new Dictionary<string, object>();
    private Dictionary<string,List<DataTransferObject>> subEntities = new Dictionary<string, List<DataTransferObject>>();

...

    public static DataTransferObject ConvertEntityToDTO(object entity,Type transferType)
    {
        DataTransferObject dto = new DataTransferObject();
        string[] typePieces = transferType.AssemblyQualifiedName.Split(',');

        dto.AssemblyName = typePieces[1];
        dto.TransferType = typePieces[0];

        CollectPrimaryKeyOnDTO(dto, entity);
        CollectPropertiesOnDTO(dto, entity);
        CollectFieldsOnDTO(dto, entity);
        CollectSubEntitiesOnDTO(dto, entity);
        CollectRelatedEntitiesOnDTO(dto, entity);

        return dto;
    }
....

     private static void CollectPropertiesOnDTO(DataTransferObject dto, object entity)
    {
        List<PropertyInfo> transferProperties = ReflectionHelper.GetProperties(entity,typeof(PropertyAttribute));

        CollectPropertiesBasedOnFields(entity, transferProperties);

        foreach (PropertyInfo property in transferProperties)
        {
            object propertyValue = ReflectionHelper.GetPropertyValue(entity, property.Name);

            dto.PropertyValues.Add(property.Name, propertyValue);
        }
    }

then, when you want to resurrect the DTO:

    private static DTOConversionResults ConvertDTOToEntity(DataTransferObject transferObject,object parent)
    {
        DTOConversionResults conversionResults = new DTOConversionResults();

        object baseEntity = null;
        ObjectHandle entity = Activator.CreateInstance(transferObject.AssemblyName,
                                                       transferObject.TransferType);

        if (entity != null)
        {
            baseEntity = entity.Unwrap();

            conversionResults.Add(UpdatePrimaryKeyValue(transferObject, baseEntity));
            conversionResults.Add(UpdateFieldValues(transferObject, baseEntity));
            conversionResults.Add(UpdatePropertyValues(transferObject, baseEntity));
            conversionResults.Add(UpdateSubEntitiesValues(transferObject, baseEntity));
            conversionResults.Add(UpdateRelatedEntitiesValues(transferObject, baseEntity,parent));
....

    private static DTOConversionResult UpdatePropertyValues(DataTransferObject transferObject, object entity)
    {            
        DTOConversionResult conversionResult = new DTOConversionResult();

        foreach (KeyValuePair<string, object> values in transferObject.PropertyValues)
        {
            try
            {
                ReflectionHelper.SetPropertyValue(entity, values.Key, values.Value);
            }
            catch (Exception ex)
            {
                string failureReason = "Failed to set property " + values.Key + " value " + values.Value;

                conversionResult.Failed = true;
                conversionResult.FailureReason = failureReason;

                Logger.LogError(failureReason);
                Logger.LogError(ExceptionLogger.BuildExceptionLog(ex));
            }
        }

        return conversionResult;
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top