Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flattened properties via IncludeMembers don't get mapped correctly in expressions #151

Open
Xriuk opened this issue Nov 21, 2022 · 5 comments

Comments

@Xriuk
Copy link

Xriuk commented Nov 21, 2022

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
@BlaiseD
Copy link
Member

BlaiseD commented Nov 21, 2022

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.

@Xriuk
Copy link
Author

Xriuk commented Nov 21, 2022

I'll check tomorrow better, does it work for Ordering too for you? Like OrderBy(p => p.CategoryName)

@BlaiseD
Copy link
Member

BlaiseD commented Nov 21, 2022

It's going to be up to you to reproduce exceptions :). Plenty of tests here including OrderBy.

@Xriuk
Copy link
Author

Xriuk commented Nov 22, 2022

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

@BlaiseD
Copy link
Member

BlaiseD commented Nov 22, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants