C# 每个层次结构的表和未来的证明

C# 每个层次结构的表和未来的证明,c#,nhibernate,database-design,class-table-inheritance,C#,Nhibernate,Database Design,Class Table Inheritance,我正在开发一个应用程序,它将使用NHibernate在数据库中存储“命令”。每个命令都有一组参数。例如,“sleep”命令有一个持续时间,“set text”有一个字符串值。所有这些命令都源自相同的命令基本类型 我希望将来可以添加额外的命令,对数据库的影响尽可能小。我最初的反应是使用该模式,因为唯一需要修改的模式是向命令表中添加一列 我还考虑使用TPH模式,但映射泛型列而不是特定列,然后将它们转换为类本身中的特定(强类型)属性值(即使用强类型公共属性对映射的泛型字符串属性进行阴影处理)。这样,如

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

我希望将来可以添加额外的命令,对数据库的影响尽可能小。我最初的反应是使用该模式,因为唯一需要修改的模式是向命令表中添加一列

我还考虑使用TPH模式,但映射泛型列而不是特定列,然后将它们转换为类本身中的特定(强类型)属性值(即使用强类型公共属性对映射的泛型字符串属性进行阴影处理)。这样,如果我的列数等于任何命令可能需要的最大参数,我就根本不需要更改表。这些在我的脑海里似乎有点烦躁


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

查看
BaseImmutableUserType:IUserType
实现,这将允许您使用泛型列

查看
BaseImmutableUserType:IUserType
实现,这将允许您使用泛型列

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

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


NHibernate本机支持XML SQL Server列类型。谷歌搜索应该会带来一些如何进行映射的例子。在我看来,最好的办法是使用一个包含单个XML类型列的表,而不是在继承层次结构中烘焙,因为继承层次结构可能会或可能不会模拟实际的域问题。众所周知,XML列是发展数据模型的一种无摩擦的方式,尤其是您预期随着时间的推移可能会发生重大变化的数据模型

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


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

我最终从@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);
    }
}
的NHibernate类映射文件。 内部密封类ActionMapper:ClassMap { ///构造器。 公共ActionMapper() { DiscriminateSubClassesOnColumn(“类类型”).Not.Nullable(); Id(x=>x.Id); } } ///的NHibernate类映射文件。 内部密封类SetTextMapper:子类映射 { 公共SetTextMapper() { 鉴别器值(typeof(SetText).Name); Map(x=>x.Text).Column(“Arg1”).CustomType(); } } ///的NHibernate类映射文件。 内部密封类SleepMapper:子类映射 { 公共睡眠映射器() { 鉴别器值(typeof(Sleep).Name); Map(x=>x.Duration).Column(“Arg1”).CustomType(); } } 内部类StringArgType:BaseImmutableUserType { 公共重写SqlType[]SqlTypes { //所有参数都映射到数据库中的字符串 获取{returnnew[]{newsqltype(DbType.String)};} } 公共重写对象NullSafeGet(IDataReader读取器,字符串[]名称,对象所有者) { 返回NHibernateUtil.String.NullSafeGet(读取器,名称[0]).As(); } 公共覆盖无效NullSafeSet(IDbCommand命令、对象值、int索引) { NHibernateUtil.String.NullSafeSet(命令、值、索引); } } 内部类TimeSpanArgType:BaseImmutableUserType { 公共重写SqlType[]SqlTypes { //所有参数都映射到数据库中的字符串 获取{returnnew[]{newsqltype(DbType.String)};} } 公共重写对象NullSafeGet(IDataReader读取器,字符串[]名称,对象所有者) { 返回NHibernateUtil.TimeSpan.NullSafeGet(读取器,名称[0]).As(); } 公共覆盖无效NullSafeSet(IDbCommand命令、对象值、int索引) { 对象val=DBNull.Value; if(值!=null) { TimeSpan TimeSpan=(TimeSpan)值; val=timespan.Ticks; } NHibernateUtil.String.NullSafeSet(命令、val、索引); } }
我最终从@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);
    }
}
的NHibernate类映射文件。 内部密封类ActionMapper:ClassMap { ///构造器。 公共ActionMapper() { DiscriminateSubClassesOnColumn(“类类型”).Not.Nullable(); Id(x=>x.Id); } } ///的NHibernate类映射文件。 内部密封类SetTextMapper:子类映射 { 公共SetTextMapper() { 鉴别器值(typeof(SetText).Name); Map(x=>x.Text).Column(“Arg1”).CustomType(); } } ///的NHibernate类映射文件。 内密封c