1

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

我希望将来可以添加其他命令,以尽可能减少对数据库的影响。我最初的反应是使用按层次结构表的模式,因为唯一需要修改的模式是将一列添加到命令表中。

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

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

4

3 回答 3

1

查看一个BaseImmutableUserType<T> : IUserType实现,这将允许您使用通用列。

于 2012-11-02T22:06:33.147 回答
0

IMO,您最好的选择是使用包含单个 XML 类型列的表,而不是在可能会或可能不会模拟实际域问题的继承层次结构中烘焙。众所周知,XML 列是一种发展数据模型的无摩擦方式,尤其是您预计随着时间的推移可能会发生显着变化的数据模型。

XML 列可以存储将您的命令对象表示为由 .NET BCL 类序列化或使用您自己的自定义 XML 序列化程序(请参阅 参考资料)进行序列化所需的整个对象图IXmlSerializable

NHibernate 本身支持 XML SQL Server 列类型。谷歌搜索应该带回几个如何进行映射等的例子。

于 2012-11-02T22:19:40.893 回答
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);
    }
}
于 2012-11-05T16:13:56.433 回答