【问题标题】:Using async with Entity Framework select list of type IQueryable<T>将异步与实体框架一起使用 IQueryable<T> 类型的选择列表
【发布时间】:2019-02-03 19:05:09
【问题描述】:

我正在尝试将数据访问同步查询转换为异步,到目前为止,我转换了所有内容,除了返回IQueryable&lt;T&gt; 的选择列表。

这是我到目前为止所做的:

    [Dependency]
    public SampleContext db { get; set; }

    public async System.Threading.Tasks.Task<Profile> Add(Profile item)
    {
        db.Profiles.Add(item);
        await db.SaveChangesAsync();
        return item;
    }

    public async System.Threading.Tasks.Task<Profile> Get(string id)
    {
        return await db.Profiles.AsNoTracking().Where(i => i.Id == id).FirstOrDefaultAsync();
    }

    public async System.Threading.Tasks.Task Remove(string id)
    {
        Profile item = db.Profiles.Find(id);
        item.IsDeleted = 1;
        db.Entry(item).State = EntityState.Modified;
        await db.SaveChangesAsync();
    }

    public async System.Threading.Tasks.Task<bool> Update(Profile item)
    {
        db.Set<Profile>().AddOrUpdate(item);
        await db.SaveChangesAsync();
        return true;
    }

上面的代码效果很好,我一直在转换这段代码:

    public IQueryable<Profile> GetAll()
    {
        return db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0);
    }

如何将上述代码转换为异步代码?我尝试了 Stephen Cleary 的 this sample code,但无法弄清楚 ProcessEventAsync 是什么以及如何将其应用于我的代码。另外,我不能使用.ToList(),这太贵了,无法将所有数据加载到内存中。

【问题讨论】:

    标签: c# entity-framework linq asynchronous lambda


    【解决方案1】:

    您可以使用以下示例代码选择异步

    public async System.Threading.Tasks.Task<List<Profile>> GetAll()
        {
            return await  db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0).ToListAsync();
        }
    

    但是这个实现会解析SQL语句并在调用时执行它

    没有任何理由等待您尝试做的事情。 可查询是查询的表达式,这意味着只有当您调用 ToList() 时才对数据库实际执行任何操作,这意味着查询是在数据库上执行的。

    【讨论】:

    • 不要调用ToList 这将强制所有配置文件一次加载到内存中
    • 不能使用 ToList(),通话时长会变成 1 秒到 5 秒。
    • @Matthiee 我使用 ToListAsync();因为正如我从问题中了解到的那样,他想对全选进行异步调用,但我他想使用 Iqueryable 我没有任何意义,因为它不会执行,直到调用 ToListAsync();或 ToList();
    • ToListAsync() 也将数据加载到内存中,这是我知道的,所以我不能。
    • 是的,我在答案中提到过,但它在另一个线程的异步调用中执行,但如果他想使用 Iqueryable,则无需使用异步调用
    【解决方案2】:

    恕我直言

    保持这个方法不变。

    public IQueryable<Profile> GetAll()
    {
        return db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0);
    }
    

    像这样使用这个方法。每当您使用 GetAll() 时,添加 async/await。

    public async Task<List<Profile>> GetAllProfiles(int userId) {
     return await GetAll().Where(u => u.User.Id == userId).ToListAsync();
    }
    

    IQueryable 不会访问数据库,除非执行了 ToList(),FirstOrDefault() ... 某些操作。
    您不能向其中添加 async/await。
    相反,GetAllProfiles() 方法是一个 I/O 操作,它执行一个数据库操作,因此您可以等待它。

    Similar question

    【讨论】:

      【解决方案3】:

      您希望一次从数据库中读取一条记录,而不将所有记录都加载到内存中。同步,那只是一个 foreach。做同样的事情,但使用异步连接方法:

      1) 保留您的签名,并使用ForeachAsync 使用它

      public IQueryable<Profile> GetAll()
      

      然后像这样消费它:

      await repository.GetAll().ForeachAsync(record => DoThingsWithRecord(record));
      

      请注意,如果您将其设为异步,则实际上并不会等待此处传递的操作,请参阅下面的参考问题,如果您使用此方法,如何处理此问题

      2) 更改签名,并像ForeachAsync 一样实施(我从this question 借用了示例,因为它提供了适当的等待。)

      public async Task WithAll(Func<Profile, Task> profileAsync, CancellationToken cancellationToken) {
          var asyncEnumerable = (IDbAsyncEnumerable<Profile>)db.Profiles.AsNoTracking()
                                  .Where(i => i.IsDeleted == 0);
          using (var enumerator = asyncEnumerable.GetAsyncEnumerator())
          {
      
              if (await enumerator.MoveNextAsync(cancellationToken)
                      .ConfigureAwait(continueOnCapturedContext: false))
              {
                  Task<bool> moveNextTask;
                  do
                  {
                      var current = enumerator.Current;
                      moveNextTask = enumerator.MoveNextAsync(cancellationToken);
                      await profileAsync(current); //now with await
                  }
                  while (await moveNextTask.ConfigureAwait(continueOnCapturedContext: false));
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        您必须注意查询的差异和查询的结果。 IQueryable 包含执行查询的所有内容。它不是查询本身,创建 IQueryable 不会执行查询。

        如果您更仔细地查看 LINQ 语句,您会发现有两种类型:返回 IQueryable(和 IEnumerable)的类型,以及返回 List&lt;TResult&gt;TResults、@ 的类型987654326@等,任何不是IQueryable/IEnumerable的东西。如果返回值为IQueryable,那么我们说函数使用延迟执行(或延迟执行):执行查询的Expression已创建,但查询尚未执行。

        这样做的好处是您可以连接 LINQ 语句,而无需对每个语句执行查询。

        当您要求 IQueryable 获取枚举器并且如果您开始枚举时执行查询,无论是使用 foreach 隐式使用,还是使用 IQueryable.GetEnumerator()IEnumerator.MoveNext() 显式枚举(也由 foreach 调用)。

        所以只要你在创建一个查询并返回一个IQueryable,创建一个Task是没有用的。连接LINQ语句只会改变IQueryable的表达式,这不是你必须等待的。

        仅当您创建一个将实际执行查询的函数时,您才需要一个异步版本:ToListAsyncFirstOrDefaultAsyncMaxAsync 等。在内部,这些函数将 GetEnumeratorMoveNextAsync

        结论:所有正常返回的函数 IQueryable&lt;...&gt; 不需要异步版本,所有功能 返回实际获取的数据需要异步版本

        示例。不需要异步:不执行查询:

        // Query customer addresses:
        static IQueryable<Address> QueryAddresses(this IQueryable<Customer> customers)
        {
             return customers.Select(customer => customer.Address);
        }
        

        需要异步:

        static async Task<List<Address>> FetchAddressesAsync (this IQueryable<Customer> customers)
        {
             var query = customers.QueryAddresses;   // no query executed yet
             return await query.ToListAsync();       // execute the query
             // could of course be done in one statement
        }
        
        static async Task<Address> FetchAddressAsync(this.IQueryable<Customer> customers, int customerId)
        {
            var query = customers.Where(customer => customer.Id == customerId)
                                 .QueryAddresses();
            // no query executed yet!
            // execute:
            return await query.FirstOrDefaultAsync();
        }
        

        用法:

        int customerId = ...
        using (var dbContext = new InvoiceContext())
        {
             Address fetchedCustomerAddress = await dbContext.Customers
                 .FetchAddressAsync(customerId);
        }
        

        在极少数情况下,您必须自己列举,您将在 MoveNextAsync 中等待:

        IQueryable<Customer> myCustomers = ...
        IEnumerator<Customer> customerEnumerator = myCustomers.GetEnumerator();
        
        while (await customerEnumerator.MoveNextAsync())
        {
             Customer customer = customerEnumerator.Current;
             Process(customer);
        }
        

        【讨论】:

        • 我相信你想写static IQueryable&lt;Address&gt; QueryAddresses(this IQueryable&lt;Customer&gt; customers)而不是static IQueryable&lt;Address&gt; QueryAddresses(this IQueryable&lt;Address&gt; customers),就在声明示例的正下方。不需要异步:不执行查询:
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-22
        • 1970-01-01
        • 2015-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-14
        相关资源
        最近更新 更多