如何获取OData可查询的Web API端点过滤器并从DTO对象映射它?

时间:2019-03-22 20:31:53

标签: c# asp.net-web-api odata automapper

我有一个简单的Web API端点,可以接受传入的OData查询:

public IActionResult GetProducts(ODataQueryOptions<ProductDTO> options)
{
    var results = DomainLayer.GetProducts(options);
    return Ok(results);
}

我特别希望能够针对ProductDTO对象进行查询,并能够根据DTO表示的属性进行过滤或排序。

我的设计问题是我想利用OData库的过滤器解析/应用逻辑,但不想将数据库绑定的ProductEntity对象公开给我的Web API 我不想从我的IQueryable中仅返回DataAccessLayerIEnumerable

然后我要做的是从传入的Expression的{​​{1}}属性中提取FilterQueryOption,这样我就可以使用AutoMapper的Expression Mapping功能来映射{ {1}}到ODataQueryOptions,然后最后到达Expression<Func<ProductDTO, bool>>,然后将其传递给我的Expression<Func<Product, bool>>上的Expression<Func<ProductEntity, bool>>调用(希望)在过滤器中应用于我的SQL数据库中(通过Linq-2-SQL),然后我将其完全转换回DTO对象。

我遇到的最大问题是.Where()返回的是Table<ProductEntity>而不是我期望的queryable.Expression,这意味着我无法像以前那样使用AutoMapper映射表达式计划中的...

我该如何解决?

MethodCallExpression

参考:

1 个答案:

答案 0 :(得分:2)

链接文章accepted answer的作者在结尾处写道:

  

请注意,表达式包含的内容更像SOTests.Customer[].Where($it => conditional-expression)。因此,您可能必须从lambda中提取该条件表达式。

您获得的MethodCallExpression就是这样-对Queryable.Where<ProductDTO>的“调用”,而您需要的lambda表达式Expression<Func<ProductDTO, bool>>是第二个参数(记住Queryable.Where static 扩展方法,因此第一个参数表示IQueryable<ProductDTO>,并用Expression.Quote包装。

因此,您所需要做的就是使用以下内容提取lambda表达式:

public static class ODataQueryOptionsExtensions
{
    public static Expression<Func<T, bool>> GetFilter<T>(this ODataQueryOptions<T> options)
    {
        // The same trick as in the linked post
        IQueryable query = Enumerable.Empty<T>().AsQueryable();
        query = options.Filter.ApplyTo(query, new ODataQuerySettings());
        // Extract the predicate from `Queryable.Where` call
        var call = query.Expression as MethodCallExpression;
        if (call != null && call.Method.Name == nameof(Queryable.Where) && call.Method.DeclaringType == typeof(Queryable))
        {
            var predicate = ((UnaryExpression)call.Arguments[1]).Operand;
            return (Expression<Func<T, bool>>)predicate;
        }
        return null;
    }
}

并像这样使用它:

public class DomainLayer
{
    public IEnumerable<ProductDTO> GetProductsByEntityOptions(ODataQueryOptions<ProductDTO> options)
    {
         var filter = options.GetFilter();
         // Here the type of filter variable is Expression<Func<ProductDTO, bool>> as desired
         // The rest ...
    }
}