Благодаря ответу Петра я предпринял следующую начальную попытку для пользовательского типа обрабатывать экспандо. Это работает довольно хорошо, и теперь я могу предложить свойства набора клиентов для каждого из моих объектов. Я устанавливаю свойства, которые должен иметь каждый объект, и каждый клиент может затем добавить свои собственные свойства для удовлетворения своих потребностей.
Одно предостережение - я настраиваю это только в целях настойчивости. Поиск в этом приложении не является необходимым, поскольку все запросы выполняются к базе данных MongoDB, которая денормализовала копии данных.
public class ExpandoUserType : IUserType
{
public object Assemble(object cached, object owner)
{
return cached;
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return value;
}
public bool Equals(object x, object y)
{
return false;
}
public int GetHashCode(object x)
{
return 0;
}
public bool IsMutable
{
get { return false; }
}
public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.XmlDoc.NullSafeGet(rs, names[0]);
if (obj == null) return null;
var xmldoc = (XmlDocument)obj;
dynamic expando = new ExpandoObject();
foreach (XmlElement el in xmldoc.FirstChild.ChildNodes)
{
object val = null;
switch (Convert.ToString(el.Attributes["type"].InnerText).ToLower())
{
case "string":
val = el.InnerText;
break;
case "int32":
val = Convert.ToInt32(el.InnerText);
break;
case "int16":
val = Convert.ToInt16(el.InnerText);
break;
case "int64":
val = Convert.ToInt64(el.InnerText);
break;
case "bool":
val = Convert.ToBoolean(el.InnerText);
break;
case "datetime":
val = Convert.ToDateTime(el.InnerText);
break;
case "byte":
val = Convert.ToByte(el.InnerText);
break;
case "decimal":
val = Convert.ToDecimal(el.InnerText);
break;
}
((IDictionary<String, Object>)expando).Add(el.Name, val);
}
return expando;
}
/// <summary>
/// Transforms the expando object to an XML Document for storage in SQL.
/// </summary>
/// <param name="cmd"></param>
/// <param name="value"></param>
/// <param name="index"></param>
public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
if (value == null || value == DBNull.Value)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
}
else
{
NHibernateUtil.XmlDoc.Set(cmd, expandoToXML((ExpandoObject) value, "root"), index);
}
}
public object Replace(object original, object target, object owner)
{
return original;
}
public Type ReturnedType
{
get { return typeof(ExpandoObject); }
}
public NHibernate.SqlTypes.SqlType[] SqlTypes
{
get { return new[] { NHibernateUtil.XmlDoc.SqlType }; }
}
private static XmlDocument expandoToXML(dynamic node, String nodeName)
{
XElement xmlNode = new XElement(nodeName);
foreach (var property in (IDictionary<String, Object>)node)
{
if (property.Value.GetType() == typeof(ExpandoObject))
xmlNode.Add(expandoToXML(property.Value, property.Key));
else
if (property.Value.GetType() == typeof(List<dynamic>))
foreach (var element in (List<dynamic>)property.Value)
xmlNode.Add(expandoToXML(element, property.Key));
else
{
XElement xnode = new XElement(property.Key, property.Value);
xnode.SetAttributeValue("type", property.Value.GetType().Name);
xmlNode.Add(xnode);
}
}
return xmlNode.GetXmlNode();
}
}