Wrong Parameter Expression used in mapped expression
marcOcram opened this issue · 2 comments
Hello,
I've got an issue with the expression mapping not using the parameter of a lambda. The configuration maps an expression to the boolean property IsLatest
of the DTO: cfg.CreateMap<Check, CheckDTO>().ForMember(dest => dest.IsLatest, c => c.MapFrom(src => src.Finished == null && !src.Part.History.Any(ch => ch.Started > src.Started)));
This creates the wrong expression c => ((c.Finished == null) AndAlso Not(c.Part.History.Any(ch => (c.Started > c.Started))))
where the parameter ch
is not used. c
is used instead for both c.Started > c.Started
.
.Lambda #Lambda1<System.Func`2[AutomapperExpression.Check,System.Boolean]>(AutomapperExpression.Check $c) {
$c.Finished == null && !.Call System.Linq.Enumerable.Any(
($c.Part).History,
.Lambda #Lambda2<System.Func`2[AutomapperExpression.Check,System.Boolean]>)
}
.Lambda #Lambda2<System.Func`2[AutomapperExpression.Check,System.Boolean]>(AutomapperExpression.Check $ch) {
$c.Started > $c.Started
}
If I map the checks via mapper.Map<List<CheckDTO>>(part.History);
the property IsLatest
is set correctly to only one check object.
Version used:
.NET 5
AutoMapper 10.1.1
AutoMapper.Extensions.ExpressionMapping 4.1.1
Reproduction:
using AutoMapper;
using AutoMapper.Extensions.ExpressionMapping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace AutomapperExpression
{
public class Check
{
public DateTime? Finished { get; set; }
public Part Part { get; set; }
public DateTime Started { get; set; }
}
public class CheckDTO
{
public bool IsLatest { get; set; }
public Guid Part { get; set; }
}
public class Part
{
public List<Check> History { get; } = new List<Check>();
public Guid ID { get; } = Guid.NewGuid();
}
public static class Program
{
private static void Main( string[] args )
{
// source
Part part = new Part();
part.History.Add( new Check() { Started = DateTime.Now.AddMinutes( -2 ), Part = part } );
part.History.Add( new Check() { Started = DateTime.Now, Part = part } );
// mapping configuration
MapperConfiguration mapperConfiguration = new MapperConfiguration( cfg =>
{
cfg.AddExpressionMapping();
cfg.CreateMap<Check, CheckDTO>()
.ForMember( dest => dest.Part, c => c.MapFrom( src => src.Part.ID ) )
// check is latest if history does not contain any check which has a greater started timestamp
.ForMember( dest => dest.IsLatest, c => c.MapFrom( src => src.Finished == null && !src.Part.History.Any( ch => ch.Started > src.Started ) ) );
} );
IMapper mapper = mapperConfiguration.CreateMapper();
// get check DTO which is the latest via DTO expression and mapped expression
Expression<Func<CheckDTO, bool>> dtoExpression = c => c.IsLatest;
// this creates an expression where the parameter is not used
//.Lambda #Lambda2<System.Func`2[AutomapperExpression.Check,System.Boolean]>(AutomapperExpression.Check $ch) {
// $c.Started > $c.Started
//}
Expression<Func<Check, bool>> expression = mapper.MapExpression<Expression<Func<Check, bool>>>( dtoExpression );
Console.WriteLine( "Result:" );
foreach ( var check in part.History.Where( expression.Compile() ) )
{
Console.WriteLine( $"Check: {check.Started}" );
}
Console.WriteLine( "Expectation:" );
Console.WriteLine( $"Check: {part.History.Single( c => c.Finished == null && !c.Part.History.Any( ch => ch.Started > c.Started ) ).Started}" );
Console.ReadLine();
}
}
}