Используя async с Entity Framework, выберите список типа IQueryable <T> - PullRequest
0 голосов
/ 29 августа 2018

Я пытаюсь преобразовать синхронный запрос доступа к данным в асинхронный, пока что я преобразовал все, кроме выбора списка, который возвращает 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);
    }

Как преобразовать приведенный выше код в асинхронный? Я попробовал этот пример кода Стивена Клири, но не могу понять, что такое ProcessEventAsync и как мне применить это к моему коду. Кроме того, я не могу использовать .ToList(), это будет слишком дорого, чтобы загрузить все данные в память.

Ответы [ 4 ]

0 голосов
/ 29 августа 2018

Вы должны знать разницу между запросом и его результатом. IQueryable содержит все для выполнения запроса. Это не сам запрос, и создание IQueryable не выполняет запрос.

Если вы посмотрите более внимательно на операторы LINQ, вы увидите, что есть два типа: те, которые возвращают IQueryableIEnumerable), и те, которые возвращают List<TResult>, TResults, TKey и т. Д., Все, что не IQueryable/IEnumerable. Если возвращаемое значение равно IQueryable, то мы говорим, что функция использует отложенное выполнение (или отложенное выполнение): Expression для выполнения запроса создан, но запрос еще не выполнен.

Это имеет то преимущество, что вы можете объединять операторы LINQ без выполнения запроса для каждого оператора.

Запрос выполняется, когда вы запрашиваете IQueryable для получения перечислителя, и если вы начинаете перечисление, либо неявно с помощью foreach, либо явно с использованием IQueryable.GetEnumerator() и IEnumerator.MoveNext() (которые также вызываются с помощью foreach ).

Так что, пока вы создаете запрос и возвращаете IQueryable, создавать Задачу бесполезно. Конкатенация оператора LINQ изменит только выражение IQueryable, которое вам не нужно ждать.

Только если вы создадите функцию, которая будет фактически выполнять запрос, вам потребуется асинхронная версия: ToListAsync, FirstOrDefaultAsync, MaxAsync и т. Д. Внутренне эти функции будут GetEnumerator и MoveNextAsync <- - это фактическая асинхронная функция </p>

Вывод: все ваши функции, которые обычно возвращают IQueryable<...> не требуется асинхронная версия, все функции, которые для возврата фактических извлеченных данных требуется версия Async

Примеры. Асинхронизация не требуется: запрос не выполнен:

// Query customer addresses:
static IQueryable<Address> QueryAddresses(this IQueryable<Address> 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)
                         .FetchAddresses();
    // 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);
}
0 голосов
/ 29 августа 2018

ИМХО

Сохраните этот метод как есть.

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

Используйте этот метод следующим образом. Всякий раз, когда вы используете GetAll(), добавьте асинхронное / ожидание в этой точке.

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

IQueryable не обращается к базе данных, если не выполняется какая-либо операция ToList (), FirstOrDefault () ...
Вы не можете добавить асинхронное / ожидание к нему.
Вместо этого метод GetAllProfiles() представляет собой операцию ввода-вывода, которая выполняет операцию дБ, поэтому вы можете ожидать ее.

Аналогичный вопрос

0 голосов
/ 29 августа 2018

Вы хотите читать по одной записи за раз из базы данных, не загружая все записи в память. Синхронизация, это было бы просто 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));
        }
    }
}
0 голосов
/ 29 августа 2018

Вы можете выбрать асинхронность, используя следующий пример кода

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

но эта реализация разрешит оператор SQL и выполнит его при вызове

Нет никаких оснований ждать, что вы пытаетесь сделать. Запрашиваемый является выражением запроса, означающим ничего, что на самом деле не работает с базой данных, только когда вы вызываете ToList () , что означает, что запрос выполняется в базе данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...