使用LINQ to Entities将多个记录插入表的正确方法

正如我们许多人所做的那样,我设置了一个简单的循环来从数据库添加多个记录。 一个典型的例子是这样的:

方法一:

// A list of product prices List prices = new List { 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(); 

然而,当我第一次设置循环时,我直观地写道:

方法二:

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

在做了一点阅读之后,有几个人提到如果使用方法II,则只会在表格中添加一条记录。 这似乎与直觉相反。 它是Add()函数,它加载一个新的插入,我认为,在每次调用后传入的数据都会创建一个对象。在循环声明我的Product对象似乎更好地利用资源,因为它是消耗的唯一开销。每次调用都是对象实例属性的重新赋值,而不是对象实例本身的重构。

有人可以澄清一下吗? 我找不到另一个直接处理这个问题的post。 如果有人在那里请指出它。

只需在循环中移动新产品的实例化。 您编写的代码将多次添加单个实例,这不会产生您所需的内容…您需要每个产品的单独实例… 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 ids = new List {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(); Console.WriteLine("Change Count: {0}", changes.Count()); } } Console.WriteLine(); Console.WriteLine("Distinct Instances:"); using (var ctx = new AdventureWorksEntities()) { List ids = new List { 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(); Console.WriteLine("Change Count: {0}", changes.Count()); } } Console.ReadLine(); } } 

在第一个循环中,您将重用相同的产品实例,但是当您将其添加到上下文时,您每次只使用相同的引用。 无论循环执行多少次,您都可以看到更改计数保持为1。 当然,如果你要调用ctx.SaveChanges(),只会保存最后的值。

在第二个版本中,每次更改计数都会正确递增,并且您将调用SaveChanges将保存所有不同的实体,如您所料。

+1对于Terryt的回答。 你需要坚持方法一或类似的东西。

在Entity framework 6版本中,有一种新方法可以在一个语句中添加一组数据。 这是AddRange方法 。

我想补充一点,当你想根据现有列表(或IEnumerable)添加实体时,我发现AddRange方法很优雅。

在你的情况下,可以这样做:

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

在语义上,这应该类似于您的方法1.一个Product对象在价目表中按价格实例化。 但是有一个区别,它是匿名完成的,因此没有明确定义的引用变量指向新对象。

如果性能很重要,那么这个问题可能会为您提供更多信息: entity framework中最快的插入方式

希望这会给你一些帮助。

我们不需要循环的帮助。 我们可以通过linq做到这一点。 如下面的代码所示,名称必须从nameList添加到Employee表中,位字段为IsDeleted。

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

我有类似的问题。 在我的问题中,我有这个代码:

  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()。 对我来说,问题的答案实际上就在于问题。 所以,我正在提出这个问题。