Подтвердить что ты не робот

Правильный способ вставки нескольких записей в таблицу с использованием 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);
}

После небольшого чтения несколько человек отметили, что если используется Метод II, в таблицу будет добавлена ​​только одна запись. Это похоже на интуицию. Это функция Add(), которая загружает новую вставку и, как мне кажется, создает объект после каждого вызова с переданными данными. Объявление объекта Product вне цикла будет лучше использовать ресурсы, поскольку только накладные расходы, потребленные в каждый вызов будет повторным назначением свойства экземпляра объекта, а не повторной конструкцией самого объекта объекта.

Кто-нибудь может прояснить? Я не мог найти другую должность, которая напрямую связана с этим вопросом. Если кто-то там, укажите на него.

4b9b3361

Ответ 1

Просто переместите экземпляр нового продукта внутри цикла. Ваш код, как он будет написан, добавит один экземпляр несколько раз, что не приведет к тому, что вы после... вам нужен отдельный экземпляр каждого продукта... метод 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().

Во второй версии счетчик изменений правильно увеличивается каждый раз, и вы вызываете SaveChanges, чтобы сохранить все отдельные объекты, как вы ожидали.

Ответ 2

+1 Для ответа Терри. Вам нужно придерживаться метода один или что-то подобное.

В версии Entity framework 6 существует новый метод добавления набора данных в один оператор. Это AddRange Method.

Я хотел бы добавить, что я считаю метод AddRange элегантным, если вы хотите добавить сущности на основе существующего списка (или IEnumerable).

В вашем случае это можно сделать следующим образом:

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

Семантически это должно быть похоже на ваш метод 1. Один объект продукта определяется по цене в прейскуранте. Однако есть одно отличие: анонимно, поэтому нет явных определенных ссылочных переменных, указывающих на новый объект.

Если производительность важна, этот вопрос может дать вам дополнительную информацию: Самый быстрый способ вставки в инфраструктуру Entity

Надеюсь, вам это поможет.

Ответ 3

Нам не нужна помощь цикла. Мы можем сделать это с помощью linq. Как и в приведенном ниже коде, имена должны быть добавлены в таблицу Employee из nameList с полем бит IsDeleted.

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

Ответ 4

У меня была аналогичная проблема. В моей проблеме у меня был этот код:

        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. Проблема заключалась в том, что мне нужно было вызвать мой db.SaveChanges() вне цикла foreach после добавления нескольких записей. Для меня ответ на мой вопрос был фактически в вопросе. Итак, я поднимаю вопрос.