【问题标题】:Is it possible to create a Generic API GET operation?是否可以创建通用 API GET 操作?
【发布时间】:2018-12-11 23:16:58
【问题描述】:

最近我创建了一个服务,它在我的 API 中为 GET 端点提供了一些逻辑(返回某个数据库表的所有值)。

为此创建服务的原因是我想在某一时刻修改 GET 逻辑,而不是将来必须在所有端点上更改它。

我创建了一个可以工作的测试服务,但是因为我有超过 50 个表(DTO 类),所以我想让服务更通用。

我现在已经实现了以下,这只是一个带有一个 DTO 类的 GET 操作的示例:

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;

    }
}

上面的示例有效,但是,这意味着我需要为每个 DTO 类实现超过 50 个接口属性和 50 个GetAll 实现(因此与在端点本身中更改它相比,这不是一种改进)。

有没有办法让它更通用? 我认为接口中IEnumerable 的DTO 部分和GetAll 函数应该是通用类型(这样我就可以在端点本身提供正确的ViewModel / DTO)。

我已经想出了这样的东西:

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

有人能指出我正确的方向吗?

【问题讨论】:

  • 好问题,我想看看这个问题的答案。就这个问题向我 +1。
  • Is there a way to make this more generic?。简短回答:是的
  • 实体框架和 LINQ-to-Entities 已经这样做了。
  • 顺便说一句,此代码会将整个 Commodity 表加载到内存中,以便将其映射到 CommodityViewModel 而不进行任何过滤。
  • @PanagiotisKanavos 好吧,它叫GetAll :)

标签: c# asp.net-core


【解决方案1】:

是的,使用泛型和DbContextSet&lt;T&gt;() 方法,你可以这样做:

//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);
}

然后这样称呼它:

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

但是,返回所有行几乎肯定是个坏主意,所以为什么不在我们这里添加过滤器:

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);
}

现在我们这样称呼它:

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

如果您想从此方法中提取接口,我可能会将接口设为通用:

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

然后是一个基类:

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);
    }
}

现在你可以制作特定的派生类了:

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

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

【讨论】:

  • 看起来很棒,除了我自己的一些功能(未在主帖中发布)之外,还将对其进行测试。只是两个注释,var result = this.Set&lt;TEntity&gt;().AsQueryable(); 部分应该是var result = this._context.Set&lt;TEntity&gt;().AsQueryable(); 我错了吗?而且您给定的示例不使用该接口?我试图将它实现为一个接口,但无法让它工作。
  • 是的,上下文有点错误,我已经更正了。还添加了一个如何将其用作界面的示例。
  • 你的英雄,很好的答案,谢谢你的例子!
猜你喜欢
  • 2020-10-18
  • 2013-07-28
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 2021-07-25
相关资源
最近更新 更多