Archives for: February 2005, 10

10/02/05

Permalink 10:59:28 am
Categories: Programming, .NET, XPO

Persisting of unknown (or new) types with XPO

In XPO, an ORM product by Developer Express, it's possible to use a custom "value converter" to persist information when the standard mapping techniques are not sufficient. This provides a flexible approach that can be used in many different scenarios.

Nullable types

Although the Developer Express knowledge base provides an article that shows how to persist a bitmap, I had never actually used that approach. With .NET 2.0, I was thinking about persisting nullable types and I found that XPO 1 doesn't have support for that out of the box. The support guys sent me a sample showing how a value converter can be used to extend the mapping to include nullable types. I extended that code to create a generic value converter that can be used for any nullable type. Here's the code for that:

  public class NullableValueConverter<T> : ValueConverter where T: struct {
    public NullableValueConverter( ) {
      storageType = typeof(T);
    }

    private Type storageType;
    public override Type StorageType {
      get { return storageType; }
    }

    public override object ConvertToStorageType(object value) {
      Nullable<T> nullableValue = (Nullable<T>) value;
      return nullableValue.HasValue ? (object) nullableValue.Value : null;
    }

    public override object ConvertFromStorageType(object value) {
      return (Nullable<T>) (value == null ? null : value);
    }
  }

Now how do you use that thing? It's simple: just decorate a property that has a nullable type with the ValueConverterAttribute:

  private int? intVal;
  [ValueConverter(typeof(NullableValueConverter<int>))]
  public int? IntVal {
    get { return intVal; }
    set { intVal = value; }
  }

With newer versions of XPO (I'm not sure if that feature is in any released version yet, if it isn't and you want it (and you are a customer), feel free to send mail to support@devexpress.com) you can also register the value converter for a type globally, so all properties of that type will be converted automatically. Here's how:

  Session.DefaultSession.Dictionary.RegisterValueConverter(
    new NullableValueConverter<int>(), typeof(int?));

Unknown types

The second interesting use of the value converter was something I stumbled upon a few days later. I had a field in a class that should have been persisted, but I really didn't know what that field might contain later on. The information would be sent to a server via Remoting and the type depended on the client implementation, so I could only use "object" as the type for the field. Now, how would I go about persisting this information?

The solution I found was to create a value converter that uses serialization to persist the object into a string field. Of course, the object has to be Serializable for this to work, but that's a requirement for the Remoting part anyway, so I didn't care. With my SerializableObjectConverter it's possible to decorate a property like this:

  private object data;
  [ValueConverter(typeof(SerializableObjectConverter)), Size(SizeAttribute.Unlimited)]
  public object Data {
    get { return data; }
    set { data = value; }
  }

And here's the code for the SerializableObjectConverter:

public class SerializableObjectConverter : ValueConverter {
  public override Type StorageType { get { return typeof(string); } }

  public override object ConvertToStorageType(object value) {
    Assert.Check(value.GetType().IsSerializable, 
      String.Format("The given object ({0}) is not serializable.", value));

    string result = null;

    MemoryStream stream = new MemoryStream();
    try {
      SoapFormatter formatter = new SoapFormatter( );
      formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
      formatter.Serialize(stream, value);
      result = Encoding.Default.GetString(stream.GetBuffer( ));
    }
    finally {
      stream.Close();
    }

    return result;
  }

  public override object ConvertFromStorageType(object value) {
    object result = null;
			
    MemoryStream stream = new MemoryStream(
      Encoding.Default.GetBytes((string) value));
    try {
      stream.Position = 0;
      SoapFormatter formatter = new SoapFormatter();
      formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
      result = formatter.Deserialize(stream);
    }
    finally {
      stream.Close();
    }

    return result;
  }
}

Enter your email address:

Search