【问题标题】:The right way to insert multiple records to a table using LINQ to Entities使用 LINQ to Entities 向表中插入多条记录的正确方法
【发布时间】:2014-04-24 17:01:50
【问题描述】:

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

方法一:

// 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();

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

方法二:

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

看了一小会后,有几个人提到如果使用方法二,只会在表中添加一条记录。这似乎违反直觉。加载新插入的是 Add() 函数,并且我认为,在每次调用后使用传入的数据创建一个对象。在循环中声明我的 Product 对象 outside 似乎可以更好地利用资源,因为每次调用所消耗的唯一开销是对象实例属性的重新分配,而不是对象实例本身的重新构造。

谁能解释一下?我找不到另一个直接处理这个问题的帖子。如果有,请指出来。

【问题讨论】:

    标签: c# entity-framework ado.net linq-to-entities


    【解决方案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 会如您所愿保存所有不同的实体。

    【讨论】:

    • 我听到你在说什么。您能否添加一个参考来演示 Add() 方法的工作原理?
    • 5 个月后,我又回到了同样的问题。由于我完全忘记了我问过这个问题,所以我在谷歌上搜索了这个答案。它解决了我的问题(再次:-))。当我去投票你的答案和问题时,我意识到这是我的问题。哈哈!很抱歉花了这么长时间才选择您的回答作为答案。再次感谢!!
    • 示例很棒。解决了我的批量插入问题。
    【解决方案2】:

    +1 对于特里特的回答。您需要坚持使用方法一或类似的方法。

    在实体框架 6 版本中,有一种新方法可以在单个语句中添加一组数据。这是AddRange Method

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

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

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

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

    如果性能很重要,那么这个问题可能会为您提供更多信息:Fastest Way of Inserting in Entity Framework

    希望这能给你一些帮助。

    【讨论】:

      【解决方案3】:

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

      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 中添加了第一条记录。问题是我需要在添加多条记录后在 foreach 循环之外调用我的 db.SaveChanges() 。对我来说,我的问题的答案实际上就在问题中。所以,我赞成这个问题。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多