web-dev-qa-db-ja.com

オブジェクトのコレクションのプロパティを条件付きで更新する良い方法は何ですか?

別のシステムを更新するために反復しているShipmentオブジェクトのコレクションがあります。シップメントのプロパティはストックルームです。ターゲットシステムには、ストックルームレコードを定義する一意のIDがあります。出荷のコレクションがあり、それらの一部が、一意のIDを格納するための主要な相互参照レコードをまだ記録していないStockroomに関連している場合、そのシステムのStockroomリソースで初めてGETを実行する必要があります。その後、キーの相互参照レコードを保存しますが、1)そのStockroomを使用するShipmentsコレクション内の残りのオブジェクトに、その一意のIDを通知し、2)追加のGETおよびキーを実行しないようにする必要もあります。同じストックルームの相互参照保存。

データ階層の質問のため、ここにプロパティとShipmentオブジェクトのコンストラクターの完全なリストを示します。

public string PopReceiptNumber { get; set; }
public int ReceiptLineNumber { get; set; }
public decimal QuantityShipped { get; set; }
public GPItem Item { get; set; }
public GPStockroom Stockroom { get; set; }

public GPShipment(DataRow row)
{
    PopReceiptNumber = row.Field<string>("POPRCTNM");
    ReceiptLineNumber = Int32.Parse(row.Field<string>("RCPTLNNM"));
    QuantityShipped = row.Field<decimal>("QTYSHPPD");
    Item = new GPItem(row);
    Stockroom = new GPStockroom(row);
}
public class GPStockroom
{
    public string Name { get; set; }
    public string Sys_ID { get; set; } // unique ID in target system
}

// Here is a snippet of code where pushing each Shipment happens
foreach (Shipment shipment in shipments)
{
    shipment.Integrate();
}

ShipmentクラスのIntegrate()メソッドには、ShipmentのStockroomのSys_IDが空の場合にターゲットシステムからGETを実行するコードが含まれています。同じStockroom Name値を持つShipmentsのコレクション内の残りのオブジェクトを識別し、それらのSys_IDを更新するための良い方法は何でしょうか。 StackOverflowでこの問題に対する回答がLINQの使用を提案しているのがわかりますが、これはベストプラクティスではないという強力な推奨事項も確認できます。明確にするための推奨事項は、foreachループを使用することです。私は既にコレクションの前向きなので、この質問をしています。

2
CodenameCain

私が理解しているように、Integrateの現在の実装は次のようになります。

public void Integrate()
{
    if (this.Stockroom.Sys_ID != null) return;
    if (this.Stockroom.Name == null) return;
    this.Stockroom.Sys_ID = _someService.GetSysID(this.Stockroom.Name);
}

私はこのアプローチに問題はないと思います。そのシンプルで明確、そして簡潔です。あなたにとっての問題は、同じストックルーム名でGetSysIDが複数回呼び出され、余分な呼び出しが不要になる可能性があることです。

考えられる解決策の1つは、単にキャッシュを導入することです。

static Dictionary<string,string> _cache = new Dictionary<string,string>();

public void Integrate()
{
    if (this.Stockroom.Sys_ID != null) return;
    if (this.Stockroom.Name == null) return;
    this.Stockroom.Sys_ID = GetSysIDWithCache(this.Stockroom.Name);
}

protected string GetSysIDWithCache(string stockroomName)
{
    string result;
    if (_cache.TryGetValue(stockroomName, out result)) return result;

    var id = _someService.GetSysID(stockroomName);
    _cache.Add(stockroomName, id);
    return id;
}

このようにして、元のロジックに複雑さを導入することを避け、代わりにキャッシュに依存して余分な呼び出しを回避します。

1
John Wu

重砲(ドメインイベント)を呼び出す前

工場からオブジェクトを渡されることを検討してください。次に、ファクトリは、重複するGPStockroomが作成されないようにすることができます。これは、プロパティを比較して、作成された新しいGPStockroomインスタンスへの参照を保持し、別のGPShipmentが参照する場合に同じGPStockroomを指定すると、ファクトリは同じ参照を再利用します。次に、Integrate()メソッド内で何をしても、この1つの共有GPStockroom参照がインスタンス化され、後続のGPShipmentオブジェクトがこのインスタンス化された単一の参照にアクセスします、すでにそこにあるプロパティを見つけ、DataRowの追加の「読み取り」を実行する必要はありません。

public class GPShipmentFactory
{
    //An additional suggestion is to make the GPShipment constructor
    //internal to force use of this factory instead of direct instantiation
    //of GPShipment objects.

    private Dictionary<string, GPStockroom> _uniqueStockrooms =
                                            new Dictionary<string, GPStockroom>();

    public static GPShipment CreateFromRow(DataRow row)
    {
        GPShipment shipment = new GPShipment();

        shipment.PopReceiptNumber = row.Field<string>("POPRCTNM");
        shipment.ReceiptLineNumber = Int32.Parse(row.Field<string>("RCPTLNNM"));
        shipment.QuantityShipped = row.Field<decimal>("QTYSHPPD");
        shipment.Item = new GPItem(row);

        //Check if the relevant row properties mean that it refers to an
        //identical Stockroom. For example if you want to use ID and the
        //field's name is STCKRMID:
        string stockroomID = row.Field<string>("STCKRMID");

        if (_createdStockrooms.ContainsKey(stockroomID)
        {
            //If a GPStockroom instance has already been created for this
            //ID, then there is no need to create a second one, just pass
            //the same reference.
            shipment.Stockroom = _createdStockrooms[stockroomID];
        }
        else
        {
            GPStockroom stockroom = new GPStockroom(row);
            shipment.Stockroom = stockroom;
            _uniqueStockrooms.Add(stockroomID, stockroom);
        }
    }
}

何らかの理由で絶対にmustを使用している場合、GPStockroomのインスタンスが複数あり、名前とSys_IDの文字列が同じである場合、それらを同期するもう1つの方法は、Dictionary<string, GPShipment>どこかで、Nameをキーとして使用します。次に、各GET操作で(つまり、)、同じ名前のすべてのGPShipmentインスタンスを直接取得し、同じSys_IDをそれらにアタッチできます。

オブジェクトを同期する最後の方法は、イベントを使用することです。しかし、私は、あなたの引用されたデザインから、これは少し伸びすぎると感じています。この機会にイベントを使用しても柔軟性は追加されません。柔軟性の向上を大幅に相殺するのに十分な複雑さを追加しながら、あまり良くないデザインの詳細を修正するだけです。

0
Vector Zita

私が理解している問題をもう一度説明します。繰り返し処理してテーブルを更新するShipmentオブジェクトのコレクションがあります。

ShipmentクラスのプロパティはStockroomという名前です。

「ターゲット」テーブルには、倉庫の行を定義する一意のid列があります。

私は持っているのではなく、積荷のコレクションを持っています。一部の発送は倉庫に関連しています。いいえ、すべての出荷はストックルームに関連しています。そうでない場合、なぜストックルームプロパティが出荷クラスに埋め込まれているのですか? (懸念事項の分離やSRPのため、実際にはそこに埋め込むべきではありませんが、私はあなたのデザインを変更することはできないので、それはそれです。)

一部の出荷は、データベースの結合テーブルに保存されていません。それで、初めて、ストックルームテーブルからすべての一意のIDを取得します(そして、そのデータセットをクライアントに返します。痛いです!)

次に、結合テーブルに保存します。

ただし、1)コレクション内の一部の貨物に一意のIDを通知し、2)データベースの呼び出しと保存の重複を避けることも必要です。

提案されたソリューション:

想定データベーススキーマ

エンティティテーブル:出荷

結合テーブル:マニフェスト(出荷IDと製品ID、請求書番号、配達日)

エンティティテーブル:製品(ラインアイテム)

結合テーブル:配信(マニフェストID、製品IDおよびストックルームID、数量)

結合テーブル:在庫(ストックルームIDと製品ID、数量)

エンティティテーブル:ストックルーム

すべての行を配信テーブルまたは同等のものに挿入します。挿入したばかりの一部の配信テーブル行には、nullストックルームIDがあります。各イテレーションの複雑なクライアント側の代わりに、ビジネスルールで作成した単純なアルゴリズムを使用して、配信テーブルのnullストックルームIDの列を直接処理できるようになりました。

あなたがフォローアップしたいかもしれない他の提案された概念:単一の責任主体とデメテルの法則

0
James Axsom