1

NHibernate を使用してデータベースに「コマンド」を保存するアプリケーションに取り組んでいます。各コマンドには一連の引数があります。たとえば、「sleep」コマンドには持続時間があり、「set text」には文字列値があります。これらのコマンドはすべて、同じCommand基本タイプから派生します。

データベースへの影響を最小限に抑えながら、将来的にコマンドを追加できるようにしたいと考えています。私の最初の反応は、階層ごとのテーブルパターンを使用することです。必要なスキーマの変更は、コマンド テーブルに列を追加することだけだからです。

また、TPH パターンを使用することも検討しましたが、特定の列ではなく汎用列をマッピングし、それらをクラス自体の特定の (厳密に型指定された) プロパティ値に変換します (つまり、マップされた汎用文字列プロパティを厳密に型指定されたパブリック プロパティでシャドウします)。 . そうすれば、コマンドが必要とする可能性のあるほとんどの引数に等しい列数があれば、テーブルをまったく変更する必要がありません。これらは私の頭の中では少しハックに思えましたが...

データベース設計と NHibernate の使用に比較的慣れていないので、誰かがこれらのアプローチの穴を指摘したり、より良い方法を提案したりできますか? 将来の拡張と単純な C# API を可能にしながら、スキーマの変更を (可能な限り) 回避しようとしています。

4

3 に答える 3

1

実装を調べてくださいBaseImmutableUserType<T> : IUserType。これにより、汎用列を使用できるようになります。

于 2012-11-02T22:06:33.147 に答える
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 に答える
0

IMO、最善の策は、実際のドメインの問題をモデル化する場合とモデル化しない場合がある継承階層を焼き込むのではなく、単一の XML 型の列を含むテーブルを使用することです。XML 列は、データ モデルを進化させる摩擦のない方法であることがよく知られています。特に、時間の経過とともに大幅に変化すると予想されるデータ モデルはそうです。

XML 列には、コマンド オブジェクトを表すために必要なオブジェクト グラフ全体を、.NET BCL クラスによって、または独自のカスタム XML シリアライザーを使用してシリアル化されたものとして格納できます (「 」を参照IXmlSerializable)。

NHibernate は、XML SQL Server 列タイプをネイティブにサポートします。グーグルは、マッピングなどを行う方法のいくつかの例を返すはずです.

于 2012-11-02T22:19:40.893 に答える