如何获取 OData 可查询 Web API 端点过滤器并将其映射到 DTO 对象?
How to take OData Queryable Web API endpoint filter and map it from DTO object?
我有一个简单的 Web API 端点,它可以接受传入的 OData 查询:
public IActionResult GetProducts(ODataQueryOptions<ProductDTO> options)
{
var results = DomainLayer.GetProducts(options);
return Ok(results);
}
我特别希望能够针对 ProductDTO
对象进行查询,并且能够针对 DTO 表示的属性进行过滤或排序。
我的设计问题是我想利用 OData 库的过滤器 parsing/applying 逻辑,但我不想将我的数据库绑定 ProductEntity
对象公开到我的 Web API AND 我不想 return 来自我的 DataAccessLayer
的 IQueryable
,只有 IEnumerable
s.
然后我想做的是从传入的 ODataQueryOptions
的 FilterQueryOption
属性 中提取 Expression
这样我就可以使用 AutoMapper 的表达式映射功能来将表达式从 Expression<Func<ProductDTO, bool>>
映射到 Expression<Func<Product, bool>>
,最后映射到 Expression<Func<ProductEntity, bool>>
,然后我会将其传递到 .Where()
对我的 Table<ProductEntity>
[=45] 的调用中=]在我的 SQL 数据库中(通过 Linq-2-SQL) 应用过滤器(希望),然后我将它一直转换回 DTO 对象.
我遇到的最大障碍是 queryable.Expression
正在 returning 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);
}
}
}
参考文献:
- 我在哪里找到了从过滤器中获取表达式的技巧:
- 一个相关的 GitHub 问题:https://github.com/OData/WebApi/issues/33
嗯,链接post的accepted answer作者在最后写道:
Notice that the expression contains looks more like this, SOTests.Customer[].Where($it => conditional-expression)
. So, you might have to extract that conditional expression from the lambda.
你得到的 MethodCallExpression
正是 - "call" 到 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 ...
}
}
我有一个简单的 Web API 端点,它可以接受传入的 OData 查询:
public IActionResult GetProducts(ODataQueryOptions<ProductDTO> options)
{
var results = DomainLayer.GetProducts(options);
return Ok(results);
}
我特别希望能够针对 ProductDTO
对象进行查询,并且能够针对 DTO 表示的属性进行过滤或排序。
我的设计问题是我想利用 OData 库的过滤器 parsing/applying 逻辑,但我不想将我的数据库绑定 ProductEntity
对象公开到我的 Web API AND 我不想 return 来自我的 DataAccessLayer
的 IQueryable
,只有 IEnumerable
s.
然后我想做的是从传入的 ODataQueryOptions
的 FilterQueryOption
属性 中提取 Expression
这样我就可以使用 AutoMapper 的表达式映射功能来将表达式从 Expression<Func<ProductDTO, bool>>
映射到 Expression<Func<Product, bool>>
,最后映射到 Expression<Func<ProductEntity, bool>>
,然后我会将其传递到 .Where()
对我的 Table<ProductEntity>
[=45] 的调用中=]在我的 SQL 数据库中(通过 Linq-2-SQL) 应用过滤器(希望),然后我将它一直转换回 DTO 对象.
我遇到的最大障碍是 queryable.Expression
正在 returning 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);
}
}
}
参考文献:
- 我在哪里找到了从过滤器中获取表达式的技巧:
- 一个相关的 GitHub 问题:https://github.com/OData/WebApi/issues/33
嗯,链接post的accepted answer作者在最后写道:
Notice that the expression contains looks more like this,
SOTests.Customer[].Where($it => conditional-expression)
. So, you might have to extract that conditional expression from the lambda.
你得到的 MethodCallExpression
正是 - "call" 到 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 ...
}
}