I am not sure if this is possible, but I have started a new project and I am trying to clean up the way I work with the DbContext. I am trying to do this by using generics.
The first part is easy. I create a Repository class like this:
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();
}
Now I want to create a generic Service class that can use all the CRUD methods. I tried to create it like this:
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;
}
}
I am using TKey to specify the primitive type, but the issue is with the GetAsync method. Obviously it doesn't know what the object is at this point, so it will not compile. I get this error:
'T' does not contain a definition for 'Id' and no extension method 'Id' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Now I know this is because I haven't said what T is.
Looking at how IdentityFramework handle this, they use IUser, I could do the same, but it would mean ALL my entities would have to implement this interface.
If I created an interface like this:
public interface IEntity<out TKey>
{
TKey Id { get; }
}
And then updated all my entities to implement the interface like this:
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; }
}
That seems to work. I guess I only have one question.
Is this the best way to do it?
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…