简体   繁体   中英

Use Automapper to filter DTO

I'm getting myself familiar with Automapper. I'm using this project template as a starting point and always run into problem with Automapper.

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))
            // This is the problematic where statement.
            .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));
        
    }
}

The following is my query.

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)
    };

When I try to execute the query, I receive following error. I spent hours on trying to solve the following issue in vain. I tried different methods like Automapper Condition, PreCondition, etc... none did work so far. could anyone please shad a right here?

> 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()

See theAutoMapper documentation for Queryable Extensions .

It states that ProjectTo must be the last call in the chain, any filtering and sorting must be done on the entities first, before the projection to DTOs. If not you'll get the exception you are received because Linq to SQL does not know how to deal with your DTO type.

Swapping the call to OrderByDescending with ProjectTo should fix the issue, but you'll need to rewrite your lambda inside OrderByDescending to use the entity property instead of the DTO.

I ended up moving the filter away from Automapper. But I still would like to know what the best a way forward on this is.

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 SubOrder
                                      {
                                Id = s.Id,
                                SubOrderNumber = s.SubOrderNumber,
                                OrderedMeals = s.OrderedMeals.Where(x => x.CompletedOn.HasValue && x.CompletedOn != DateTime.MinValue).ToList(),
                                      }
                            ).ProjectTo<GetMealsToBeServedListDto>(_mapper.ConfigurationProvider)
                            .ToListAsync(cancellationToken)
    };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM