简体   繁体   English

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

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

I'm trying to convert data access synchronous query to asynchronous, so far I converted everything except selecting list which returns IQueryable<T> .我正在尝试将数据访问同步查询转换为异步,到目前为止,我转换了除返回IQueryable<T>选择列表之外的所有内容。

Here's what I have done so far:这是我到目前为止所做的:

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

Above code works well, I'm stuck at converting this piece of code:上面的代码运行良好,我一直在转换这段代码:

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

How do I convert above code to asynchronous?如何将上面的代码转换为异步? I tried this sample code by Stephen Cleary but can't figure out what is ProcessEventAsync and how do I apply this to my code.我尝试了 Stephen Cleary 的这个示例代码,但无法弄清楚什么是ProcessEventAsync以及如何将其应用于我的代码。 Also, I can't use .ToList() , this will be too expensive to load all the data in memory.另外,我不能使用.ToList() ,这对于将所有数据加载到内存中太昂贵了。

You have to be aware between the difference of a query, and the result of the query.您必须了解查询的差异和查询的结果之间的差异。 An IQueryable holds everything to perform the query. IQueryable包含执行查询的所有内容。 It isn't the query itself, and creating an IQueryable doesn't perform the query.它不是查询本身,创建 IQueryable 不会执行查询。

If you look more closely to LINQ statements, you'll see that there are two types: the ones that return IQueryable (and IEnumerable ), and the ones that return List<TResult> , TResults , TKey , etc, anything that are not IQueryable/IEnumerable .如果您更仔细地查看 LINQ 语句,您会发现有两种类型:返回IQueryable (和IEnumerable )的类型,以及返回List<TResult>TResultsTKey等的类型,任何不是IQueryable/IEnumerable If the return value is an IQueryable , then we say that the function uses delayed execution (or lazy execution): the Expression to perform the query is created, but the query is not executed yet.如果返回值是一个IQueryable ,那么我们说该函数使用延迟执行(或延迟执行):执行查询的Expression已创建,但查询尚未执行。

This has the advantage that you can concatenate LINQ statements, without executing a query per statement.这样做的好处是您可以连接 LINQ 语句,而无需为每个语句执行查询。

The query is executed when you ask the IQueryable to get an enumerator and if you start enumerating, either implicitly by using foreach , or explicitly by using IQueryable.GetEnumerator() and IEnumerator.MoveNext() (which are also called by foreach ).当您要求 IQueryable 获取枚举器并且如果您开始枚举时,将执行查询,无论是通过使用foreach隐式,还是通过使用IQueryable.GetEnumerator()IEnumerator.MoveNext() (它们也由foreach调用)。

So as long as you are creating a query and returning an IQueryable, it is useless to create a Task.所以只要你在创建一个查询并返回一个IQueryable,创建一个Task是没有用的。 Concatenating LINQ statement will only change the Expression of the IQueryable , which is not something that you have to wait for.连接 LINQ 语句只会更改IQueryable的表达式,这不是您必须等待的。

Only if you create a function that will actually execute the query you'll need an async version: ToListAsync , FirstOrDefaultAsync , MaxAsync , etc. Internally these functions will GetEnumerator and MoveNextAsync <-- that is the actual async function只有当您创建一个实际执行查询的函数时,您才需要一个异步版本: ToListAsyncFirstOrDefaultAsyncMaxAsync等。在内部,这些函数将GetEnumeratorMoveNextAsync <-- 这是实际的异步函数

Conclusion: all your functions that would normally return IQueryable<...> don't need an Async version , all functions that return actual fetched data need an Async version结论:通常会返回IQueryable<...>所有函数都不需要 Async version ,所有返回实际获取数据的函数都需要 Async 版本

Examples.例子。 No async needed: no query executed:不需要异步:没有执行查询:

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

async needed:需要异步:

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

Usage:用法:

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

In the rare case that you'll have to enumerate yourself, you'll await in MoveNextAsync :在极少数情况下,您必须枚举自己,您将在MoveNextAsync等待:

IQueryable<Customer> myCustomers = ...
IEnumerator<Customer> customerEnumerator = myCustomers.GetEnumerator();

while (await customerEnumerator.MoveNextAsync())
{
     Customer customer = customerEnumerator.Current;
     Process(customer);
}

You want to read one record at a time from the database without loading all records in to memory.您希望一次从数据库中读取一条记录,而不将所有记录加载到内存中。 Sync, that would just be a foreach.同步,那只是一个 foreach。 To do the same thing but using an async connection method:做同样的事情,但使用异步连接方法:

1) Keep your signature, and consume it using ForeachAsync 1) 保留您的签名,并使用ForeachAsync使用它

public IQueryable<Profile> GetAll()

and then consuming it like this:然后像这样消费它:

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

Do note that the action passed here is not actually awaited if you make it async, see referenced question below how to handle this if you go with this method请注意,如果您将其设为异步,则此处传递的操作实际上并未等待,请参阅下面引用的问题,如果您使用此方法,如何处理此问题

2) Change signature, and implement it like ForeachAsync does (I borrowed the example from this question , as it provides a proper await.) 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));
        }
    }
}

IMHO恕我直言

Keep this method as it is.保持这个方法不变。

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

Use this method like this.像这样使用这个方法。 Whenever you use GetAll() add the async/await at that point.每当您使用GetAll()时,请在该点添加 async/await。

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

IQueryable doesn't access database unless a ToList(),FirstOrDefault() ... some operation is performed. IQueryable不访问数据库,除非 ToList(),FirstOrDefault() ... 执行某些操作。
You cannot add async/await to it.你不能向它添加 async/await。
Instead the GetAllProfiles() method is an I/O operation which performs a db operation so you can Await on it.相反, GetAllProfiles()方法是一个 I/O 操作,它执行 db 操作,因此您可以等待它。

Similar question 类似问题

you can select async using following sample code您可以使用以下示例代码选择异步

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

but this implementation will resolve the SQL statement and execute it when call但是这个实现将解析 SQL 语句并在调用时执行它

There is not any reason to await what you are trying to do.没有任何理由等待您正在尝试做的事情。 A queryable is an expression of a query, meaning nothing that actually do work against the database only when you call ToList() that mean the query is exectued on the database.可查询是查询的表达式,这意味着只有当您调用ToList() 时,才真正对数据库起作用,这意味着查询是在数据库上执行的。

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

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