Someone is trying to achieve an abstraction in ASP.NET MVC with C#.
They have a base entity controller which should do most of the job for its derived classes(entities). In it, they have a problematic method which should return the database context of each table(they are not using any DB frameworks like EF). Here is the method:
protected abstract DbContext<EntityViewModel> CreateContext();
So, say they have a Category table, the method should be implemented:
protected override DbContext<EntityViewModel> CreateContext()
{
return new CategoryDbContext();
}
But C# says they can't implicitly cast it, etc...
Here are the context classes:
public abstract class DbContext<T>
{
public abstract void Create(T entity);
public abstract List<T> Read(ModifyData data);
public abstract void Update(T entity);
public abstract void Delete(T entity);
}
public class CategoryDbContext : DbContext<CategoryViewModel>
{
public override void Create(CategoryViewModel entity)
{
}
public override List<CategoryViewModel> Read(ModifyData data)
{
}
public override void Update(CategoryViewModel entity)
{
}
public override void Delete(CategoryViewModel entity)
{
}
}
What is wrong here in this design and how can these classes be changed to work?
Modify the base entity controller class to something like the following:
public abstract class BaseEntityController<TDbContext, TEntityViewModel>
where TDbContext : DbContext<TEntityViewModel>
where TEntityViewModel : EntityViewModel
{
protected abstract TDbContext CreateContext();
}
And then subclass the BaseEntityController like so:
public class CategoryController : BaseEntityController<CategoryDbContext, CategoryViewModel>
{
protected abstract TDbContext CreateContext();
}
And then subclass the DbContext as so:
public class CategoryDbContext : DbContext<CategoryViewModel>
{
public override void Create(CategoryViewModel entity)
{
}
public override List<CategoryViewModel> Read(ModifyData data)
{
}
public override void Update(CategoryViewModel entity)
{
}
public override void Delete(CategoryViewModel entity)
{
}
}
By adding a generic type parameter for the subclass form of the DbContext itself to the BaseEntityController class, we are able to return that placeholder instead of the base generic form of the DbContext thereby avoiding casting issues and making the code more strongly typed.
As a bonus, we can then refactor the above code to DRY up the generic type parameter declarations itself using a bit of generic nested class (parametric/generic namespace) technique. For example:
public abstract class Entity<TEntity, TDbContext, TViewModel>
where TEntity : Entity<TEntity, TDbContext, TEntityViewModel>
where TDbContext : Entity<TEntity, TDbContext, TEntityViewModel>.BaseDbContext, new()
where TViewModel : Entity<TEntity, TDbContext, TEntityViewModel>.BaseViewModel
{
public class EntityController
{
protected TDbContext CreateContext() { return new TDbContext(); }
}
public abstract class BaseDbContext
{
public abstract void Create(TViewModel entity);
public abstract List<TViewModel> Read(ModifyData data);
public abstract void Update(TViewModel entity);
public abstract void Delete(TViewModel entity);
}
public abstract class BaseViewModel {}
}
Then subclass(sub-namespace?) the the generic namespace and its members as so:
public class Category : Entity<Category, Category.DbContext, Category.ViewModel>
{
pubic class DbContext : BaseDbContext
{
public override void Create(CategoryViewModel entity)
{
}
public override List<ViewModel> Read(ModifyData data)
{
}
public override void Update(CategoryViewModel entity)
{
}
public override void Delete(CategoryViewModel entity)
{
}
}
pubic class ViewModel : BaseViewModel
{
}
}
Note that without further requirements indicating otherwise, we have been able to eliminate the need to subclass the EntityController at this point. But should we need to subclass it, we can change EntityController into BaseEntityController, and optionally add a generic type parameter for its subclass form to the Entity "generic namespace" in case we need to use or return that future subclass type within the base code.