每个层次的表和未来的证明

时间:2012-11-02 21:50:19

标签: c# nhibernate database-design class-table-inheritance

我正在开发一个应用程序,它将使用NHibernate将“命令”存储在数据库中。每个命令都有一组参数。例如,“sleep”命令具有持续时间,“set text”具有字符串值。所有这些命令都来自相同的Command基类型。

我希望将来允许添加其他命令,同时尽可能减少对数据库的影响。我最初的反应是使用table-per-hierarchy模式,因为所需的唯一模式修改是将一列添加到Command表中。

我还考虑过使用TPH模式但是映射通用列而不是特定列,然后将它们转换为类本身中的特定(强类型)属性值(即使用强类型映射映射的通用字符串属性)公共财产)。这样,如果我的列数等于任何命令可能需要的大多数参数,我根本不需要更改表。虽然这些在我的脑海里看起来有些神奇......

作为数据库设计和NHibernate使用的相对新手,有人可以指出这些方法中的漏洞,或建议更好的东西吗?我试图避免更改架构(尽可能多)允许将来扩展和简单的C#API。

3 个答案:

答案 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);
    }
}