Issue
I am working on .Net core application with EF, have around 20 tables/Entities from I have to get data, and each table has different columns.
Lets say Table are Table1, Table2, Table3……Table20.
- Interface : Created a Generic interface has some methods with one example.
public interface IDataService<TEntity>
{
public Task<List<TEntity>> GeDataAsync();
}
- Implementation : Now my implementation class was like below which is basically populates data from Table1.
public class Table1Service : IDataService<Table1>
{
private readonly ModelContext _context;
public Table1Service(ModelContext context)
{
_context = context;
}
public async Task<List<Table1>> GeDataAsync();
{
return await _context.Table1s().ToListAsync();
}
- Controller :
public class DataController : Controller
{
private IDataService<Table1> _table1Service;
public DataController(IDataService<Table1> table1Service; )
{
_table1Service = table1Service;
}
[HttpGet("GeDataAsync")]
public async Task<IActionResult> GeDataAsync( string tableName; )
{
var tableEnum = Enum.Parse<TableNames>(tableName); //;
switch (tableEnum)
{
case TableNames.Table1:
return Ok(ApiResult<List<Table1>>.Success200(await _table1Service.GeDataAsync()));
break;
}
// ...................................
}
}
- Ajax call from jquery
There are at least 2 problem with this approach:
- I have to create 19 more implementation classes ( Table2Service , Table2Service… Table20Service) to populate data from their respective classes. Classes (Table2, Table3……Table20)
- I can’t pass those many IDataService,DataService.. DataService in the controller class’s constructor. There could be more in future…
what I am looking for is :
- One generic interface
- One generic implementation class which give data for all those 20 tables.
- One controller and method GeDataAsync() which return table result based on Table name passed from ajax.
Any help is much appreciated.
Solution
To have completely generic solution you need to do some changes.
- You need to change your interface – make method generic, not the interface:
public interface IDataService
{
public Task<List<TEntity>> GeDataAsync<TEntity>() where TEntity : class;
}
The generic constraint is needed here for EF to work.
- Change your service implementation:
public class GenericService : IDataService
{
private readonly ModelContext _context;
public GenericService(ModelContext context)
{
_context = context;
}
public async Task<List<TEntity>> GeDataAsync<TEntity>() where TEntity : class =>
await _context.Set<TEntity>().ToListAsync();
}
You can call Set()
method on context to point the EF to work with that entity type.
- Now your controller needs only 1 dependency of
GenericService
(IDataService
):
public class DataController : Controller
{
private IDataService _dataService;
public DataController(IDataService dataService)
{
_dataService = dataService;
}
}
- The only thing left is some kind of mapping to map table name from request to correct entity type to call the service method. As far as I know there is no some "special" method to point the EF to work with some table only by table name, so you will end up with similar
GetDataAsync()
action implementation in controller.
To make mapping generic you can use reflection:
- You get table name in controller action.
- Having table name you should get the type of the entity to use – either by having some mapping (like
Dictionary<string, Type>
) or maybe checking the attributes of your table (entity) classes. So as a result you should have theType
variable which is entity type to use. - You can find
Set()
method on context class, make it generic by callingMakeGenericMethod()
and setting the entity type got in 2nd step. - Now you can call the generic
Set()
method by usingyourMethodInfo.Invoke()
.
If you go with reflection I suggest passing table name to service method and do all this stuff in service.
Answered By – Roman Doskoch
Answer Checked By – Dawn Plyler (BugsFixing Volunteer)