[SOLVED] Generic Interface/Implementation class/Method returns different result set

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.

  1. Interface : Created a Generic interface has some methods with one example.
    public interface IDataService<TEntity>
    {
        public Task<List<TEntity>> GeDataAsync();
    }  
  1. 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();

        }

  1. 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;
                
            }
       // ...................................
        }


}
  1. Ajax call from jquery

There are at least 2 problem with this approach:

  1. I have to create 19 more implementation classes ( Table2Service , Table2Service… Table20Service) to populate data from their respective classes. Classes (Table2, Table3……Table20)
  2. 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 :

  1. One generic interface
  2. One generic implementation class which give data for all those 20 tables.
  3. 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.

  1. 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.

  1. 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.

  1. Now your controller needs only 1 dependency of GenericService(IDataService):
public class DataController : Controller
{
    private IDataService _dataService;

    public DataController(IDataService dataService)
    {
        _dataService = dataService;
    }
}
  1. 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:

  1. You get table name in controller action.
  2. 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 the Type variable which is entity type to use.
  3. You can find Set() method on context class, make it generic by calling MakeGenericMethod() and setting the entity type got in 2nd step.
  4. Now you can call the generic Set() method by using yourMethodInfo.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)

Leave a Reply

Your email address will not be published. Required fields are marked *