web-dev-qa-db-ja.com

LINQ to Entitiesを使用して複数のレコードをテーブルに挿入する正しい方法

私たちの多くが行ったように、データベースから複数のレコードを追加する単純なループを設定しました。プロトタイプの例は次のようになります。

方法I:

// A list of product prices
List<int> prices = new List<int> { 1, 2, 3 };

NorthwindEntities NWEntities = new NorthwindEntities();

foreach (int price in prices)
{
   Product newProduct = new Product();
   newProduct.Price = price;
   NWEntities.Products.AddObject(newProduct);
}

NWEntities.SaveChanges();

ただし、最初にループを設定したとき、直感的に次のように書きました。

方法II:

Product newProduct = new Product();

foreach (int price in prices)
{
   newProduct.Price = price;
   NWEntities.Products.Add(newProduct);
}

少し読んだ後、数人がMethod IIを使用すると、1つのレコードだけがテーブルに追加されると述べました。これは直感に反するようです。新しい挿入をロードするのはAdd()関数です。各呼び出しの後に、渡されたデータを使用してオブジェクトを作成すると思います。Productオブジェクトの宣言outside各呼び出しで消費されるオーバーヘッドはオブジェクトインスタンス自体の再構築ではなく、オブジェクトインスタンスプロパティの再割り当てのみであるため、ループはリソースをより有効に使用するように思われます。

誰でも明確にできますか?この質問に直接対処する別の投稿を見つけることができませんでした。誰かがそこにいるなら、それを指してください。

26
seebiscuit

新しい製品のインスタンス化をループ内に移動するだけです。作成されたコードは、単一のインスタンスを複数回追加しますが、これは後のものを生成しません...各製品の個別のインスタンスが必要です... Addメソッドはコピーを作成せず、オブジェクトをコンテキストおよび挿入用のマーク。

foreach (int price in prices)
{
   Product newProduct = new Product();
   newProduct.Price = price;
   NWEntities.Products.Add(newProduct);
}

何が起こっているかをもう少し明示的に確認するには、次のことを考慮してください。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Try to reuse same Instance:");
        using (var ctx = new AdventureWorksEntities())
        {
            List<int> ids = new List<int> {1, 2, 3}; 
            Product p1 = new Product();
            Product reference = p1;
            Product p2;
            Console.WriteLine("Start Count: {0}", ctx.Products.Count());
            foreach (var id in ids)
            {
                p1.ProductID = id;
                p2 = ctx.Products.Add(p1);
                Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
                Console.WriteLine("p2 = reference? {0}", p2 == reference);
                Console.WriteLine("State: {0}", ctx.Entry(p1).State);
                var changes = ctx.ChangeTracker.Entries<Product>();
                Console.WriteLine("Change Count: {0}", changes.Count());
            }
        }
        Console.WriteLine();
        Console.WriteLine("Distinct Instances:");
        using (var ctx = new AdventureWorksEntities())
        {
            List<int> ids = new List<int> { 1, 2, 3 };
            Product p2;
            foreach (var id in ids)
            {
                var p1 = new Product {ProductID = id};
                p2 = ctx.Products.Add(p1);
                Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
                Console.WriteLine("State: {0}", ctx.Entry(p1).State);
                var changes = ctx.ChangeTracker.Entries<Product>();
                Console.WriteLine("Change Count: {0}", changes.Count());
            }
        }

        Console.ReadLine();
    }
}

最初のループでは同じ製品インスタンスを再利用していますが、それをコンテキストに追加すると、毎回同じ参照を使用しているだけです。ループの実行回数に関係なく、変更カウントが1のままであることがわかります。もちろん、ctx.SaveChanges()を呼び出す場合、最後の値のみが保存されます。

2番目のバージョンでは、変更カウントは毎回正しく増分され、SaveChangesを呼び出すと、予想どおりにすべての個別のエンティティが保存されます。

24
terryt

+1テリーの答え。方法1または類似の方法に固執する必要があります。

Entity Framework 6バージョンでは、1つのステートメントにデータセットを追加する新しいメソッドがあります。これは AddRangeメソッド です。

既存のリスト(またはIEnumerable)に基づいてエンティティを追加する場合、AddRangeメソッドがエレガントであることがわかります。

あなたの場合、それは次のようにいくらかできます:

NWEntities.Products.AddRange(
    Prices.Select(priceitem =>
    new Product{price = priceitem})
)

意味的には、これはメソッド1に似ているはずです。価格リストの価格ごとに1つのProductオブジェクトがインスタンス化されます。ただし、1つの違いがあり、匿名で行われるため、新しいオブジェクトを指す明示的に定義された参照変数はありません。

パフォーマンスが重要な場合、この質問はさらに情報を提供する可能性があります。 Entity Frameworkに挿入する最も速い方法

これがあなたにいくらかの助けを与えることを願っています。

14
karstenols

ループの助けは必要ありません。 linqでこれを行うことができます。以下のコードのように、名前はビットフィールドIsDeletedを含むnameListからEmployeeテーブルに追加する必要があります。

db.Employee.AddRange(
   nameList.Select(name =>
      new Employee
      {
           Name = name,
           IsDeleted = false
      })
   );
1

同様の問題がありました。私の問題では、このコードがありました:

        var cratelist = db.TruckContainerLoads.Where(x => x.TruckID == truckid).Select(x => x.ContainerID);
        if (!cratelist.Any())
        {
            return;
        }
        foreach (var crateid in cratelist) {
            TruckContainerLoad crInstance = new TruckContainerLoad();
            crInstance.ContainerID = crateid;
            try
            {
                db.TruckContainerLoads.Add(crInstance);
                db.SaveChanges();
            }
            catch
            {
                return;
            }
        }

私のクエリは、foreachの最初のレコードのみを追加しました。問題は、複数のレコードを追加した後、foreachループの外側でdb.SaveChanges()を呼び出す必要があることでした。私にとって、私の問題への答えは実際には問題にありました。だから、私は質問に賛成です。

0
Patrick Knott