foreach循环中的异步调用

时间:2018-08-14 12:48:45

标签: c# entity-framework asynchronous async-await

更多关于概念的问题... 是否有任何理由在这样的foreach循环中异步调用我的DataContext是一个坏主意?

private async Task ProcessItems(List<Item> items)
{
    var modifiedItems = new List<modifiedItem>();

    foreach (var item in items)
    {
        // **edited to reflect link between items and properties**
        // var properties = await _context.Properties
        //   .Where(p => p.Condition == true).ToListAsync();
        var properties = await _context.Properties
          .Where(p => p.Condition == item.Condition).ToListAsync();

        foreach (var property in properties)
        {
            // do something to modify 'item'
            // based on the value of 'property'
            // save to variable 'modifiedItem'

            modifiedItems.Add(modifiedItem)
        }
    }

    await _context.ModifiedItems.AddRangeAsync(modifiedItems);
    await _context.SaveChangesAsync();
}

由于内部的foreach循环依赖于properties变量,所以直到properties变量被完全实例化之后它才开始吗?

由于modifiedItems变量是在父foreach循环之外声明的,将每个modifiedItem异步添加到modifiedItems列表中是个坏主意吗?

Linq实体框架中是否有任何属性/方法,哪种更适合此类任务?比做嵌入式foreach循环更好的主意?

(如果有人想要一些背景信息,... IRL,items是传感器的读数列表。properties是将原始读数转换为有意义的数据(例如体积和重量)的数学方程式以不同的单位...然后将这些计算出的数据点存储在数据库中。)

4 个答案:

答案 0 :(得分:4)

没有,但是您在这里错过了一些概念。

由于使用的是异步方法,因此ProcessItems方法应称为ProcessItemsAsync并返回task

这对您有用:Async/Await - Best Practices in Asynchronous Programming

根据您的需求,建议添加CancellationToken并考虑异常处理,请注意不要swallow exceptions

答案 1 :(得分:1)

这里使用异步没有问题,一旦您等待异步调用,返回的对象就相同。

如果您可以在循环外运行一次DB调用,并过滤内存中的数据以对其执行操作,则可能需要重新考虑在foreach中执行DB调用。每个用例都不同,您必须确保可以在内存中处理更大的返回集。

通常一次从数据库中获取1000行的速度比10行中的100行的速度快。

答案 2 :(得分:1)

在这种情况下,我写它来为所有感兴趣的传感器加载所有属性的单个列表(考虑所有items并基于您的macAddress / sensorKey您提到的属性),并将其存储在列表中。我们将其称为allProperties。我们await一次,避免重复进行数据库调用。

然后,使用LINQ To Objects将items连接到allProperties中匹配的对象。迭代该联接的结果,并且在循环内无需执行await

答案 3 :(得分:0)

是的,这是个坏主意。

您建议的方法按item的顺序对每个items执行一个数据库查询。除了效率低下外,还可能导致结果集不一致。可能有人在您的第一次查询和最后一次查询之间更改了数据库,因此第一个返回的对象与最后一次查询的结果属于不同的数据库状态。

一个简化的示例:让我们查询人员及其工作所在的公司的名称:

Table of Companies:
Id    Name
 1    "Better Bakery"
 2    "Google Inc"

Table of Persons
Id    Name    CompanyId
 1    "John"     1
 2    "Pete"     1

假设我想要Id的{​​{1}} / Name / CompnanyName与ID {1,2}

在您的第一个查询和第二个查询之间,有人将面包店的名称更改为最佳面包店。您的结果将是:

Persons

突然约翰和皮特在不同的公司工作?

如果您要求ID为{1,1}的PersonId Name CompanyName 1 "John" "Better Bakery" 2 "Pete" "Best Bakery" 将会是什么结果?同一个人会为两家不同的公司工作吗?

当然这不是您想要的!

除了结果不正确之外,如果数据库知道您需要多个项目的结果,则可以改进其查询。数据库创建临时表以计算结果。这些表只需要生成一次。

因此,如果您想要一组表示查询时数据状态的一致数据,请使用一个查询创建数据:

在一个数据库查询中的结果相同:

Persons

换句话说:从var itemConditions = items.Select(item => item.Condition); IQueryable<Property> queryProperties = dbContext.Properties .Where(property => itemConditions.Contains(property.Condition); 条件输入序列中的每个item开始。

然后查询数据库表Items (in your case a list), take the property。在表中仅保留具有{{1}中的Properties之一的Condition的行。

请注意,尚未执行任何查询或枚举。尚未访问数据库。 Condidions中只有itemConditions被修改。

要执行查询:

Expression

如果需要,可以将这些语句连接成一个大的LINQ语句。我怀疑这是否会提高性能,是否肯定会降低可读性,从而降低可测试性/可维护性。

在一个数据库查询中获取了所有所需的属性后,就可以对其进行更改:

IQueryable

只要保持List<Property> fetchedProperties = await queryProperties.ToListAsync(); 有效,它就会记住foreach (Property fetchedProperty in fetchedProperties) { // change some values } await dbContext.SaveChangesAsync(); 中所有表中所有已提取行的状态。这是dbContext的序列。每获取的行一个对象。每个dbContext.ChangeTracker.Entries都保留原始的获取值和修改后的值。它们用于标识哪些项目已更改,因此需要在DbEntityEntries

期间保存在一个数据库事务中

因此,几乎不需要主动通知DbEntityEntry您已更改获取的对象

我看到的唯一用例是,当您想在不先添加或获取对象的情况下更改对象时,这可能很危险,因为该对象的某些属性可能会被其他人更改。

您可以在

中看到它