如何获取 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
,只返回IEnumerable
s。
然后我要做的是从传入的ODataQueryOptions
的FilterQueryOption
属性中提取Expression
,这样我就可以使用AutoMapper 的表达式映射功能将表达式从Expression<Func<ProductDTO, bool>>
映射到@987654334 @ 然后最后到 Expression<Func<ProductEntity, bool>>
,然后我将把它传递给 .Where()
调用我的 Table<ProductEntity>
过滤器在我的 SQL 数据库中应用的地方(通过 Linq-2-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://***.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 => conditional-expression)
。因此,您可能必须从 lambda 中提取该条件表达式。
您得到的 MethodCallExpression
正是这样 - 对 Queryable.Where<ProductDTO>
的“调用”,而您需要的 lambda 表达式 Expression<Func<ProductDTO, bool>>
是第二个参数(请记住 Queryable.Where
是 static em> 扩展方法,所以第一个参数代表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 ...
【讨论】:
以上是关于如何获取 OData 可查询 Web API 端点过滤器并从 DTO 对象映射它?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法让 Swashbuckle 将 OData 参数添加到 Web API 2 IQueryable<T> 端点?