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

Kyl*_* V. 4 c# automapper odata asp.net-web-api

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

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

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

我的设计问题是,我想利用 OData 库的过滤器解析/应用逻辑,但我不想将数据库绑定 ProductEntity对象暴露给我的 Web API ,并且我不想仅从IQueryable我的s。DataAccessLayerIEnumerable

然后我想做的是ExpressionFilterQueryOption传入的属性中提取 the ODataQueryOptions,以便我可以使用 AutoMapper 的表达式映射功能将表达式从 a 映射Expression<Func<ProductDTO, bool>>到 aExpression<Func<Product, bool>>然后最后到 a Expression<Func<ProductEntity, bool>>where 我将它传递到.Where()对我的Table<ProductEntity> where (希望)过滤器应用于我的 SQL 数据库(通过 Linq-2-SQL),然后我将其一路转换回 DTO 对象。

我遇到的最大问题是queryable.Expression返回 aMethodCallExpression而不是Expression<Func<ProductDTO, bool>>我预期的 a,这意味着我无法像我计划的那样使用 AutoMapper 映射表达式......

我该如何解决这个问题?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.AspNet.OData.Query;
using AutoMapper.Extensions.ExpressionMapping;
using AutoMapper.QueryableExtensions;

namespace ProductApp
{
    public class DomainLayer
    {
        public IEnumerable<ProductDTO> GetProductsByEntityOptions(ODataQueryOptions<ProductDTO> options)
        {
            var mapper = MyMapper.GetMapper();

            // This is the trick to get the expression out of the FilterQueryOption...
            IQueryable queryable = Enumerable.Empty<ProductDTO>().AsQueryable();
            queryable = options.Filter.ApplyTo(queryable, new ODataQuerySettings());            
            var exp = (MethodCallExpression) queryable.Expression;              // <-- This comes back as a MethodCallExpression...

            // Map the expression to my intermediate Product object type
            var mappedExp = mapper.Map<Expression<Func<Product, bool>>>(exp);   // <-- But I want it as a Expression<Func<ProductDTO, bool>> so I can map it...

            IEnumerable<Product> results = _dataAccessLayer.GetProducts(mappedExp);

            return mapper.Map<IEnumerable<ProductDTO>>(results);
        }
    }

    public class DataAccessLayer
    {
        public IEnumerable<Product> GetProducts(Expression<Func<Product, bool>> exp)
        {
            var mapper = MyMapper.GetMapper();

            var mappedExp = mapper.Map<Expression<Func<ProductEntity, bool>>>(exp);
            IEnumerable<ProductEntity> result = _dataContext.GetTable<ProductEntity>().Where(mappedExpression).ToList();

            return mapper.Map<IEnumerable<Product>>(result);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

参考:

Iva*_*oev 5

好吧,链接帖子的接受答案的作者在最后写道:

请注意,表达式 contains 看起来更像这样,SOTests.Customer[].Where($it => conditional-expression)。因此,您可能必须从 lambda 中提取条件表达式。

MethodCallExpression得到的正是 - 对 的“调用” Queryable.Where<ProductDTO>Expression<Func<ProductDTO, bool>>您需要的 lambda 表达式是第二个参数(记住 是Queryable.Where静态扩展方法,因此第一个参数代表),用Expression.QuoteIQueryable<ProductDTO>包装。

因此,您所需要做的就是使用如下内容提取 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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 ...
    }
}
Run Code Online (Sandbox Code Playgroud)