【发布时间】:2017-11-27 12:55:19
【问题描述】:
我不确定这是否可行,但我已经开始了一个新项目,并且正在尝试清理我使用 DbContext 的方式。我正在尝试通过使用泛型来做到这一点。 第一部分很简单。我像这样创建一个 Repository 类:
public class Repository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly DbSet<T> _dbEntitySet;
public Repository(InteractiveChoicesContext dbContext)
{
_dbContext = dbContext;
_dbEntitySet = dbContext.Set<T>();
}
/// <summary>
/// Gets all the entities
/// </summary>
/// <param name="includes">Option includes for eager loading</param>
/// <returns></returns>
public IQueryable<T> List(params string[] includes)
{
IQueryable<T> query = _dbEntitySet;
foreach (var include in includes)
query = query.Include(include);
return query;
}
/// <summary>
/// Creates an entity
/// </summary>
/// <param name="model"></param>
public void Create(T model) => _dbEntitySet.Add(model);
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="model"></param>
public void Update(T model) => _dbEntitySet.Update(model);
/// <summary>
/// Removes an entity
/// </summary>
/// <param name="model"></param>
public void Remove(T model) => _dbContext.Entry<T>(model).State = EntityState.Deleted;
/// <summary>
/// Saves the database context changes
/// </summary>
/// <returns></returns>
public async Task SaveChangesAsync()
{
await _dbContext.SaveChangesAsync();
}
现在我想创建一个可以使用所有 CRUD 方法的通用 Service 类。我试着像这样创建它:
public class Service<T> : Service<T, int> where T : class
{
public Service(InteractiveChoicesContext dbContext) : base(dbContext)
{
}
}
public class Service<T, TKey>: Repository<T> where T: class
{
public Service(InteractiveChoicesContext dbContext) : base(dbContext)
{
}
public virtual async Task<IEnumerable<T>> ListAsync() => await List().ToListAsync();
public virtual async Task<T> GetAsync(TKey id) => await List().SingleOrDefaultAsync(m => m.Id == id);
public virtual async Task<T> CreateAsync(T model)
{
Create(model);
await SaveChangesAsync();
return model;
}
public virtual async Task<T> UpdateAsync(T model)
{
Update(model);
await SaveChangesAsync();
return model;
}
public virtual async Task<bool> DeleteAsync(TKey id)
{
var model = await GetAsync(id);
Remove(model);
await SaveChangesAsync();
return true;
}
}
我使用 TKey 来指定原始类型,但问题在于 GetAsync 方法。显然此时它不知道对象是什么,所以它不会编译。我收到此错误:
“T”不包含“Id”的定义,并且找不到接受“T”类型的第一个参数的扩展方法“Id”(您是否缺少 using 指令或程序集引用?)
现在我知道这是因为我没有说 T 是什么。 看看 IdentityFramework 如何处理这个问题,他们使用 IUser,我也可以这样做,但这意味着我的所有实体都必须实现这个接口。 如果我创建了这样的界面:
public interface IEntity<out TKey>
{
TKey Id { get; }
}
然后更新我所有的实体来实现这样的接口:
public class Question : IEntity<int>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
[Required, MaxLength(100)] public string Text { get; set; }
public bool MultipleChoice { get; set; }
public OutcomeGroup Group { get; set; }
public IEnumerable<Answer> Answers { get; set; }
}
这似乎行得通。我想我只有一个问题。 这是最好的方法吗?
【问题讨论】:
-
如果您的代码有效并且您只是询问这是否是“最佳”方式,那么问题可能属于codereview.stackexchange.com(尽管最好先检查他们的规则)
-
是的,实现接口来访问泛型变量的特定属性通常是最好的方法。请注意,EF
DbSet具有Find(和FindAsync)方法,该方法接受表示实体主键的对象数组,可以在您的情况下使用它而不是SingleOrDefault。 -
啊,谢谢你:)
-
因此,如果您的表有一百万条记录,并且您计划使用 GetAsync 获取一条记录,那么您首先将所有百万条记录放到本地内存中,然后将它们全部丢弃,除了一条?考虑尽可能长时间地保持它 IQueryable。让您的调用者决定何时执行您的查询,以便他可以有效地连接 IQueryable 扩展
-
我将它保留为 IQueryable 直到它到达 API 端点
标签: c# entity-framework generics