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

Posted

技术标签:

【中文标题】如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?【英文标题】:How to take OData Queryable Web API endpoint filter and map it from DTO object? 【发布时间】:2019-08-13 21:33:15 【问题描述】:

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

public IActionResult GetProducts(ODataQueryOptions<ProductDTO> options)

    var results = DomainLayer.GetProducts(options);
    return Ok(results);

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

我的设计问题是我想利用 OData 库的过滤器解析/应用逻辑,但我不想将我的数据库绑定 ProductEntity 对象暴露给我的 Web API AND 我不想从我的DataAccessLayer 返回IQueryable,只返回IEnumerables。

然后我要做的是从传入的ODataQueryOptionsFilterQueryOption 属性中提取Expression,这样我就可以使用AutoMapper 的表达式映射功能将表达式从Expression&lt;Func&lt;ProductDTO, bool&gt;&gt; 映射到@987654334 @ 然后最后到 Expression&lt;Func&lt;ProductEntity, bool&gt;&gt; ,然后我将把它传递给 .Where() 调用我的 Table&lt;ProductEntity&gt; 过滤器在我的 SQL 数据库中应用的地方(通过 Linq-2-SQL) 然后我将其一直转换回 DTO 对象。

我遇到的最大问题是 queryable.Expression 返回 MethodCallExpression 而不是我预期的 Expression&lt;Func&lt;ProductDTO, bool&gt;&gt;,这意味着我无法像我计划的那样使用 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://***.com/a/16447514/1504964 相关的 GitHub 问题:https://github.com/OData/WebApi/issues/33

【问题讨论】:

github.com/AutoMapper/AutoMapper.Extensions.ExpressionMapping/… 【参考方案1】:

好吧,链接帖子accepted answer的作者在最后写道:

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

您得到的 MethodCallExpression 正是这样 - 对 Queryable.Where&lt;ProductDTO&gt; 的“调用”,而您需要的 lambda 表达式 Expression&lt;Func&lt;ProductDTO, bool&gt;&gt; 是第二个参数(请记住 Queryable.Wherestatic em> 扩展方法,所以第一个参数代表IQueryable&lt;ProductDTO&gt;),用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 ...
    

【讨论】:

以上是关于如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法让 Swashbuckle 将 OData 参数添加到 Web API 2 IQueryable<T> 端点?

OData端点:是否可以编辑文档服务

无法将服务引用添加到 OData 端点

参数中带有 IEnumerable 的 C# OData Web API POST 端点返回错误 400,输入无效

如何安全地使用 MVC WebAPI OData 端点?

Asp.Net MVC4 Web API - 我们是不是需要 OData 来构建快速查询服务