Правильный способ вставки нескольких записей в таблицу с использованием 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 вне цикла будет лучше использовать ресурсы, поскольку только накладные расходы, потребленные в каждый вызов будет повторным назначением свойства экземпляра объекта, а не повторной конструкцией самого объекта объекта.
Кто-нибудь может прояснить? Я не мог найти другую должность, которая напрямую связана с этим вопросом. Если кто-то там, укажите на него.
Ответы
Ответ 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 после добавления нескольких записей. Для меня ответ на мой вопрос был фактически в вопросе. Итак, я поднимаю вопрос.