代码之家  ›  专栏  ›  技术社区  ›  JED

foreach循环中的异步调用

  •  2
  • JED  · 技术社区  · 6 年前

    更多的是一个概念问题。。。 在这样的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 变量,直到 属性

    就是那个 modifiedItems 变量声明在父foreach循环之外,异步添加每个 modifiedItem 修饰语 名单?

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

    (如果有人想了解一些情况。。。爱尔兰共和国, items 属性

    4 回复  |  直到 6 年前
        1
  •  4
  •   Barr J    6 年前

    不,没有,但是你忽略了一些概念。

    因为您使用异步方法,所以 ProcessItems 应调用方法 ProcessItemsAsync 然后返回一个 task

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

    CancellationToken 还要考虑异常处理,只是要注意不要 swallow exceptions .

        2
  •  1
  •   Richard Hubley    6 年前

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

    通常,一次从DB获得1000行比10次获得100行快。

        3
  •  1
  •   Damien_The_Unbeliever    6 年前

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

    然后,使用LINQ To Objects加入 项目 到中的对象 他们匹配。迭代那个连接的结果 等待 在圈内做。

        4
  •  0
  •   Harald Coppoolse    6 年前

    您建议的方法,在每个 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 / Name CompnanyName 属于 Persons 带Id{1,2}

    在第一次和第二次查询之间,有人将面包店的名称更改为 你的结果是:

    PersonId  Name     CompanyName
        1    "John"   "Better Bakery"
        2    "Pete"   "Best Bakery"
    

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

    如果你要求 身份证是{1,1}?同一个人会为两个不同的公司工作?

    当然这不是你想要的!

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

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

    var itemConditions = items.Select(item => item.Condition);
    
    IQueryable<Property> queryProperties = dbContext.Properties
        .Where(property => itemConditions.Contains(property.Condition);
    

    换句话说:来自 在你的输入序列中 Items (in your case a list), take the property

    然后查询数据库表 Properties . 只保留此表中具有 Condition 那是 Condidions itemConditions .

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

    List<Property> fetchedProperties = await queryProperties.ToListAsync();
    

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

    在一个数据库查询中获取所有所需属性后,可以更改它们:

    foreach (Property fetchedProperty in fetchedProperties)
    {
          // change some values
    }
    await dbContext.SaveChangesAsync();
    

    只要你保持 dbContext dbContext.ChangeTracker.Entries . 这是一系列 DbEntityEntries . 每个获取的行一个对象。每 DbEntityEntry 保存原始获取的值和修改的值。它们用于标识哪些项已更改,因此需要在 Savechanges()

    因此,很少需要主动通知 DbContext

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