简体   繁体   中英

Is it possible to create a Generic API GET operation?

Recently I created a service which has some logic for a GET endpoint in my API (returning all values of some database table).

The reason for creating a service for this is the fact that I want to modify GET logic at one point instead of having to change it on all my endpoints in the future.

I created a test service which works, but because I have over 50 tables (DTO classes) I want to make the service more generic.

I now have implemented the following, which is just an example of one GET operation with one DTO class :

public interface IOMSService
{
    IEnumerable<CommodityViewModel> GetAll(); // Can I use <T> for this? - now I need to make interface properties for every OMS class (50+)
}

public class OMSService : IOMSService
{
    private MyDBContext _context;
    private IMapper _mapper;

    public OMSService(MyDBContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public IEnumerable<CommodityViewModel> GetAll() // How to make this more generic?
    {
        var result = this._context.Commodity
                     .Include(i => i.OmsCommodityMaterial);

        var CommodityVM = _mapper.Map<IList<CommodityViewModel>>(result);

        return CommodityVM;

    }
}

The above example works, however, this would mean I need to implement over 50 interface properties and 50 GetAll implementations for every DTO class (and thus not an improvement as opposed to changing it in the endpoints itself).

Is there a way to make this more generic? I think the DTO part of the IEnumerable in the interface and GetAll function should be of a Generic type (so that I can just supply the right ViewModel / DTO at the endpoint itself).

I already came up with something like this:

public interface IOMSService<T, U>
        where T : IEnumerable<U>
{
    T GetAll { get; }
}

Could someone point me in the right direction?

Yes, using generics and the Set<T>() method of DbContext , you can do something like this:

//Note we need to make the entity and the model it maps to generic
public IEnumerable<TModel> GetAll<TEntity, TModel>(
    params Expression<Func<TEntity, object>>[] includes)
    where TEntity : class
{
    var result = _context.Set<TEntity>().AsQueryable();

    if(includes != null)
    {
        foreach (var include in includes)
        {
            result = result.Include(include);
        }
    }

    return _mapper.Map<IList<TModel>>(result);
}

And call it like this:

var allTheThings = GetAll<Commodity, CommodityViewModel>(i => i.OmsCommodityMaterial);

However, to return all your rows is almost certainly a bad idea, so why not add in a filter while we are here:

public IEnumerable<TModel> Get<TEntity, TModel>(
    Expression<Func<TEntity, bool>> predicate, 
    params Expression<Func<TEntity, object>>[] includes) 
    where TEntity : class
{
    var result = _context.Set<TEntity>()
        .Where(predicate);

    if(includes != null)
    {
        foreach (var include in includes)
        {
            result = result.Include(include);
        }
    }

    return _mapper.Map<IList<TModel>>(result);
}

Now we call it like this:

var someOfTheThings = Get<Commodity, CommodityViewModel>(
    x => x.SomeProperty == 42,
    i => i.OmsCommodityMaterial);

If you want to extract an interface from this method, I'd probably make the interface generic:

public interface IOMSService<TEntity>
{
    IEnumerable<TModel> Get<TModel>(
        Expression<Func<TEntity, bool>> predicate, 
        params Expression<Func<TEntity, object>>[] includes) 
}

And then a base class:

public abstract class BaseOMSService<TEntity> : IOMSService<TEntity>
    where TEntity : class
{
    private MyDBContext _context;
    private IMapper _mapper;

    public BaseOMSService(MyDBContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public IEnumerable<TModel> Get<TModel>(
            Expression<Func<TEntity, bool>> predicate, 
            params Expression<Func<TEntity, object>>[] includes) 
    {
        var result = _context.Set<TEntity>()
            .Where(predicate);

        if(includes != null)
        {
            foreach (var include in includes)
            {
                result = result.Include(include);
            }
        }

        return _mapper.Map<IList<TModel>>(result);
    }
}

And now you can make specific derived classes:

public class CheeseOMSService : BaseOMSService<Cheese>
{
    // snip
}

public class ZombieOMSService : BaseOMSService<Zombie>
{
    // snip
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM