views:

52

answers:

2

I'm wondering how to persist a property that depends on both regular persisted properties (e.g. string, int) as well as some custom transforms of their own.

For instance suppose I have

class A
{
    public int Id {get; set;}
    public string SomeString {get; set;}
    public object SpecialProperty {get; set;}
}

suppose that persisting SpecialProperty requires reading SomeString and depending on it's value, producing some byte[] which can then be stored in the database.

My first thought was to use an IUserType, but this the method NullSafeGet(IDataReader rs, string[] names, object owner) is invoked before (or actually, during) SomeString is persisted (or not), so it is not set.

My second thought was to use a ICompositeUserType and some rather convoluted setup using wrappers.

My third thought was maybe I could implement ILifecycle and hook the OnLoad() method. But if I wanted to do that I would need to create a seperate property for the byte[] payload which I don't really want to store. This certainly seems the easiest to implement but also somewhat inelegant.

e.g.

class A : ILifecycle
{
    public int Id {get; set;}
    public string SomeString {get; set;}
    public object SpecialProperty {get; set;}

    private byte[] payloadData { get; set; }

    public void OnLoad(ISession s, object id)
    {
     SpecialProperty = customIn(SomeString, payloadData);
     payloadData = null;

    }

    static object customIn(string a, byte[] payload)
    {
     // ...
    }

}

Does anybody know of an an easier and possibly more concise way?

A: 

I'd do something like the following:

class A
{
    public virtual int Id {get; set;}
    protected virtual string _SomeString { get; set; }
    protected virtual object _SpecialProperty { get; set; }

    public virtual object SpecialProperty {get { return _SpecialProperty; } }
    public virtual string SomeString {get { return _SomeString; } set { _SomeString = value; _SpecialProperty = SomeManipulatorMethod(value); } }
}

I'd only map Id, _SomeString and _SpecialProperty.

Spencer Ruport
A: 

I was so far and yet so close. Of course, it turns out we can access the dependent property from the IDataReader in the NullSafeGet method.

A complete(-ish) solution is here:

Firstly let us define a fluent mapping:

 public class AMap : ClassMap<A>
 {
  public AMap()
  {
   Id(x => x.Id);
   Map(x => x.SomeString);
   Map(x => x.SpecialProperty)
    .CustomType(typeof (DependentProperty))
    ;
  }

  static object DeserialiseSpecialProperty(string SomeString, byte[] specialProperty)
  {
  // ...
  }

  static byte[] SerialiseSpecialProperty(object specialProperty)
  {
  // ...
  }


 }

Now, implement DependentProperty.

public class DependentProperty: IUserType
{
// change SqlTypes appropriatelly if your backing field is something else
 public SqlType[] SqlTypes { get { return new[] {new SqlType(DbType.Binary)}; } }

 public Type ReturnedType { get { return typeof (object); } }

 public bool IsMutable { get { return false; } }

 public int GetHashCode(object x)
 {
  if (x == null)
   return 0;
  return x.GetHashCode();
 }

 public object NullSafeGet(IDataReader rs, string[] names, object owner)
 {
  var SomeString = (string)rs.GetValue(1);
  object obj = NHibernateUtil.Binary.NullSafeGet(rs, names[0]);
  return AMap.DeserialiseSpecialProperty(SomeString, (byte[])obj);
 }

 public void NullSafeSet(IDbCommand cmd, object value, int index)
 {
  if (value == null)
   ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
  else
   ((IDataParameter)cmd.Parameters[index]).Value = AMap.DeserialiseSpecialProperty(value);
 }

 public object DeepCopy(object value) { return value; }

 public object Replace(object original, object target, object owner) { return original; }

 public object Assemble(object cached, object owner) { return cached; }

 public object Disassemble(object value) { return value; }

 bool IUserType.Equals(object x, object y) { return object.Equals(x, y); }
}

Notes:

  1. The IUserType implementation is based on a template from a nhforge tutorial. It should be fine for value types, but if you are doing anything complex with reference types or deep object graphs, you may have to provide different implementations for the various methods.

  2. Due to directly accessing the IDataReader this is VERY BRITTLE. Changes to the order of mappings in AMap will most likely require you to update the index accessed. Quite possibly other changes to AMap (and maybe even A) could also break this implementation.

    This is unpleasant, yes, but then so is having to store persistence fields (and custom serialiser/deserialiser baggage) directly in your business objects and having them called every time the external property is get/set'd. I would argue that a broken index should be picked up by your persistence validation and this implementation should lead to a generally cleaner separation of business/persistence concerns.

  3. Combine this with (pimp my post?) sqlite's manifest typing capabilities and you can do some very cool dynamic typing stuff :)

fostandy