AutoMapper映射OData has操作符至HasFlag引发EF Core翻译异常
从AutoMapper.Extensions.OData仓库指引至此,咨询以下问题,不确定是否与AutoMapper.AspNetCore.OData.EFCore、AutoMapper.Extensions.ExpressionMapping相关。已准备复现问题的示例仓库,项目搭建及运行步骤见仓库README。
问题概述
我有领域实体Foo和DTOFoo,二者均包含Flags枚举类型FooType,代码如下:
namespace Domain.Enumerations; [Flags] public enum FooType { One = 0b0001, Two = 0b0010, Three = 0b0100, Four = 0b1000 } namespace Domain.Components.Foos; public class Foo { public Guid Id { get; set; } public FooType Type { get; set; } } namespace ApiModel; [Flags] public enum FooType { One = 0b0001, Two = 0b0010, Three = 0b0100, Four = 0b1000 } public class Foo { public Guid Id { get; set; } public FooType Type { get; set; } }
领域实体Foo通过EF Core绑定数据库,DTOFoo用于构建OData EDM。使用AutoMapper的OData/EFCore/ExpressionMapping扩展实现实体与模型间的转换。
当使用$filter=type eq 'Four'查询API时一切正常,但使用$filter=type has 'Four'时,应用抛出异常:
System.InvalidOperationException: LINQ表达式'DbSet().Where(f => f.Type.HasFlag((Enum)Four))'无法被翻译。
补充信息:方法'System.Enum.HasFlag'的翻译失败。若该方法可映射到自定义函数,请查看相关文档了解更多信息。请将查询重写为可翻译的形式,或通过调用'AsEnumerable'、'AsAsyncEnumerable'、'ToList'或'ToListAsync'显式切换为客户端评估。查看相关文档了解更多信息。at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_01.<ExecuteAsync>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken) at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at AutoMapper.AspNet.OData.QueryableExtensions.GetAsync[TModel,TData](IQueryable1 query, IMapper mapper, Expression1 filter, Expression1 queryFunc, ICollection1 includeProperties, AsyncSettings asyncSettings) at AutoMapper.AspNet.OData.QueryableExtensions.GetAsync[TModel,TData](IQueryable1 query, IMapper mapper, ODataQueryOptions`1 options, QuerySettings querySettings)
at Domain.Components.Foos.Queries.GetFoosODataQueryHandler.Handle(GetFoosODataQuery query, CancellationToken ct) in C:\Projects\AutoMapper.OData.EFCore.Issue\Domain\Components\Foos\Queries\GetFoosOData.cs:line 29
在旧项目中,直接将DbSet<Foo>作为IQueryable从控制器返回时,flags enum的has操作可正常工作,但版本化API中这种方式不可行,因为API契约与数据库架构并非一一对应。曾考虑用数据库视图作为转换层再暴露为OData EDM,但架构设计上不理想。
表达式映射调试
添加单元测试对比两个表达式的差异:
直接针对领域实体的表达式:
Expression<Func<Foo, bool>> filter = x => x.Type.HasFlag(FooType.Three);
通过AutoMapper映射后的表达式:
Expression<Func<ApiModel.Foo, bool>> modelFilter = x => x.Type.HasFlag(ApiModel.FooType.Three); var filter = _mapper.MapExpression<Expression<Func<Foo, bool>>>(modelFilter);
目前未发现两个filter的差异,但前者可正常工作,后者抛出上述异常。
请问有人遇到过类似问题吗?希望继续用AutoMapper实现层间映射,不想将该职责转移到数据库。
感谢关注!
内容的提问来源于stack exchange,提问作者engenb

