Reputation: 28137
I have the following classes:
public class Base
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Alpha : Base
{
public string Foo { get; set; }
public int Bar { get; set; }
}
public class Beta : Base
{
public string Baz { get; set; }
}
And I want to be able to store this in a SQL database like so:
Id Name Type ExtraInfo
=======================================
1 Asdf Alpha {"Foo":"hello","Bar":7}
2 Qwer Beta {"Baz":"goodbye"}
And be able to retrieve it in a sensible way, e.g. (pseudo-code):
repo.Bases.First(a => a.Type == "Alpha").Magic<Alpha>().Foo; // returns hello
...where Magic
is some unknown method which performs the mapping.
My prefered ORM is Entity Framework, however, I am pretty sure this is a bit beyond the scope of it.
Is this possible with any ORM (NHibernate, or even EF, etc)?
Upvotes: 5
Views: 3062
Reputation: 1928
With small tweaks that could be achieved with NHibernate and its UserTypes
public class Base
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Alpha : Base
{
public virtual CustomData Custom { get; set; }
public class CustomData
{
public string Foo { get; set; }
public int Bar { get; set; }
}
}
// NHibernate mapping
public class AlphaMapping : SubclassMapping<Alpha>
{
public AlphaMapping()
{
Property(a => a.Custom, m =>
{
// tell NHibernate to use custom type
m.Type<JsonType<Alpha.CustomData>>();
});
}
}
// NHibernate UserType to serialize object to json and back
public class JsonType<T> : IUserType
{
public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
var json = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json);
}
public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(value);
NHibernateUtil.String.NullSafeSet(cmd, json, index);
}
}
Upvotes: 6
Reputation: 123891
The solution should be splitted into 2 parts, I would say. 1) The mapping of the serialized stuff and 2) serialization. I am using NHibernate. this scenario is in fact very similar to more common issue storing 1) the path to a file 2) finding the file on the share.
The mapping. It is trivial. Just introduce the new Property ExtraInfo
, could be on the base level
public virtual string ExtranInfo { get; set; }
Regardless of the ORM tool, we will simply map this to a nvarchar column ExtraInfo.
The inheritance is also pretty simple, for NHibernate we can use 8.1.1. Table per class hierarchy (I cannot help my self, the xml mapping is more readable, but fluent would do the same)
<class name="Base" table="BaseTable">
<!-- common id -->
<id name="Id" generator="native" />
<!-- the column keeping the subtype selector -->
<discriminator column="Type" type="String"/>
<!-- base properties
<property name="Name" />
<property name="ExtraInfo" /> <!-- the JSON -->
<subclass name="Alpha" discriminator-value="Alpha" />
<subclass name="Beta" discriminator-value="Beta" />
<!-- ... more subclasses -->
</class>
Serialization. It depends on the architecture (and partially on ORM tool). We can use AOP (preferable solution) hooked on the Read and Write operations to create the JSON or fill the other properties. NHibernate supports 12.1. Interceptors and 12.2. Event system
The comprehensive explanation of the Intercepting in Hibernate/NHibernate world could be found here
The query how to get our Alpha:
ISession session = ... // get NHSession standard way
var result = session.Query<Base>()
.Where(b => b is Alpha)
.Take(1)
.Cast<EmployeeField>()
.ToList<EmployeeField>()
So in this case, Interceptor in the OnLoad
method had access to all the loaded information and can do that job for us
public override bool OnLoad(object entity, object id,
object[] state, string[] propertyNames, IType[] types)
NHibernate has another nice feature: IResultTransformer
. It could be injected into the query processing this way (16. QueryOver Queries):
// not linq but QueryOver
var result = session.QueryOver<Base>()
.Where(b => b is Alpha)
// here we go
.TransformUsing(new MyTransformer())
.Take(1)
.List<Alpha>()
Example of the custom ResultTransformer implementation, the signiture of the essential method:
public object TransformTuple(object[] tuple, string[] aliases)
{
Summary. Despite of the fact, that storing properties as JSON is not out of the box supported, we can manage that with simple varchar column persistence and some AOP/Interceptors.
Maybe interesting reading how the "dynamic columns/properties" is working in NHibernate: NHibernate Dynamic Columns Number
Upvotes: 1