web-dev-qa-db-ja.com

データベースに保存された親/子オブジェクトの変更を記録するための設計パターン

親子関係に1つのデータベーステーブルが2つあります。

これら2つのテーブルのデータを表す3つのクラスがあります。

Parent Class
{
    Public int ID {get; set;}
    .. other properties
}

Child Class
{
    Public int ID {get;set;}
    Public int ParentID {get; set;}
    .. other properties
}

TogetherClass
{
    Public Parent Parent;
    Public List<Child> ChildList;
}

最後に、クライアントとサーバーのアプリケーションを用意しました。私は両端を制御できるので、必要に応じて両方のプログラムを変更できます。

クライアントはParentIDを要求し、一致する親のTogetherクラスとすべての子レコードを受け取ります。

クライアントアプリは子に変更を加える場合があります。新しい子の追加、既存の子の削除または変更を行います。次に、クライアントアプリはTogetherクラスをサーバーアプリに送り返します。

サーバーアプリは、データベースの親レコードと子レコードを更新する必要があります。

さらに、変更をログに記録できるようにしたいと思います。これは、親用と子用の2つの別々のテーブルを用意することで実現しています。各列には、元の列と変更された日付時刻、誰が変更したか、および変更のリストと同じ列が含まれています。

新しいレコード、削除するレコード、フィールドが変更されていないレコード、一部のフィールドが変更されたレコードなど、レコードの変更を検出するための最良の方法がわかりません。

親と子のレコードを読んで、それらをTogetherクラスのレコードと比較する必要があると思います。

戦略A:

Togetherクラスの子レコードのIDが0の場合、それは新しいレコードを示します。インサート。削除された子レコードはTogetherクラスに含まれなくなります。 Togetherクラスに比較子レコードが見つからないかどうかを確認し、見つからない場合は削除します(IDを使用して比較)。

各子レコードで変更を確認し、変更されている場合はログを確認します。

戦略B:新しい更新済みTogetherClassを作成する

UpdatedClass
{
    Public Parent Parent {get; set}
    Public List<Child> ListNewChild {get;set;}
    Public List<Child> DeletedChild {get;set;}
    Public List<Child> ExistingChild {get;set;} // used for no changes and modified rows
}

そして、リストに従って処理します。

私がアイデアを求めているのは、これらのソリューションはどちらも私には最適ではないようで、この問題はすでに解決されているのではないかと思います。ある種のデザインパターンですか?

この一般的なアプローチには、クライアントアプリAがレコードを要求するという潜在的な問題が1つあります。アプリBは同じレコードをリクエストします。次に、変更を保存します。次にBは、Aが行った変更を上書きする可能性のある変更を保存します。これは別のロックの問題であり、実装に問題がある場合は別の質問をします。

実際の実装はc#、SQL Server、クライアントとサーバー間のWCFであり、クラスの実装を含むライブラリを共有しています。

これが重複する投稿である場合はお詫び–一致するものを見つけることなく、さまざまな用語を検索してみました。


SvickとTimGからのコメントに基づいて更新:

変更を追跡するには、基本クラスは次のようになります。

public class ChangedRowBase
    {
        public enum StatusState
        {
        New, Unchanged, Updated, Deleted
    };

    protected StatusState _Status;

    public StatusState Status
    {
        get
        {
            return _Status;
        }
    }

    public void SetUnchanged()
    {
        _Status = StatusState.Unchanged;
    }

    public void SetDeleted()
    {
        _Status = StatusState.Deleted;
    }

}

次に、私の子クラスは次のようなものになります:

    public int SomeDumbProperty
    {
        get;
        set
        {
            base.Status = StatusState.Updated;
            // Missing line here to set the property itself
        }
    }

    private int _SomeIntelligentProperty;

    public int SomeIntelligentProperty
    {
        get { return _SomeIntelligentProperty; }

        set
        {
            if (value != _SomeIntelligentProperty)
            {
                _SomeIntelligentProperty = value;
                base.Status = StatusState.Updated;
            }
        }
    }

インテリジェントなものが実際に変更があったかどうかを検出するという考えです。それ以外の場合、クライアントアプリは、変更を検出して変更があった場合にのみ更新を行うか、セッターを使用して、実際には発生しなかった変更にフラグを立てる必要があります。

また、現在のところ、自分のダムプロパティが機能しない(またはコンパイルできない)ことも知っています。ゲッターまたはセッターでコードが必要な場合、これは別のプライベート変数を自分で定義する必要があることを意味しますか?私は短い手を知っています{get; set;}これを舞台裏で行います。

5
andrew

これらのシナリオを処理するための一般的な設計パターンを次に示します。

基本クラスを設定します。子と親はベースから継承します。基本クラスは次のことを行う必要があります。

  1. オブジェクトが{変更されていない、新しい、更新されている、削除されている}かどうかを追跡する.Statusプロパティ(読み取り専用)があります。ステータスは{コンストラクタ、.delete()メソッド、各プロパティのセッター}で設定されます。ステータスが「変更なし」のオブジェクトインスタンスを生成するファクトリメソッド、またはそれ自体を「変更なし」としてマークするメソッドが必要です。
    ところで。このアプローチは、オブジェクトが自己完結型であり、オブジェクト自身の変更を追跡するため、オブジェクトをより「アトミック」にします。これは、人々がパブリック変数の代わりにプロパティ(ゲッター/セッター)を使用する理由の例でもあります。

  2. 一般的に使用される疑似ロックメカニズムは、各DBテーブル(非コードテーブル/静的テーブル)で.LastModifiedDateTime(読み取り専用)列と.LastModifier(読み取り専用)列の組み合わせを使用することです。これらは、コンストラクタ/ファクトリメソッドでも設定されます。更新/保存時に、現在のLastModifiedDateTime&LastModifierをDBと比較します。それらが一致しない場合、この変更を拒否します。

#1は、多くのORMが状態を維持する(変更を処理する)方法に似ています。 #2は、しばしば「ソフトロックメカニズム」と呼ばれます。

1
TimG

ここでの良い解決策は、クライアントモデルがそれ自体への変更を検出し、変更のみをサーバーに送信することだと思います。

つまり、プロパティのセッターは、少なくとも何かが変更されたことを記録する必要があります。そして、コレクションは単純な_List<T>_ sにすることはできませんが、変更に対してカスタムの反応を持たせることができます。 (これには、InsertItem()などのオーバーライドされたメソッドで _Collection<T>_ を使用できます。)

0
svick