繁体   English   中英

如何让实体框架对每条记录使用 1 个更新查询而不是 1 个?

[英]How to make Entity Framework use 1 update query instead of 1 for each record?

我有这个代码:

var query = _calendarDayRepo.Queryable().Where(x => x.Date >= calendarDayDto.Date);

foreach (var calendarDay in query)
{
    calendarDay.WorkingDayCounter += (updatedEntityAdd ? 1 : -1);
}

await _calendarDayRepo.SaveChangesAsync();  

我正在使用 SQL Server 数据库。

目前,EF 将执行查询以更新每条记录,但我相信如果 EF 仅生成 1 个查询以一次更新所有记录,性能会更高。

我怎样才能做到这一点?

使用实体框架时,有两种获取数据的方法。 只有其中之一用于获取需要更新的项目。

如果您使用Select查询数据,则使用 SQL Select ... From ...等执行查询。获取的数据将返回给您。 DbContext 不会保存这些数据。 如果您不打算更新获取的数据,则此方法是首选方法,因为它是此用途最有效的方法

如果要使用实体框架更新项目,正确的方法是获取要更新的数据,更新需要更改的属性并调用SaveChanges 后者将进行实际更新。

要使用这种方法,您需要查询竞争对象,而不是使用 Select。 DbContext 有一个 ChangeTracker。 如果您执行这样的查询,所获取的对象将被放入 ChangeTracker 中,以及它的一个副本。 你得到了对副本的引用(或者可能是原始的,哪个无关紧要)。 因为您有引用,更改属性将更改 ChangeTracker 中的副本。 当您调用 SaveChanges 时,ChangeTracker 中的所有原件都会按值与其副本进行比较。 更改将转换为 SQL Update ... Set ... Where ...语句。

到目前为止,您应该清楚地看到,在不使用 Select 的情况下获取您不打算更新的项目是没有效率的。

使用实体框架时,请始终使用Select来获取项目,并且仅选择您实际计划使用的属性。 只提取而不选择,如果您计划更新提取的项目,则仅使用Include

例子

假设您有一个包含客户、产品、订单等的数据库。客户和订单之间是一对多,产品和订单之间是一对多。

“给我所有尚未付款的订单的地址和其他一些信息,因为我想向他们发送提醒”

没有更新,使用选择。 仅获取您实际计划使用的项目。

DateTime dueDate = DateTime.Today - TimeSpan.FromDays(14); // 2 weeks ago
using (var dbContext = new OrderContext(...))
{
    var ordersThatNeedAReminder = dbContext.Orders
        .Where(order => !order.Paid && order.DueDate <= dueDate)
        .Select(order => new
        {
             Customer = new
             {
                 // only select the Customer Properties that you plan to use
                 Name = order.Customer.Name,
                 AddressLine1 = order.Customer.Address.AddressLine1,
                 ...

                 // probably not needed:
                 // Id = customer.Id
             },

             Order = new
             {
                 DeliveryDate = order.DeliveryDate,
                 PayDate = order.PayDate,
                 Amount = order.Total,
                 ...
             },
       })
       .ToList();

       ProcessNonPaidOrders(ordersThatNeedAReminder);
}

因为我使用Select来获取所有项目,所以获取的项目没有放在 ChangeTracker 中。

“所有割草机都需要调价”

using (var dbContext = new OrderContext(...))
{
    // Fetch the lawnMowers
    IEnumerable<Product> lawnMowers = dbContext.Products
        .Where(product => product.Type == ProductType.LawnMower)
        .ToList();

    UpdatePrice(lawnMowers, ...);    // not part of the question

    DbContext.SaveChanges();
}

因为在没有Select的情况下查询数据,获取的item和一个副本放在ChangeTracker中。 您将获得对副本的引用,该副本的属性 Price 已更改。 SaveChanges将比较 ChangeTracker 中所有项目的原始和副本,并将为所有更改的属性创建正确的 SQL 更新。

此方法是执行更新的首选方法。

  • 优点:在更改项目之前,您将获得该项目的最新版本。
  • 缺点:您必须获取完整的项目(不是通过外键引用的项目,除非它们还需要更新)。

有一种方法可以立即将更改项的值放入 ChangeTracker。

更新 Husqvarna 割草机的价格

long husqvarnaLawnMowerId = ...
Product lawnMowerHusqvarna = FetchProduct(husqvaranLawnMowerId);
decimal adjustedPrice = AskOperatorForNewPrice(lawnMowerHusqvarna);

lawnMowerHusqvarna.Price = adjustedPrice;

using (var dbContext = new OrderContext(...))
{
    dbContext.Products.Attach(lawnMowerHusqvarna);
    dbContext.Savechanges();
}

因此,如果您已经知道要更新的对象的所有字段的值,则不必先获取它们。

但请注意,其他人可能已经更改了一个或多个属性。 由于您获取了当前值,您将丢失自己或其他人所做的更改。

因此我不推荐这种方法。 通常,与查询次数相比,对数据库的更改次数相对较少。 通常在操作员输入之后进行更改,因此这些更改不必非常快。 我的建议是:不要使用这种方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM