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

时间:2014-03-18 19:26:28

标签: c# entity-framework ado.net 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对象之外的循环似乎为了更好地利用资源,因为每次调用中消耗的唯一开销是对象实例属性的重新分配,而不是对象实例本身的重构。

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

4 个答案:

答案 0 :(得分:24)

只需在循环中移动新产品的实例化即可。您编写的代码将多次添加单个实例,这不会产生您所需的内容...您需要每个产品的单独实例... 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将保存所有不同的实体,如您所料。

答案 1 :(得分:14)

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

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

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

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

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

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

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

希望这会给你一些帮助。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

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

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

相关问题