ORM and Custom Object Serialization

Occasionally we need to store data in a database that doesn’t neatly fit into the “normal” column type definitions of a database – for example we might want to store an image or just a serialized object. The newest OpenNETCF.ORM Library provides a simple mechanism for handling these types of objects, it’s just not all that clear from the object model how it works. I’m hoping that this post will clarify things.


First, ORM can handle *any* type of object that is marked as a Field in an Entity class. If the Field is an Object, however, you have to help it out by providing a mechanism to serialize and deserialize that object.


Let’s look at a simple example. Let’s say I have the following Entity definition:

[Entity(KeyScheme.Identity)]
public class TestTable
{
[Field(IsPrimaryKey = true)]
public int TestID { get; set; }

[Field]
public CustomObject CustomObject { get; set; }
}


Sure, it’s likely going to have more fields, but what’s important here is the CustomObject Property that is a CustomObject class. What happens when the ORM library hits one of these when it’s doing an Insert or Select?


If the object boils down to being an object, it calls either a Serialize or Deserialize method, depending on which it needs, in the Entity instance itself. The expectation is that these methods will have the following signatures (yes, I realize these should maybe be defined in some abstract base class, but for now they aren’t):

[Entity(KeyScheme.Identity)]
public class TestTable
{
[Field(IsPrimaryKey = true)]
public int TestID { get; set; }

[Field]
public CustomObject CustomObject { get; set; }

public byte[] Serialize(string fieldName)
{
// serialize
}

public object Deserialize(string fieldName, byte[] data)
{
// deserialize
}
}


What happens here is that a string representation of the Field being requested will be passed in by the framework, and it’s up to your implementation to correctly handle it. Let’s extend this example by defining our CustomObject like this:

public class CustomObject
{
public string ObjectName { get; set; }
public Guid Identifier { get; set; }
public int SomeIntProp { get; set; }

public CustomObject()
{
}

public CustomObject(byte[] data)
{
// deserialization ctor
int offset = 0;

// get the name length
var nameLength = BitConverter.ToInt32(data, offset);

// get the name bytes
offset += 4; // past the length
this.ObjectName = Encoding.ASCII.GetString(data, offset, nameLength);

// get the GUID
offset += nameLength;
byte[] guidData = new byte[16];
// we must copy the data since Guid doesn’t have a ctor that allows us to specify an offset
Buffer.BlockCopy(data, offset, guidData, 0, guidData.Length);
this.Identifier = new Guid(guidData);

// get the int property
offset += guidData.Length;
this.SomeIntProp = BitConverter.ToInt32(data, offset);
}

public byte[] AsByteArray()
{
List buffer = new List();

byte[] nameData = Encoding.ASCII.GetBytes(this.ObjectName);

// store the name length
buffer.AddRange(BitConverter.GetBytes(nameData.Length));

// store the name data
buffer.AddRange(nameData);

// store the GUID
buffer.AddRange(this.Identifier.ToByteArray());

// store the IntProp
buffer.AddRange(BitConverter.GetBytes(this.SomeIntProp));

return buffer.ToArray();
}
}


Notice that I’ve decided to create my own custom serialization routines for the object. You could just as easily use a built-in serializer or whatever you’d like. I’ve put the serialization routines in the custom object class itself to prevent cluttering up my Entity definition. Now the Entity definition get’s fleshed out to look like this for the Serialize and Deserialize methods:

[Entity(KeyScheme.Identity)]
public class TestTable
{
[Field(IsPrimaryKey = true)]
public int TestID { get; set; }

[Field]
public CustomObject CustomObject { get; set; }

public byte[] Serialize(string fieldName)
{
if (fieldName == “CustomObject”)
{
// This will always be true in this case since CustomObject is our only
// Object Field. The “if” block could be omitted, but for sample
// clarity I’m keeping it
if (this.CustomObject == null) return null;
return this.CustomObject.AsByteArray();
}

throw new NotSupportedException();
}

public object Deserialize(string fieldName, byte[] data)
{
if (fieldName == “CustomObject”)
{
// This will always be true in this case since CustomObject is our only
// Object Field. The “if” block could be omitted, but for sample
// clarity I’m keeping it
return new CustomObject(data);
}

throw new NotSupportedException();
}
}


Pretty straightforward, especially if you only have one Object Field to worry about. Once the Serialize/Deserialize methods are implemented in the Entity, the new Entity works just like any other entity in ORM, so you can do something like this to insert a row and retrieve it back:

    var newObject = new CustomObject
{
ObjectName = “Object A”,
Identifier = Guid.NewGuid(),
SomeIntProp = 12345
};

var testRow = new TestTable
{
CustomObject = newObject
};

Store.Insert(testRow);

var existing = Store.Select().First();


I’ve found this to be extremely useful in a project where I had to store spectrum data for a row. Keep in mind that if the object is of any size, it could seriously impact your Select performance since the ORM has to select and rehydrate all of these objects for your returned data set. Use it sparingly, and if the object is often empty, it might pay off to put the custom object into its own child table and pull it in as a relationship so that the Serialize/Deserialize routines only get called when absolutely necessary.

7 thoughts on “ORM and Custom Object Serialization”

  1. Hi,

    Would it be possible to change the Serialize and Deserialize method signatures so that the PropertyInfo of the field is also passed to these methods? The reason I’m asking is because there is an entity in the project that I’m currently working on that has several fields that are of enumeration types. These fields have to be persisted to the database as integers, so have to decorate each of these fields with Datatype = DbType.Object so that the Serialize method is called when an insertupdate is performed. Instead of having an ‘if’ statement for each field, I would like to be able to do something similar to this:
    public object Serialize(string fieldName, PropertyInfo property)
    {
    object value = property.GetValue(this, null);

    if (property.PropertyType.IsEnum)
    {
    value = Convert.ToInt16(value);
    }

    return value;
    }

    I’m using a previous version of your ORM library, but it seems to me that this would be valid in the current version.

    Thanks

    Like

  2. I just tested the existing code base with Fields that are Enums and, as I expected, they work just fine without any additional code. Serialization isn’t called at all and they get properly turned into integers in the database and back to proper Enum values on a Select. I didn’t decorate them with any DataType attribute at all:

    [Field]
    public TestEnum EnumField { get; set; }

    How old is the code you’re working with?

    Like

  3. I’m using build 1.0.10239.

    I tested a similar scenario, but an InvalidCastException is thrown when the ORM is setting the value in the column of the SqlCeUpdatableRecord. To be more precise, the problem arises in the following lines of code in the Insert method of SqlCeDatastore class:

    var value = field.PropertyInfo.GetValue(item, null); //this line returns the correct value

    record.SetValue(field.Ordinal, value); //InvalidCastException is thrown

    The last two lines of the stack trace of the exception are:

    en System.Data.SqlServerCe.SqlCeUpdatableRecord.SetClrTypeValue(Int32 ordinal, Object value, String method)

    en System.Data.SqlServerCe.SqlCeUpdatableRecord.SetValue(Int32 ordinal, Object value)

    That’s why I opted to decorate the field with Datatype = DbType.Object, so that the Serialize method would be called and I could convert the enum to Int16. The database field is of type smallint…could that be the cause of the problem?

    Thanks

    Like

  4. I’m not using ORM to create the database, it’s being created by another library. I’m sure using the ORM library to create the database will solve the issue.

    Thanks for the help.

    Like

  5. Ah, that’s the difference then. Let me think on how to improve ORM to protect against this sort of scenario. As a side note, you might want to move this type of discussion over to the Codeplex project itself so others can benefit from it.

    Like

  6. Yes, as soon as I posted the first comment I remembered that I should’ve posted the question on Codeplex. Next time I’ll make sure I do that.

    Thanks again.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s