Вопрос

I am probably trying to do something that isn't possible. I am working with Castle.ActiveRecord / Nhibernate. I have been happy so far, but one thing I have always wanted to able to so is have something like:

[Property(ColumnType = "StringClob")]
public IDictionary<string, string> MetaData { get; set; }

This, for obvious reasons, this isn't possible at the moment. I started playing around with things to make this work and have come down to the following.

I have created a custom class that extends Dictionary

public class SerializableDictionary<TK, TV> : Dictionary<TK, TV>
{
    public static explicit operator SerializableDictionary<TK, TV>(System.String serialized)
    {
        return JsonConvert.DeserializeObject<SerializableDictionary<TK, TV>>(serialized);
    }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

And in my ActiveRecord model I have this:

[Property(ColumnType = "StringClob")]
public SerializableDictionary<string, string> MetaData { get; set; }

This works extremely well when I save the data. It gets serialized nicely and stores as a string. When I try to load a model though, I get the following error:

Unable to cast object of type 'System.String' to type 'MyNamespace.SerializableDictionary`2[System.String,System.String]'.

I have looked as deep as possible (using dotPeek) into where this is happening. The stack trace led me to:

NHibernate.Bytecode.Lightweight.AccessOptimizer.SetPropertyValues(Object target, Object[] values)

I am at a complete loss. I have scoured google to see if what I am trying to do is even possible. Any help is much appreciated.

Это было полезно?

Решение

I was able to figure this out after being pointed in the right direction by Mike Christensen in the comments.

All of this code is probably overkill, and somethings are probably inefficient because I was doing it as fast as I could. But here it is...

I started with the same extend class as in the question (I modified it a little, because I'm still not sure how to implement the generics).

public class SerializableDictionary : Dictionary<string, object>
{
    public static explicit operator SerializableDictionary(String serialized)
    {
        return JsonConvert.DeserializeObject<SerializableDictionary>(serialized);
    }

    public static explicit operator String(SerializableDictionary dict)
    {
        return JsonConvert.SerializeObject(dict);
    }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

I next had to create a custom SqlType for NHibernate. This class had to implement IUserType, which came with a bunch methods that had to be implemented (I'm not sure I implemented these the best way possible). This class now allows NHibernate to use the NullSafeSet to set the model property with the string from the database. There I was able to do the deserialization.

public class SerializableDictionaryType : IUserType
{
    public new bool Equals(object x, object y)
    {
        return x != null && x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        string dbString = (string) NHibernateUtil.String.NullSafeGet(rs, names);
        SerializableDictionary dict = JsonConvert.DeserializeObject<SerializableDictionary>(dbString);
        return dict;
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        if (value == null)
        {
            NHibernateUtil.String.NullSafeSet(cmd, null, index);
            return;
        }
        value = value.ToString();
        NHibernateUtil.String.NullSafeSet(cmd, value, index);
    }

    public object DeepCopy(object value)
    {
        if (value == null) return null;
        SerializableDictionary newDict = new SerializableDictionary();
        foreach (KeyValuePair<string, object> item in (SerializableDictionary)value)
        {
            newDict.Add(item.Key, item.Value);
        }
        return newDict;
    }

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

    public object Assemble(object cached, object owner)
    {
        return JsonConvert.DeserializeObject<SerializableDictionary>(cached.ToString());
    }

    public object Disassemble(object value)
    {
        return JsonConvert.SerializeObject(value);
    }

    public SqlType[] SqlTypes
    {
        get
        {
            SqlType[] types = new SqlType[1];
            types[0] = new SqlType(DbType.String);
            return types;
        }
    }

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

    public bool IsMutable
    {
        get { return false; }
    }
}

Then on the model itself I had to put the following:

[Property(ColumnType = "MyNamespace.SerializableDictionaryType, MyNamespace")]
public SerializableDictionary UserData { get; set; }

This ColumnType refers to the custom type mapper that I created. If you notice I had to use the full namespace and class along with the assembly it's located in.

With all of this setup it works like a charm. I'm planning on optimizing things to make it look nicer and would still like to find a way to deal with generics instead of being stuck with:

<string, object>

I'll update the answer with anything I find.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top