You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AutoMapper过滤DTO时LINQ表达式无法转换的问题求助

问题:AutoMapper结合EF Core ProjectTo时的LINQ翻译错误

基于CleanArchitecture模板开发,使用AutoMapper映射DTO时遇到EF Core无法翻译LINQ表达式的异常,以下是相关代码及错误信息:

DTO定义

public class GetMealsToBeServedListDto : IMapFrom<SubOrder>
{
    public int SubOrderId { get; set; }
    public int SubOrderNumber { get; set; }

    public ICollection<GetMealsToBeServedListOrderedMealDto>? GetMealsToBeServedListOrderedMealDtos { get; set; }

    public void Mapping(Profile profile)
    {
        profile.CreateMap<SubOrder, GetMealsToBeServedListDto>()
            .ForMember(d => d.SubOrderId, opt => opt.MapFrom(s => s.Id))
            .ForMember(d => d.SubOrderNumber, opt => opt.MapFrom(s => s.SubOrderNumber))
            // 此处为有问题的where语句
            .ForMember(d => d.GetMealsToBeServedListOrderedMealDtos, opt => opt.MapFrom(s => s.OrderedMeals != null ? s.OrderedMeals.Where(p => p.CompletedOn.HasValue && p.CompletedOn != DateTime.MinValue) :
           null));
    }
}

public class GetMealsToBeServedListOrderedMealDto : IMapFrom<OrderedMeal>
{
    public int Quantity { get; set; }    
    public DateTime CompletedOn { get; set; }


    public void Mapping(Profile profile)
    {
        profile.CreateMap<OrderedMeal, GetMealsToBeServedListOrderedMealDto>()
            .ForMember(d => d.Quantity, opt => opt.MapFrom(s => s.Quantity))
            .ForMember(d => d.CompletedOn, opt => opt.MapFrom(s => s.CompletedOn));
        
    }
}

查询代码

return new GetMealsToBeServedListVm
{
    GetMealsToBeServedListDtos = await _context.SubOrders
                        .AsNoTracking()
                        .Where(x => x.OrderedMeals != null && x.OrderedMeals.Any(p => p.CompletedOn.HasValue && p.CompletedOn != DateTime.MinValue))
                        .ProjectTo<GetMealsToBeServedListDto>(_mapper.ConfigurationProvider)
                        .OrderByDescending(t => t.SubOrderNumber)
                        .ToListAsync(cancellationToken)
};

错误信息

System.InvalidOperationException: The LINQ expression 'dtoOrderedMeal => new GetMealsToBeServedListOrderedMealDto{ 
    CompletedOn = dtoOrderedMeal.CompletedOn ?? 01/01/0001 00:00:00, 
    Quantity = dtoOrderedMeal.Quantity 
}
' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitLambda[T](Expression`1 lambdaExpression)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 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.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at test.Application.Orders.Queries.GetMealsToBeServedList.GetOrderPageListQueryHander.Handle(GetMealsToBeServedListQuery request, CancellationToken cancellationToken) in E:\Dev\test\backend\src\Application\Orders\Queries\GetMealsToBeServedList\GetMealsToBeServedListQuery.cs:line 44
   at test.Application.Common.Behaviours.PerformanceBehaviour`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in E:\Dev\test\backend\src\Application\Common\Behaviours\PerformanceBehaviour.cs:line 31
   at test.Application.Common.Behaviours.ValidationBehaviour`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in E:\Dev\test\backend\src\Application\Common\Behaviours\ValidationBehaviour.cs:line 35
   at test.Application.Common.Behaviours.AuthorizationBehaviour`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in E:\Dev\test\backend\src\Application\Common\Behaviours\AuthorizationBehaviour.cs:line 78
   at test.Application.Common.Behaviours.UnhandledExceptionBehaviour`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in E:\Dev\test\backend\src\Application\Common\Behaviours\UnhandledExceptionBehaviour.cs:line 19
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at test.WebUI.Controllers.OrderController.MealToBeServedList(GetMealsToBeServedListQuery query) in E:\Dev\test\backend\src\WebUI\Controllers\OrderController.cs:line 59
   at lambda_method253(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()

解决方案

1. 修复可空类型不匹配问题

错误核心是CompletedOn的可空类型转换:实体类OrderedMeal的CompletedOn是可空类型(DateTime?),但DTO的CompletedOn是非可空类型(DateTime),AutoMapper自动生成的?? DateTime.MinValue表达式无法被EF Core翻译。

方案A:修改DTO为可空类型

public class GetMealsToBeServedListOrderedMealDto : IMapFrom<OrderedMeal>
{
    public int Quantity { get; set; }    
    public DateTime? CompletedOn { get; set; } // 改为可空类型

    public void Mapping(Profile profile)
    {
        profile.CreateMap<OrderedMeal, GetMealsToBeServedListOrderedMealDto>()
            .ForMember(d => d.Quantity, opt => opt.MapFrom(s => s.Quantity))
            .ForMember(d => d.CompletedOn, opt => opt.MapFrom(s => s.CompletedOn));
    }
}

方案B:显式处理空值(保持DTO非可空)

profile.CreateMap<OrderedMeal, GetMealsToBeServedListOrderedMealDto>()
    .ForMember(d => d.Quantity, opt => opt.MapFrom(s => s.Quantity))
    .ForMember(d => d.CompletedOn, opt => opt.MapFrom(s => s.CompletedOn.HasValue ? s.CompletedOn.Value : DateTime.MinValue));

2. 调整集合过滤逻辑

在MapFrom中直接对OrderedMeals做Where过滤,EF Core难以翻译嵌套的LINQ表达式,推荐以下两种处理方式:

方式一:查询时直接投影过滤(推荐)

绕过AutoMapper的嵌套映射,直接在LINQ查询中完成投影和过滤,确保EF Core能正确翻译:

return new GetMealsToBeServedListVm
{
    GetMealsToBeServedListDtos = await _context.SubOrders
                        .AsNoTracking()
                        .Where(x => x.OrderedMeals != null && x.OrderedMeals.Any(p => p.CompletedOn.HasValue && p.CompletedOn != DateTime.MinValue))
                        .Select(s => new GetMealsToBeServedListDto
                        {
                            SubOrderId = s.Id,
                            SubOrderNumber = s.SubOrderNumber,
                            GetMealsToBeServedListOrderedMealDtos = s.OrderedMeals
                                .Where(p => p.CompletedOn.HasValue && p.CompletedOn != DateTime.MinValue)
                                .Select(p => new GetMealsToBeServedListOrderedMealDto
                                {
                                    Quantity = p.Quantity,
                                    CompletedOn = p.CompletedOn.HasValue ? p.CompletedOn.Value : DateTime.MinValue
                                }).ToList()
                        })
                        .OrderByDescending(t => t.SubOrderNumber)
                        .ToListAsync(cancellationToken)
};

方式二:AutoMapper客户端过滤(小数据量场景)

先加载数据到内存再过滤,适合数据量较小的情况:

// 修改GetMealsToBeServedListDto的Mapping方法
profile.CreateMap<SubOrder, GetMealsToBeServedListDto>()
    .ForMember(d => d.SubOrderId, opt => opt.MapFrom(s => s.Id))
    .ForMember(d => d.SubOrderNumber, opt => opt.MapFrom(s => s.SubOrderNumber))
    .ForMember(d => d.GetMealsToBeServedListOrderedMealDtos, opt => opt.MapFrom(s => s.OrderedMeals))
    .AfterMap((src, dest) => 
    {
        dest.GetMealsToBeServedListOrderedMealDtos = dest.GetMealsToBeServedListOrderedMealDtos?
            .Where(p => p.CompletedOn != DateTime.MinValue)
            .ToList();
    });

3. 验证AutoMapper配置

确保IMapFrom接口已被正确扫描,CleanArchitecture模板中通常会在MappingProfile中自动扫描实现类:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i => 
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var methodInfo = type.GetMethod("Mapping") ?? 
                type.GetInterface("IMapFrom`1")?.GetMethod("Mapping");
            
            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

内容的提问来源于stack exchange,提问作者ecasper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 10:45:44