views:

925

answers:

2

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...

+7  A: 

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.

sirrocco
Thanks for the tip!
SoloBold
I see your point :) Now I'm just trying to figure out how to generate them through reflection...
SoloBold
+1 Excellent answer!
Andrei Rinea
+1  A: 

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;
    }
Miki Watts
care to post an example?
SoloBold
very interesting
SoloBold