我正在开发一个应用程序,它将使用NHibernate将“命令”存储在数据库中。每个命令都有一组参数。例如,“sleep”命令具有持续时间,“set text”具有字符串值。所有这些命令都来自相同的Command
基类型。
我希望将来允许添加其他命令,同时尽可能减少对数据库的影响。我最初的反应是使用table-per-hierarchy模式,因为所需的唯一模式修改是将一列添加到Command表中。
我还考虑过使用TPH模式但是映射通用列而不是特定列,然后将它们转换为类本身中的特定(强类型)属性值(即使用强类型映射映射的通用字符串属性)公共财产)。这样,如果我的列数等于任何命令可能需要的大多数参数,我根本不需要更改表。虽然这些在我的脑海里看起来有些神奇......
作为数据库设计和NHibernate使用的相对新手,有人可以指出这些方法中的漏洞,或建议更好的东西吗?我试图避免更改架构(尽可能多)允许将来扩展和简单的C#API。
答案 0 :(得分:1)
查看BaseImmutableUserType<T> : IUserType
实现,这将允许您使用通用列。
答案 1 :(得分:0)
IMO,您最好的选择是使用包含单个XML类型列的表,而不是在继承层次结构中烘焙,这可能会或可能不会模拟实际的域问题。众所周知,XML列是一种发展数据模型的无摩擦方式,尤其是您预期随着时间的推移可能会发生显着变化的数据模型。
XML列可以存储表示命令对象所需的整个对象图,这些对象图由.NET BCL类序列化或使用您自己的自定义XML序列化程序(参见IXmlSerializable
)。
NHibernate本身支持XML SQL Server列类型。谷歌搜索应该带回几个如何进行映射等的例子。
答案 2 :(得分:0)
我最终从@mxmissile那里得到了答案,以下是实施中有趣部分的详细信息,希望能帮助其他人。结果总体来说非常干净,所有逻辑都在映射中处理。
/// <summary>NHibernate class mapping file for <see cref="Action"/>.</summary>
internal sealed class ActionMapper : ClassMap<Action>
{
/// <summary>Constructor.</summary>
public ActionMapper()
{
DiscriminateSubClassesOnColumn("ClassType").Not.Nullable();
Id(x => x.Id);
}
}
/// <summary>NHibernate class mapping file for <see cref="SetText"/>.</summary>
internal sealed class SetTextMapper : SubclassMap<SetText>
{
public SetTextMapper()
{
DiscriminatorValue(typeof(SetText).Name);
Map(x => x.Text).Column("Arg1").CustomType<StringArgType>();
}
}
/// <summary>NHibernate class mapping file for <see cref="Sleep"/>.</summary>
internal sealed class SleepMapper : SubclassMap<Sleep>
{
public SleepMapper()
{
DiscriminatorValue(typeof(Sleep).Name);
Map(x => x.Duration).Column("Arg1").CustomType<TimeSpanArgType>();
}
}
internal class StringArgType : BaseImmutableUserType<String>
{
public override SqlType[] SqlTypes
{
// All arguments map to strings in the database
get { return new[] {new SqlType(DbType.String)}; }
}
public override object NullSafeGet(IDataReader Reader, string[] Names, object Owner)
{
return NHibernateUtil.String.NullSafeGet(Reader, Names[0]).As<String>();
}
public override void NullSafeSet(IDbCommand Command, object Value, int Index)
{
NHibernateUtil.String.NullSafeSet(Command, Value, Index);
}
}
internal class TimeSpanArgType : BaseImmutableUserType<TimeSpan>
{
public override SqlType[] SqlTypes
{
// All arguments map to strings in the database
get { return new[] {new SqlType(DbType.String)}; }
}
public override object NullSafeGet(IDataReader Reader, string[] Names, object Owner)
{
return NHibernateUtil.TimeSpan.NullSafeGet(Reader, Names[0]).As<TimeSpan?>();
}
public override void NullSafeSet(IDbCommand Command, object Value, int Index)
{
object val = DBNull.Value;
if (Value != null)
{
TimeSpan timespan = (TimeSpan)Value;
val = timespan.Ticks;
}
NHibernateUtil.String.NullSafeSet(Command, val, Index);
}
}