Flattened properties via IncludeMembers don't get mapped correctly in expressions
Opened this issue · 5 comments
This time I think this issue is related to this library.
I have a model and dtos like this:
public class Category{
[...]
public string? Name {get; set;}
}
public class Product{
[...]
public Category? Category {get; set;}
}
public class ProductDTO{
[...]
public string? CategoryName {get; set;}
}
If I create the maps "manually", everything works:
CreateMap<Product, ProductDTO>()
[...]
.ForMember(p => p.CategoryName, c => c.MapFrom(p => p.Category!.Name));
I can then query it regularly:
Db.Products!.UseAsDataSource(_mapper.ConfigurationProvider).For<ProductDTO>()
.FirstOrDefault(p => p.CategoryName == "MyCategory");
But if I try to include the Category entity:
CreateMap<Category, ProductDTO>()
[...]
.ForMember(p => p.CategoryName, c => c.MapFrom(c => c.Name));
CreateMap<Product, ProductDTO>()
[...]
.IncludeMembers(p => p.Category);
Then I get an exception in the query:
'Property 'String CategoryName' is not defined for type '[...].Product' Arg_ParamName_Name'
Here's the full stack trace:
in System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 284
in System.Linq.Expressions.Expression.MakeMemberAccess(Expression expression, MemberInfo member) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 398
in System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func) in /_/src/libraries/System.Linq/src/System/Linq/Aggregate.cs: riga 54
in System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 68
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitBinary(BinaryExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 106
in System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/BinaryExpression.cs: riga 310
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression[T](Expression`1 expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 170
in System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs: riga 290
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func) in /_/src/libraries/System.Linq/src/System/Linq/Aggregate.cs: riga 54
in System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs: riga 290
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 540
in System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs: riga 84
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 69
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 76
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 65
in System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs: riga 108
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 69
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 76
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 65
in System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs: riga 108
in AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.ConvertDestinationExpressionToSourceExpression(Expression expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs: riga 320
in AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.Execute[TResult](Expression expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs: riga 82
in System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source) in /_/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs: riga 1064
What you've posted runs for me without an exception. This [...]
means removed for brevity correct?
[Fact]
public void Issuew()
{
var config = new MapperConfiguration(c =>
{
c.CreateMap<Category, ProductDTO>()
.ForMember(p => p.CategoryName, c => c.MapFrom(c => c.Name));
c.CreateMap<Product, ProductDTO>()
.IncludeMembers(p => p.Category);
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
Expression<Func<ProductDTO, bool>> expr = x => x.CategoryName == "MyCategory";
//var mappedExpression = mapper.MapExpression<Expression<Func<Product, bool>>>(expr);
IQueryable<Product> products = new List<Product>() { new Product { Category = new Category { Name = "MyCategory" } } }.AsQueryable();
var dto = products.UseAsDataSource(mapper.ConfigurationProvider).For<ProductDTO>().FirstOrDefault(p => p.CategoryName == "MyCategory");
}
public class Category
{
public string? Name { get; set; }
}
public class Product
{
public Category? Category { get; set; }
}
public class ProductDTO
{
public string? CategoryName { get; set; }
}
You'll want to post something anyone can copy, paste, run and see the exception.
I'll check tomorrow better, does it work for Ordering too for you? Like OrderBy(p => p.CategoryName)
It's going to be up to you to reproduce exceptions :). Plenty of tests here including OrderBy
.
I tried again, with the following code:
public class TestCategory {
public string? Name { get; set; }
}
public class TestProduct {
public TestCategory? Category { get; set; }
}
public class TestProductDTO {
public string? Name { get; set; }
}
void Test(){
var config = new MapperConfiguration(c =>
{
c.CreateMap<TestCategory, TestProductDTO>();
c.CreateMap<TestProduct, TestProductDTO>()
.IncludeMembers(p => p.Category);
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var products = new List<TestProduct>() {
new TestProduct {
Category = new TestCategory { Name = "MyCategory" }
}
}.AsQueryable();
Expression<Func<TestProductDTO, bool>> expr = x => x.Name == "MyCategory";
var mappedExpression = mapper.MapExpression<Expression<Func<TestProduct, bool>>>(expr);
var aaa = products.UseAsDataSource(mapper.ConfigurationProvider).For<TestProductDTO>()
.FirstOrDefault(x => x.Name == "MyCategory");
}
And I get weird results... mappedExpression
is getting mapped correctly to x => x.Category.Name == "MyCategory"
while when querying below I get the same exception:
'Property 'System.String Name' is not defined for type '[...].TestProduct' Arg_ParamName_Name'
The stack trace is a little bit different:
in System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 284
in System.Linq.Expressions.Expression.MakeMemberAccess(Expression expression, MemberInfo member) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 398
in System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func) in /_/src/libraries/System.Linq/src/System/Linq/Aggregate.cs: riga 54
in System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MemberExpression.cs: riga 68
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitBinary(BinaryExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 106
in System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/BinaryExpression.cs: riga 310
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression[T](Expression`1 expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 170
in System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs: riga 290
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func) in /_/src/libraries/System.Linq/src/System/Linq/Aggregate.cs: riga 54
in System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/LambdaExpression.cs: riga 290
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 540
in System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs: riga 84
in System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 35
in System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/ExpressionVisitor.cs: riga 69
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 76
in AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node) in /_/src/AutoMapper.Extensions.ExpressionMapping/ExpressionMapper.cs: riga 65
in System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) in /_/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs: riga 108
in AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.ConvertDestinationExpressionToSourceExpression(Expression expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs: riga 320
in AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.Execute[TResult](Expression expression) in /_/src/AutoMapper.Extensions.ExpressionMapping/Impl/SourceInjectedQueryProvider.cs: riga 82
in System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate) in /_/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs: riga 1095
The recommended approach is the following:
[Fact]
public void Issue()
{
var config = new MapperConfiguration(c =>
{
c.CreateMap<TestCategory, TestProductDTO>();
c.CreateMap<TestProduct, TestProductDTO>()
.IncludeMembers(p => p.Category);
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var products = new List<TestProduct>() {
new TestProduct {
Category = new TestCategory { Name = "MyCategory" }
}
}.AsQueryable();
Expression<Func<TestProductDTO, bool>> expr = x => x.Name == "MyCategory";
var mappedExpression = mapper.MapExpression<Expression<Func<TestProduct, bool>>>(expr);
products = products.Where(mappedExpression);
var result = mapper.ProjectTo<TestProductDTO>(products).FirstOrDefault();
//var aaa = products.UseAsDataSource(mapper.ConfigurationProvider).For<TestProductDTO>()
//.FirstOrDefault(x => x.Name == "MyCategory");
}
It is less "black boxed" for one thing. Also filtering before projection is recommended - see this issue from a month ago. The ReadMe has a couple of examples of extension methods - using Map
not ProjectTo
but that's the idea.
Note that the expression mapping code in UseAsDataSource
is not the same code used by MapExpression
and is less frequently maintained. Ok to submit a PR if you're interested.