繁体   English   中英

将异步与实体框架选择列表 IQueryable 一起使用<T>

[英]Using async with Entity Framework select list of type IQueryable<T>

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

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

    [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 的这个示例代码,但无法弄清楚什么是ProcessEventAsync以及如何将其应用于我的代码。 另外,我不能使用.ToList() ,这对于将所有数据加载到内存中太昂贵了。

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

如果您更仔细地查看 LINQ 语句,您会发现有两种类型:返回IQueryable (和IEnumerable )的类型,以及返回List<TResult>TResultsTKey等的类型,任何不是IQueryable/IEnumerable 如果返回值是一个IQueryable ,那么我们说该函数使用延迟执行(或延迟执行):执行查询的Expression已创建,但查询尚未执行。

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

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

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

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

结论:通常会返回IQueryable<...>所有函数都不需要 Async version ,所有返回实际获取数据的函数都需要 Async 版本

例子。 不需要异步:没有执行查询:

// 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);
}

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

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

public IQueryable<Profile> GetAll()

然后像这样消费它:

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

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

2)更改签名,并像ForeachAsync那样实现它(我从这个问题借用了示例,因为它提供了适当的等待。)

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));
        }
    }
}

恕我直言

保持这个方法不变。

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 操作,它执行 db 操作,因此您可以等待它。

类似问题

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

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

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

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

暂无
暂无

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

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