IDbSet<T>
接口中省略了FindAsync()
方法有什么原因吗?Find
是接口的一部分,异步版本不可用似乎很奇怪。我需要转换到DbSet<T>
才能访问它,这有点麻烦:
User user = await ((DbSet<User>)db.Users)
.FindAsync("de7d5d4a-9d0f-48ff-9478-d240cd5eb035");
如果你拥有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
{
}
这是一个适配器模式,真的。它将代码期望的接口与实体框架提供的接口解耦。现在,它们是相同的——这就是为什么实现除了继承DbSet<T>
之外什么都不做。但后来他们可能会出现分歧。到那时,您仍然可以在不破坏代码的情况下使用最新的DbSet。
以下是我在我们的一个项目中如何解决这个问题:
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方法封装在一个异步模式中,该模式将提供卸载(并且不像真正的异步方法那样具有可伸缩性)。然而,调用方必须意识到这一点,以确保在调用可能干扰的FindAsync方法后,不会在上下文中调用方法。然而,让调用者知道特定的实现并不是一个很好的设计imho,因为它很容易导致问题。然而,对于OP,IDbSet是DbSet,因此调用将是异步的。
我相信现在的正确方法(从EF 6开始)是从DbSet继承,而不是实现IDbSet。
将FindAsync方法更改为FirstOrDefaultAsync(x=>x.Id==yourId);
使用此扩展解决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;
});
}
}