Подтвердить что ты не робот

Нет метода FindAsync() для IDbSet <T>

Есть ли причина, по которой метод FindAsync() не указан в интерфейсе IDbSet<T>? Find является частью интерфейса, кажется странным, что версия async недоступна. Мне нужно отправить в DbSet<T> для доступа к нему, что немного громоздко:

User user = await ((DbSet<User>)db.Users)
    .FindAsync("de7d5d4a-9d0f-48ff-9478-d240cd5eb035");
4b9b3361

Ответ 1

Если у вас есть потребитель IDbSet<T>, который я предполагаю, что вы делаете, потому что хотите получить доступ к FindAsync() от пользователя, тогда простое решение - создать свой собственный интерфейс, который включает IDbSet и содержит в зависимости от того, что FindAsync(), который вы хотите использовать:

public interface IAsyncDbSet<T> : IDbSet<T>
    where T : class
{
    Task<T> FindAsync(params Object[] keyValues);
}

Это решает проблему не отбрасывать на DbSet, что, кстати, сбрасывает абстрактную выгоду от контрактного кодирования. Но это также вводит свой собственный набор проблем.

Лучшим решением (imo), требующим немного больше работы, является определение интерфейса, который содержит только те элементы, которые вы хотите использовать в том, что в противном случае было бы вашим объектом DbSet, подклассом DbSet при реализации интерфейса, а затем использовать этот интерфейс в вашем коде:

public interface IMyAsyncDbSet<TEntity>
    where TEntity : class
{
    TEntity Add(TEntity entity);
    TEntity Remove(TEntity entity);

    // Copy other methods from IDbSet<T> as needed.

    Task<Object> FindAsync(params Object[] keyValues);
}

public class MyDbSet<T> : DbSet<T>, IMyAsyncDbSet<T>
    where T : class
{
}

На самом деле это шаблон адаптера. Он отделяет интерфейс, который ожидает ваш код от интерфейса, предоставляемого Entity Framework. Сейчас они идентичны - поэтому реализация ничего не делает, кроме наследования DbSet<T>. Но позже они могут расходиться. В этот момент вы все равно сможете использовать последний DbSet, не нарушая свой код.

Ответ 2

Вот как я занялся этим в одном из наших проектов:

using System.Threading.Tasks;

namespace System.Data.Entity
{
    public static class IDbSetExtensions
    {
        /// <summary>
        /// If possible asynchronously finds an entity with the given primary key values 
        /// otherwise finds the entity synchronously.  
        /// If an entity with the given primary key values exists in the context, then it is
        /// returned immediately without making a request to the store. Otherwise, a
        /// request is made to the store for an entity with the given primary key values
        /// and this entity, if found, is attached to the context and returned. If no
        /// entity is found in the context or the store, then null is returned.
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="this"></param>
        /// <param name="keyValues">The values of the primary key for the entity to be found.</param>
        /// <returns>A task that represents the asynchronous find operation. The task result contains 
        /// the entity found, or null.</returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        public static async Task<TEntity> FindAsync<TEntity>(this IDbSet<TEntity> @this, params object[] keyValues)
        where TEntity : class
        {
            DbSet<TEntity> thisDbSet = @this as DbSet<TEntity>;
            if (thisDbSet != null)
            {
                return await thisDbSet.FindAsync(keyValues);
            }
            else
            {
                return @this.Find(keyValues);
            }
        }
    }
}

Можно рассмотреть вопрос об упаковке метода Find в шаблоне async-over-sync, который обеспечит разгрузку (и отсутствие масштабируемости, как это делают истинные асинхронные методы). Однако вызывающий абонент должен знать об этом, чтобы убедиться, что они не будут вызывать методы в контексте после вызова метода FindAsync, который может вмешиваться. Приведение вызывающих абонентов в известность о конкретной реализации - это не очень хороший дизайн imho, поскольку он может легко привести к проблемам. Для OP IDbSet - это DbSet, поэтому вызов будет асинхронным.

Ответ 3

Я считаю, что правильный способ в наши дни (начиная с EF 6) включает в себя наследование с DbSet вместо реализации IDbSet.

Ответ 4

Измените метод FindAsync на FirstOrDefaultAsync (x = > x.Id == yourId);

Ответ 5

Используйте это расширение для решения проблемы FindAsync

/// <summary>
/// IDbSet extension
/// </summary>
public static class IDbSetExtension
{
    public static Task<TEntity> FindAsync<TEntity>(this IDbSet<TEntity> set, params object[] keyValues) 
        where TEntity : class
    {
        return Task<TEntity>.Run(() =>
        {
            var entity = set.Find(keyValues);
            return entity;
        });
    }
}