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

4

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

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

我希望能够针对 ProductDTO 对象查询并根据 DTO 表示的属性进行过滤或排序。我的设计问题在于,我想利用 OData 库的过滤器解析/应用逻辑,但我不想将我的数据库绑定的 ProductEntity 对象公开到 Web API,也不想从我的 DataAccessLayer 返回一个 IQueryable,仅返回 IEnumerable。因此,我正在尝试从传入的 ODataQueryOptionsFilterQueryOption 属性中提取 Expression,以便可以使用 AutoMapper 的表达式映射功能将表达式从 Expression<Func<ProductDTO, bool>> 映射到 Expression<Func<Product, bool>>,最后映射到Expression<Func<ProductEntity, bool>>,然后将其传递到 Table<ProductEntity>.Where() 调用中, 通过 Linq-2-SQL 在 SQL 数据库中应用筛选器(希望如此),最终将其转换回 DTO 对象。
我遇到的大障碍是 queryable.Expression 返回的是 MethodCallExpression 而不是我预期的 Expression<Func<ProductDTO, bool>>,这意味着我不能像计划的那样使用 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);
        }
    }
}

参考资料:


https://github.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/issues/23#issuecomment-459355233 - Lucian Bargaoanu
1个回答

5

好的,链接帖子的被接受的答案的作者在结尾写道:

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

您获得的MethodCallExpression恰好是对Queryable.Where<ProductDTO>的“调用”,而您需要的lambda表达式Expression<Func<ProductDTO, bool>>是第二个参数(记住Queryable.Where是一个静态扩展方法,因此第一个参数表示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 ...
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接