Giter Site home page Giter Site logo

automapper.extensions.expressionmapping's Introduction

AutoMapper

CI NuGet MyGet (dev) Documentation Status

What is AutoMapper?

AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?

This is the main repository for AutoMapper, but there's more:

How do I get started?

First, configure AutoMapper to know what types you want to map, in the startup of your application:

var configuration = new MapperConfiguration(cfg => 
{
    cfg.CreateMap<Foo, FooDto>();
    cfg.CreateMap<Bar, BarDto>();
});
// only during development, validate your mappings; remove it before release
#if DEBUG
configuration.AssertConfigurationIsValid();
#endif
// use DI (http://docs.automapper.org/en/latest/Dependency-injection.html) or create the mapper yourself
var mapper = configuration.CreateMapper();

Then in your application code, execute the mappings:

var fooDto = mapper.Map<FooDto>(foo);
var barDto = mapper.Map<BarDto>(bar);

Check out the getting started guide. When you're done there, the wiki goes in to the nitty-gritty details. If you have questions, you can post them to Stack Overflow or in our Gitter.

Where can I get it?

First, install NuGet. Then, install AutoMapper from the package manager console:

PM> Install-Package AutoMapper

Or from the .NET CLI as:

dotnet add package AutoMapper

Do you have an issue?

First check if it's already fixed by trying the MyGet build.

You might want to know exactly what your mapping does at runtime.

If you're still running into problems, file an issue above.

License, etc.

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct.

AutoMapper is Copyright © 2009 Jimmy Bogard and other contributors under the MIT license.

.NET Foundation

This project is supported by the .NET Foundation.

automapper.extensions.expressionmapping's People

Contributors

acjh avatar blaised avatar csboling avatar erikgjers avatar jbogard avatar jzabroski avatar kakone avatar lbargaoanu avatar lionelvallet avatar say25 avatar tasteful avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

automapper.extensions.expressionmapping's Issues

InvalidOperationException mapping a string with Plus (+) operator

Steps to Reproduce:

        [Fact]
        public void Uses_the_correct_Add_expression_when_mapping_string_plus_operator()
        {
            //Arrange
            Expression<Func<UserModel, bool>> selection = s => s.FullName + "XYX" == "";

            //Act
            Expression<Func<User, bool>> selectionMapped = mapper.MapExpression<Expression<Func<User, bool>>>(selection);
            List<User> users = Users.Where(selectionMapped).ToList();

            //Assert
            Assert.True(users.Count == 0);
        }

Exception:

Message: 
    System.InvalidOperationException : The binary operator Add is not defined for the types 'System.String' and 'System.String'.
  Stack Trace: 
    Expression.GetUserDefinedBinaryOperatorOrThrow(ExpressionType binaryType, String name, Expression left, Expression right, Boolean liftToNull)
    Expression.Add(Expression left, Expression right, MethodInfo method)
    Expression.MakeBinary(ExpressionType binaryType, Expression left, Expression right, Boolean liftToNull, MethodInfo method, LambdaExpression conversion)
    Expression.MakeBinary(ExpressionType binaryType, Expression left, Expression right)
    XpressionMapperVisitor.<VisitBinary>g__DoVisitBinary|19_0(Expression newLeft, Expression newRight, Expression conversion, <>c__DisplayClass19_0& ) line 143
    XpressionMapperVisitor.VisitBinary(BinaryExpression node) line 134
    BinaryExpression.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    XpressionMapperVisitor.VisitBinary(BinaryExpression node) line 134
    BinaryExpression.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    MapperExtensions.<MapExpression>g__MapBody|5_1[TDestDelegate](Dictionary`2 typeMappings, XpressionMapperVisitor visitor, <>c__DisplayClass5_0`1& ) line 89
    MapperExtensions.<MapExpression>g__CreateVisitor|5_0[TDestDelegate](Dictionary`2 typeMappings, <>c__DisplayClass5_0`1& ) line 86
    MapperExtensions.MapExpression[TDestDelegate](IConfigurationProvider configurationProvider, LambdaExpression expression, Type typeSourceFunc, Type typeDestFunc, Func`3 getVisitor) line 83
    MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression, Func`3 getVisitor) line 66
    MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression) line 56
    XpressionMapperTests.Uses_the_correct_Add_expression_when_mapping_string_plus_operator() line 186

Mapping Expressions - Why does is cause System.ArgumentException?

Hi.

I have a question to the Expression mapping provided by AutoMapper. I am using AutoMapper 8.0 and the provided example with the OrderLines and OrderLindeDTOs. I am getting a

System.ArgumentException

which says something similar to

The property "System.String Item" for type "OrderLine" is not definied.
(Original: 'Die Eigenschaft "System.String Item" ist für den Typ "OrderLine" nicht definiert.')

The thing is why I have to use that UseAsDataSource method combined with a For<OrderLineDTO>()call. Isn't the expression mapping making a
Expression<Func<OrderLineDTO, bool>> to an Expression<Func<OrderLine, bool>> with all the mapped properties?
I am using Entity Framework Core 2.1 with SQLite.

class Program
    {
        static void Main(string[] args)
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<OrderLine, OrderLineDTO>()
                    .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name));
                cfg.CreateMap<OrderLineDTO, OrderLine>()
                    .ForMember(ol => ol.Item, conf => conf.MapFrom(dto => dto));
                cfg.CreateMap<OrderLineDTO, Item>()
                    .ForMember(i => i.Name, conf => conf.MapFrom(dto => dto.Item));
            });

            using (var context = new DataContext())
            {
                context.Database.Migrate();

                Expression<Func<OrderLineDTO, bool>> dtoExpression = dto => dto.Item.StartsWith("A");
                var expression = Mapper.Map<Expression<Func<OrderLine, bool>>>(dtoExpression);

                var orderLines = context.OrderLines.Where(expression).ToArray();
            }

        }
    }

    public class DataContext : DbContext
    {
        public DbSet<OrderLine> OrderLines { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Data Source=orders.db");
        }
    }

    public class OrderLine
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public Item Item { get; set; }
        public decimal Quantity { get; set; }
    }

    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class OrderLineDTO
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public string Item { get; set; }
        public decimal Quantity { get; set; }
    }

MapExpression throws "Missing type map configuration or unsupported mapping" error

`

		var config = new MapperConfiguration(cfg =>
		{
			cfg.AddExpressionMapping();

			cfg.CreateMap<Source, SourceDto>()
				.ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => s.Name)));

			cfg.CreateMap<SourceDto, Source>()
				.ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => new SubSource{Name = s})));
		});

		var mapper = config.CreateMapper();

		// This works
		Expression<Func<Source, bool>> expression1 = o => string.Equals("item1", "item2");
		var mapped1 = mapper.MapExpression<Expression<Func<SourceDto, bool>>>(expression1);

		// This does not
		Expression<Func<SourceDto, bool>> expression2 = o => string.Equals("item1", "item2");
		var mapped2 = mapper.MapExpression<Expression<Func<Source, bool>>>(expression2);`

When trying to map the above expression, automapper throws an unsupported mapping error:

Missing type map configuration or unsupported mapping.

Mapping types:
String -> SubSource
System.String -> ExpressionMapping.SubSource

Why does automapper need this mapping?
When I add the following:
cfg.CreateMap<string, SubSource>().ConvertUsing(o => new SubSource { Name = o });

I receive this error:

Expression of type 'ExpressionMapping.SubSource' cannot be used for parameter of type 'System.String' of method 'Boolean Equals(System.String, System.String)' (Parameter 'arg0')

Why does automapper try to map the first argument in string.equals to SubSource?

This error appears in 10.0.0(Automapper) and 4.0.0(Expressions). When downgrading to 9.0.0 and 3.1.2 the error is no longer thrown.

Classes:
public class Source { public ICollection<SubSource> Items { get; set; } }

public class SubSource { public int ID { get; set; } public string Name { get; set; } }

public class SourceDto { public string[] Items { get; set; } }

Bad LINQ when using with OData

I have a test case where I am using AutoMapper.ProjectTo to translate from EF (6) objects and return the IQueryable to Web Api Odata 7 (running dotnet core 2.1 on full framework).

The following simple query:

        var query1 = Repository.MandatorSet
            .Take(10)
            .ProjectTo<Api.Odata.OpsTenant>(new Dictionary<string, object>(), includes.ToArray());

works like a charm.

Replacing this with the following UseDataSource:

        var query2 = Repository.MandatorSet
            .Take(10)
            .UseAsDataSource()
            .OnError(OnException)
            .BeforeProjection(new EntityFrameworkCompatibilityVisitor())
            .For<Api.Odata.OpsTenant>(new Dictionary<string, object>(), includes.ToArray())
            .AsQueryable();
            ;

results in a ArgumentException "Argument types do not match" with no SQL generated and the stack trace:

at System.Linq.Expressions.Expression.Bind(MemberInfo member, Expression expression)
at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection1 nodes, Func2 elementVisitor)
at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression[T](Expression1 expression) at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable1 source, TAccumulate seed, Func3 func) at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node) at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection1 nodes)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node)
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider2.ConvertDestinationExpressionToSourceExpression(Expression expression) at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider2.Execute[TResult](Expression expression)

There is no information available on what is not working.

As can be seen, I have already added an ExpressionVisitor with the following code:

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // replace call to "LongCount" with "Count"            
        if (node.Method.IsGenericMethod && node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == "LongCount")
        {
            var method = node.Method.DeclaringType.GetMethods(BindingFlags.Public | BindingFlags.Static).First(m => m.Name == "Count");
            method = method.MakeGenericMethod(node.Method.GetGenericArguments());
            return Expression.Call(method, node.Arguments);
        }

        return base.VisitMethodCall(node);
    }

but I seem to be unable to get the Lambda that the 2nd query is generating. Query 1 - the one using ProjectTO, generates:

.Call System.Linq.Queryable.Select(
.Call System.Linq.Queryable.Take(
.Call .Constant<System.Data.Entity.Core.Objects.ObjectQuery1[DAL.MyTypes.G.TMandator]>(System.Data.Entity.Core.Objects.ObjectQuery1[DAL.MyTypes.G.TMandator]).MergeAs(.Constant<System.Data.Entity.Core.Objects.MergeOption>(AppendOnly))
,
10),
'(.Lambda #Lambda1<System.Func`2[DAL.MyTypes.G.TMandator,Api.Odata.OpsTenant]>))

.Lambda #Lambda1<System.Func2[DAL.MyTypes.G.TMandator,Api.Odata.OpsTenant]>(DAL.MyTypes.G.TMandator $dtoTMandator) { .New Api.Odata.OpsTenant(){ Buildings = .Call System.Linq.Enumerable.ToList(.Call System.Linq.Enumerable.Select( $dtoTMandator.Buildings, .Lambda #Lambda2<System.Func2[DAL.MyTypes.Ob.TBuilding,Api.Odata.CoreBuilding]>)),
Identity = $dtoTMandator.Identity,
Name = $dtoTMandator.Name
}
}

.Lambda #Lambda2<System.Func`2[DAL.MyTypes.Ob.TBuilding,Api.Odata.CoreBuilding]>(DAL.MyTypes.Ob.TBuilding $dtoTBuilding) {
.New Api.Odata.CoreBuilding(){
Identity = $dtoTBuilding.Identity,
Name = $dtoTBuilding.LongName
}
}

whcih is as simple as it ets (as can be seen). For Query 2 - the UseDataSource, all I can ever seem to get is

.Constant<System.Linq.EnumerableQuery`1[Api.Odata.OpsTenant]>(Api.Odata.OpsTenant[])

I will gladly show the generated underlying Lambda if I can get my hands on it.

Whatever it is, the generated queryable and usability is not identical with the projection in the base automapper.

Executing Query2 prior to returning it ( var re = query2.ToArray(); ) works and generates the result.

I can likely provide a multi project repro case, I am willing to run any analysis that helps on my test project, but I am unsure at the moment how to even get the lambda that actually is having problems here -I assume it has something to do with what OData does, but without seeing what it is.... On Exception sadly does not provide any more context information.

Mapping GroupBy.SelectMany Expression throws a System.InvalidOperationException.

Steps to reproduce:

    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration
           (
                cfg =>
                {
                    cfg.AddExpressionMapping();

                    cfg.CreateMap<User, UserModel>()
                     .ForMember(d => d.Id, opt => opt.MapFrom(s => s.UserId))
                     .ForMember(d => d.FullName, opt => opt.MapFrom(s => string.Concat(s.FirstName, " ", s.LastName)))
                     .ForMember(d => d.AgeInYears, opt => opt.MapFrom(s => s.Age));
                }
           );
           var mapper = new Mapper(config);

            Expression<Func<IQueryable<UserModel>, IQueryable<UserModel>>> exp = q => q.OrderBy(s => s.Id).ThenBy(s => s.FullName).GroupBy(s => s.AgeInYears).SelectMany(grp => grp);

            Expression<Func<IQueryable<User>, IQueryable<User>>> expMapped = mapper.Map<Expression<Func<IQueryable<User>, IQueryable<User>>>>(exp);
        }
    }

    public class User
    {
        public int UserId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public bool Active { get; set; }
    }

    public class UserModel
    {
        public int Id { get; set; }
        public string FullName { get; set; }
        public string AccountName { get; set; }
        public int AgeInYears { get; set; }
        public bool IsActive { get; set; }
    }

Error:

AutoMapper.AutoMapperMappingException
  HResult=0x80131500
  Message=Error mapping types.
  Source=<Cannot evaluate the exception source>
  StackTrace:
<Cannot evaluate the exception stack trace>

Inner Exception 1:
InvalidOperationException: No generic method 'SelectMany' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic. 

UseAsDataSource failing with interfaces on the DTO.

Not a complete example, but the following code:

                    var tname = typeof(T).Name;
                    var pName = typeof(P).Name;

                    var query = Repository.Set<T>()
                        // No tracking. 
                        //.AsNoTracking()
                        .UseAsDataSource(MapperConfiguration)
                        .For<P>(EmptyParams, includes)
                        //.ProjectTo<P>(MapperConfiguration, EmptyParams, includes)
                    ;

where the following names are resolved at runtime (to show those are real object names as per naming convention):

  Name Value Type
  tname "GTMandator" string
  Name Value Type
  pName "Account" string

results upon execution on a runtime error the moment the following where clause is added to query:

.Where(x => x.Identity == key);

I get an exception like this:

System.ArgumentException
HResult=0x80070057
Message=Property 'System.Guid GIdentity' is not defined for type 'Api.Odata.IIdentityEntity' (Parameter 'property')
Source=System.Linq.Expressions

StackTrace:
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at AutoMapper.Internal.ExpressionFactory.ReplaceParameters(LambdaExpression exp, Expression[] replace)
at AutoMapper.ExpressionExtensions.ReplaceParameters(LambdaExpression exp, Expression[] replace)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMember(MemberExpression node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitBinary(BinaryExpression node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression[T](Expression1 expression) at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambda[T](Expression1 node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.<>c__231.<VisitAllParametersExpression>b__23_9(Expression e, ExpressionVisitor v) at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable1 source, TAccumulate seed, Func3 func) at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitAllParametersExpression[T](Expression1 expression)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambda[T](Expression1 node) at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node) at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection1 nodes)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node)
at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection1 nodes) at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node) at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node) at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider2.ConvertDestinationExpressionToSourceExpression(Expression expression)
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider2.Execute[TResult](Expression expression) at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable1 source)
at Api.Odata.Web.Code.ODataIdentityEntityController2.Get(Guid key, ODataQueryOptions1 options) in C:\Work\OBB\Source.Backend\Api.Odata.Web\Code\ODataIdentityEntityController.cs:line 43

This exception was originally thrown at this call stack:
System.Linq.Expressions.Expression.Property(System.Linq.Expressions.Expression, System.Reflection.PropertyInfo)
AutoMapper.Internal.ExpressionFactory.ReplaceParameters(System.Linq.Expressions.LambdaExpression, System.Linq.Expressions.Expression[])
AutoMapper.ExpressionExtensions.ReplaceParameters(System.Linq.Expressions.LambdaExpression, System.Linq.Expressions.Expression[])
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMember(System.Linq.Expressions.MemberExpression)
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitBinary(System.Linq.Expressions.BinaryExpression)
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression(System.Linq.Expressions.Expression)
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambda(System.Linq.Expressions.Expression)
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitAllParametersExpression.AnonymousMethod__23_9(System.Linq.Expressions.Expression, System.Linq.Expressions.ExpressionVisitor)
System.Linq.Enumerable.Aggregate<TSource, TAccumulate>(System.Collections.Generic.IEnumerable, TAccumulate, System.Func<TAccumulate, TSource, TAccumulate>)
AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitAllParametersExpression(System.Linq.Expressions.Expression)
...
[Call Stack Truncated]

The only way this enters is as following:

  • Account implements IIDentityEntity, which looks like this:

    public interface IIdentityEntity
    {

      Guid Identity { get; }
    

    }

I use it as a base interface so I can use one generic controller which will automatically know what the key on the entity is. It defines the Identity as Identity which then is mapped into GIdentity on the EFCore data source side.

Somewhere in there the mapping visitor is getting confused and is not managing to map that interface to the backend property which it SOMEHOW assumes to be on the interface.

EFCore 3 OData filter with automapper throws client evaluation exception

EFCore 3.1
OData 7.3.0
Automapper 9.0.0
AutoMapper.Extensions.ExpressionMapping 3.1.0

I have a simple projection like this:

// edit: fix: change UserType from class to enum
public enum UserType {
   Human = 0,
   NonHuman = 1
}

// db model
public class User {
    public string Name { get; set; }
    public UserType UserType { get; set; }
}

// application model
public class UserDTO {
    public string Name { get; set ; }
    // for our business needs, this has to be a string, and not an enum
    public string UserType { get; set; } 
}
using AutoMapper;
public class AutoMapping : Profile
{
    public AutoMapping()
    {
        CreateMap<User, UserDTO>()
            .ForMember(dto => dto.UserType, opt => opt.MapFrom(db => db.UserType.ToString()));
    }
}

And then I have the query expression (created from OData)

    // this line of code does not really work, but it is just to illustrate a query generated from odata
    var dtoFilter = where<UserDTO>(dto => dto.UserType == "Human");

    // _mapper is injected
    var dbFilter = _mapper.MapExpression<Expression<Func<User, bool>>>(dtoFilter);

    query = query.where(dbFilter);
    return query.ToList();

This resulted in the following exception:

System.InvalidOperationException: The LINQ expression 'DbSet<User>
    .Where(m => m.UserType.ToString() == __TypedProperty_0)' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
 ...

I could reproduce exact error with the following linq query:

var result = _dbContext.Users
    .Where(x => x.UserType.ToString() == "Human")
    .ToListAsync().Result;

I could also fix the linq query by mapping the string parameter, instead of mapping the db paramter:

var result = _dbContext.Users
    .Where(x => x.UserType == Enum.Parse<UserType>("Human"))
    .ToListAsync().Result;

Is there something else I need to do to avoid this?

Argument Exception : Property is not defined when mapping a lambda using properties of child object

Hello,
I'm stuck with a specific scenario in ExpressionMapper.
In the reproduction code below (a simple console app), in the last mapping I have an ArgumentException saying "Property 'System.DateTime EventDate' is not defined for Type TestExpressionMapper.EventEntity" which is obviously wrong since the property exists on the class. Is there any mistake in my code or in the way i'm using ExpressionMapper or is it a non-covered scenario ? If so, is there a known workaround ?

Thanks !

public class TestExpressionMapper
    {
        private static IMapper _mapper;

        static void Main()
        {
            #region Populate test

            List<EmployeeEntity> empEntity = new List<EmployeeEntity>
            {
                new EmployeeEntity { Id = 1, Name = "Jean-Louis", Age = 39, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
                new EmployeeEntity { Id = 2, Name = "Jean-Paul", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
                new EmployeeEntity { Id = 3, Name = "Jean-Christophe", Age = 19, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
                new EmployeeEntity { Id = 4, Name = "Jean-Marie", Age = 27, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-3) } }.ToList() },
                new EmployeeEntity { Id = 5, Name = "Jean-Marc", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() },
                new EmployeeEntity { Id = 5, Name = "Jean-Pierre", Age = 22, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-5) } }.ToList() },
                new EmployeeEntity { Id = 6, Name = "Christophe", Age = 55, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
                new EmployeeEntity { Id = 7, Name = "Marc", Age = 23, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
                new EmployeeEntity { Id = 8, Name = "Paul", Age = 38, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-1) } }.ToList() },
                new EmployeeEntity { Id = 9, Name = "Jean", Age = 32, Events = new EventEntity[]{ new EventEntity { EventType = "Start", EventDate = DateTime.Today.AddYears(-10) }, new EventEntity { EventType = "Stop", EventDate = DateTime.Today.AddYears(-2) } }.ToList() },
            };

            #endregion

            #region Mapping config

            _mapper = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<EmployeeModel, EmployeeEntity>().ReverseMap();
                cfg.CreateMap<EventModel, EventEntity>().ReverseMap();
            }).CreateMapper();

            _mapper.ConfigurationProvider.AssertConfigurationIsValid();

            #endregion

            #region Test

            Expression<Func<EmployeeModel, bool>> filter;
            Expression<Func<EmployeeEntity, bool>> mappedFilter;

            // Works : Returns employees whose name starts with "Jean"
            filter = emp => emp.Name.StartsWith("Jean");
            mappedFilter = _mapper.MapExpression<Expression<Func<EmployeeEntity, bool>>>(filter);
            var res1 = empEntity.AsQueryable().Where(mappedFilter);

            //Works : Returns employees having at least one "Stop" event
            filter = emp => emp.Events.Any(evt => evt.EventType.Equals("Stop"));
            mappedFilter = _mapper.MapExpression<Expression<Func<EmployeeEntity, bool>>>(filter);
            var res2 = empEntity.AsQueryable().Where(mappedFilter);

            //Works : Returns employees having any event older than 3 years
            filter = emp => emp.Events.Any(evt => evt.EventDate < DateTime.Today.AddYears(-3));
            mappedFilter = _mapper.MapExpression<Expression<Func<EmployeeEntity, bool>>>(filter);
            var res3 = empEntity.AsQueryable().Where(mappedFilter);

            //Works : Returns employees having a stop event older than 1 year (no expression mapping -> lambda is built against entities)
            mappedFilter = emp =>
                emp.Events.Any(e => e.EventType.Equals("Stop")) &&
                emp.Events.First(e => e.EventType.Equals("Stop")).EventDate < DateTime.Today.AddYears(-1);
            var res4 = empEntity.AsQueryable().Where(mappedFilter);

            //Breaks on mapping : Same lambda as previous one but built against models then mapped
            filter = emp =>
                emp.Events.Any(e => e.EventType.Equals("Stop")) &&
                emp.Events.First(e => e.EventType.Equals("Stop")).EventDate < DateTime.Today.AddYears(-1);
            mappedFilter = _mapper.MapExpression<Expression<Func<EmployeeEntity, bool>>>(filter);
            var res5 = empEntity.AsQueryable().Where(mappedFilter);

            #endregion

            Console.ReadKey();
        }

    }

    internal class EmployeeEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<EventEntity> Events { get; set; }
    }

    internal class EventEntity
    {
        public string EventType { get; set; }
        public DateTime EventDate { get; set; }
    }


    internal class EmployeeModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<EventModel> Events { get; set; }
    }

    internal class EventModel
    {
        public string EventType { get; set; }
        public DateTime EventDate { get; set; }
    }

OData $expand on navigation property causes error

I left a post on StackOverflow for several days with no responses, and I am sure this has to be something I am doing wrong, but everything being roughly coded to what I think should work simply isn't working.

I have a "DomainContext" class that I am attempting to have exist between my EF Core DbContext and my API. It was very basic mapping proeprties like this:

public class PropertyDomainContext
{
	private readonly PropertyDbContext _dbContext;
	private readonly IMapper _mapper;

	public PropertyDomainContext(PropertyDbContext dbContext, IMapper mapper)
	{
		_dbContext = dbContext;
		_mapper = mapper;
	}

	public IQueryable<AppraisalYear> AppraisalYear => _dbContext.AppraisalYear.UseAsDataSource(_mapper).For<AppraisalYear>();
	. . .
}

Any time I attempt to expand a navigation property through the Url such as https://localhost:44308/odata/AppraisalYear(2019)?$expand=PropertyYearLayer, I get an error "Argument types do not match". I tried debugging the problem on my own and I think the problem is that the approach I am using is transforming the parent type, but failing to transform the type for the child navigation property.

I believe this isn't a problem with my mapping profile because I can hit the endpoint for both https://localhost:44308/odata/AppraisalYear and https://localhost:44308/odata/PropertyYearLayer just fine and I can perform other OData operations as well ($skip, $top, $select, $orderby, etc.) The error only occurs when I attempt to use $expand.

I am sure its simply that I am missing a step, not doing something right, using the wrong function, or something else. I don't know where else to turn however.

I try to create based on an example , but I get when I compile the expression - "Code supposed to be unreachable"

Source/destination types

 public partial class WSyslog
    {
        public int Id { get; set; }
        public string MessageText { get; set; }
        public DateTime RecordDate { get; set; }
    }
  public class WSyslogModel
    {
        public int Id { get; set; }
        public string MessageText { get; set; }
        public DateTime RecordDate { get; set; }
    }

Mapping configuration

 public class InfrastructureProfile : Profile
    {
        public InfrastructureProfile()
        {
            CreateMap<WSyslog, WSyslogModel>();
            CreateMap<WSyslogModel, WSyslog>();
        }
    }

Version: 8.1.0

Expected behavior

I expect my expression to be map, based on an example ,

Actual behavior

I try to create based on an example , but I get when I compile the expression - "Code supposed to be unreachable".

   public class SyslogRepository : BaseRepository, ISyslogRepository
    {
        public SyslogRepository(IBaseDbContext _baseDbContext, IMapper _iMapper) : base(_baseDbContext, _iMapper)
        {
        }
....
       public IEnumerable<WSyslogModel> Where(Expression<Func<WSyslogModel, bool>> 
       predicate)
        {
            try
            {
                Expression<Func<WSyslog, bool>> expression = _iMapper.Map<Expression<Func<WSyslog, bool>>>(predicate);
//here I get an error
                var found = DbSet.Where(expression).ToList();
                var result = _iMapper.Map<IList<WSyslog>, IList<WSyslogModel>>(found);
                return result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
....
}

Steps to reproduce

0) asp core, DI

 public class Startup
{
...
        public void ConfigureServices(IServiceCollection services)
        {
          ...
              services.AddAutoMapper();

             Mapper.Initialize(cfg =>
             {
                cfg.AddMaps("Clean", "Clean" + ".Infrastructure");
            });
          ...
        }
...
}

1) create map profiles

//for Clean assembly 
 public class ApiProfile : Profile
    {
        public ApiProfile()
        {
             CreateMap<WSyslogViewModel, WSyslogModel>().ReverseMap();
        }
    }
//for Clean.Infrastructure assembly 
  public class InfrastructureProfile : Profile
    {
        public InfrastructureProfile()
        {
            CreateMap<WSyslog, WSyslogModel>();
            CreateMap<WSyslogModel, WSyslog>();
        }
    }

2) the controller that runs the example

    public class SyslogController : Controller
    {

        private readonly ISyslogService _iSyslogService;
        private readonly ILogger<SyslogController> _iLogger;
        public readonly IMapper _iMapper;
        public SyslogController(ISyslogService _iSyslogService,
                           ILogger<SyslogController> _iLogger, IMapper _iMapper)
        {
            this._iLogger = _iLogger;
            this._iSyslogService = _iSyslogService;
            this._iMapper = _iMapper;
        }
        public ActionResult Index()
        {
            try
            {
                //TODO http://docs.automapper.org/en/stable/Expression-Translation-(UseAsDataSource).html
                Expression<Func<WSyslogModel, bool>> dtoExpression = dto => dto.MessageText.StartsWith("Удалено");
                IEnumerable<WSyslogModel> result = _iSyslogService.Where(dtoExpression);
                IList<WSyslogViewModel> vm_users = (_iMapper.Map<IList<WSyslogModel>, IList<WSyslogViewModel>>(result.ToList()));
                return View(vm_users);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
    ...
}

3) SyslogService

 public class SyslogService : ISyslogService
    {
        private readonly ISyslogRepository _iSyslogRepository;

        public SyslogService(ISyslogRepository _iSyslogRepository)
        {
            this._iSyslogRepository = _iSyslogRepository;
        }
...
      public IEnumerable<WSyslogModel> Where(Expression<Func<WSyslogModel, bool>> 
       predicate)
        {
            return _iSyslogRepository.Where(predicate);
        }
    }

4) SyslogRepository

 public class SyslogRepository: BaseRepository, ISyslogRepository
    {
        public SyslogRepository(IBaseDbContext _baseDbContext, IMapper _iMapper) : base(_baseDbContext, _iMapper)
        {
        }
....
       public IEnumerable<WSyslogModel> Where(Expression<Func<WSyslogModel, bool>> 
       predicate)
        {
            try
            {
                Expression<Func<WSyslog, bool>> expression = _iMapper.Map<Expression<Func<WSyslog, bool>>>(predicate);
//here I get an error
                var found = DbSet.Where(expression).ToList();
                var result = _iMapper.Map<IList<WSyslog>, IList<WSyslogModel>>(found);
                return result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
....
}

link to the repository in which you can reproduce this error https://github.com/UseMuse/asp-core-2.2-clean

Incorrect mapping with ConvertUsing

The mapping for ConvertUsing statements seems not to work correctly for my case. Steps to reproduce:

class Program
{
     static void Main(string[] args)
     {
          var config = new MapperConfiguration
         (
             cfg =>
             {
                 cfg.AddExpressionMapping();

                 cfg.CreateMap<EmployeeDTO, Employee>()
                     .ForMember(d => d.Departments, opt => opt.MapFrom(s => s.DepartmentNumbers))
                     .ReverseMap();

                 cfg.CreateMap<int, Department>()
                     .ForMember(d => d.Number, opt => opt.MapFrom(s => s));

                 cfg.CreateMap<Department, int>()
                     .ConvertUsing(x => x.Number);
             }
         );
         var mapper = new Mapper(config);

         Expression<Func<EmployeeDTO, bool>> exp = x => x.DepartmentNumbers.Any(y => y == 2 || y == 4);
         var expMapped = mapper.Map<Expression<Func<Employee, bool>>>(exp);
     }
}

public class Employee
{
   public List<Department> Departments { get; set; }
}

public class Department
{
   public int Number { get; set; }
}

public class EmployeeDTO
{
   public List<int> DepartmentNumbers { get; set; }
}

The resulting expression expMapped is the following:

{x => x.Departments.Any(y => ((y == value(Department)) OrElse (y == value(Department))))}

But this seems to be wrong because when I apply this expression for example on the SQL database it results in this SQL query:

("t2"."id" = 0) OR ("t2"."id" = 0)

UseAsDataSource: "System.ArgumentException : Argument types do not match" when mapping A->B->C

I was struggling on an issue also reported a while ago on the AutoMapper repo:
UseAsDataSource: System.InvalidCastException when mapping A->B->C #2262

I was able to fix one "System.InvalidCastException" when using UseAsDataSource concatenated multiple times and it is now working for objects where the property type do not change (int->int, string->string) but I do run in another issue when the properties are mapped (int->string, string->guid, etc.) in certain constellations.

I wrote some minimalist unit tests
ChangePropertyName_SourceInjectedQuery (working):
[Source(int) -> B(int) -> Destination(int)]

GuidToString_SourceInjectedQuery (fails):
[Source(int)->B(int)->Destination(string)]

GuidToString2_SourceInjectedQuery (working):
[Source(int)->B(string)->Destination(string)]

The unit tests and the patch for the "System.InvalidCastException" can be found in my branch with
Patch fix System.InvalidCastException

Another hint might give, when using UseAsDataSource concatenated from an EF.Core source, I find the Guid serialized to the SQL sting as "3b0db672-f2ff-40ed-a883-6c5661a85c4b", even though I created a map that should serialize the value as "3B0DB672F2FF40EDA8836C5661A85C4B".
cfg.CreateMap<Guid, string>().ConvertUsing(s => s.ToString("N").ToUpper());
Looks to me that the values in the expression are not mapped with AutoMapper?

Maybe my findings help someone fixing the issue or maybe someone can give me hint how to solve this problem?

My goal is to have an Odata WebApi Model (Destination), separated from an Business layer (B) and a Legacy database model (Source) without loosing the query performance as "ToList()" does.

No reference type (parent of) UserId is available to map as an include.

Hi.

When I try to map a property of one type to another, an InvalidOperationException exception is thrown with the message No reference type (parent of) UserId is available to map as an include..

For example, add this test in .\tests\AutoMapper.Extensions.ExpressionMapping.UnitTests\XpressionMapperTests.cs and run it.

        [Fact]
        public void Map_basic_includes_list()
        {
            //Arrange
            ICollection<Expression<Func<UserModel, object>>> selections = new List<Expression<Func<UserModel, object>>>() { s => s.Id, s=> s.AccountName };

            //Act
            IList<Expression<Func<User, object>>> selectionsMapped = mapper.MapIncludesList<Expression<Func<User, object>>>(selections).ToList();
            List<object> accounts = Users.Select(selectionsMapped[0]).ToList();
            List<object> branches = Users.Select(selectionsMapped[1]).ToList();

            //Assert
            Assert.True(accounts.Count == 2 && branches.Count == 2);
        }

Result:

System.InvalidOperationException : The mapped member UserId is of type System.Int32 and a child of the parameter type AutoMapper.Extensions.ExpressionMapping.UnitTests.User.  No reference type (parent of) UserId is available to map as an include.
at AutoMapper.Extensions.ExpressionMapping.FindMemberExpressionsVisitor.VisitMember(MemberExpression node) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\FindMemberExpressionsVisitor.cs:line 55
   at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.MapIncludesVisitor.<VisitMember>g__GetMemberExpression|2_2(FindMemberExpressionsVisitor visitor, Expression mappedExpression) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapIncludesVisitor.cs:line 71
   at AutoMapper.Extensions.ExpressionMapping.MapIncludesVisitor.<VisitMember>g__GetMappedMemberExpression|2_0(Expression parentExpression, List`1 propertyMapInfoList, <>c__DisplayClass2_0& ) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapIncludesVisitor.cs:line 44
   at AutoMapper.Extensions.ExpressionMapping.MapIncludesVisitor.VisitMember(MemberExpression node) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapIncludesVisitor.cs:line 35
   at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.VisitUnary(UnaryExpression node) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\XpressionMapperVisitor.cs:line 232
   at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__MapBody|5_1[TDestDelegate](Dictionary`2 typeMappings, XpressionMapperVisitor visitor, <>c__DisplayClass5_0`1& ) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 89
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__CreateVisitor|5_0[TDestDelegate](Dictionary`2 typeMappings, <>c__DisplayClass5_0`1& ) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 86
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IConfigurationProvider configurationProvider, LambdaExpression expression, Type typeSourceFunc, Type typeDestFunc, Func`3 getVisitor) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 83
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression, Func`3 getVisitor) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 66
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpressionAsInclude[TDestDelegate](IMapper mapper, LambdaExpression expression) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 134
   at System.Linq.Enumerable.SelectEnumerableIterator`2.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapIncludesList[TDestDelegate](IMapper mapper, IEnumerable`1 collection) in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 200
   at AutoMapper.Extensions.ExpressionMapping.UnitTests.XpressionMapperTests.Map_basic_includes_list() in C:\github\icnocop\AutoMapper.Extensions.ExpressionMapping\tests\AutoMapper.Extensions.ExpressionMapping.UnitTests\XpressionMapperTests.cs:line 29

Thank you.

Custom type with op_equality override failed to convert

Source Class

class Source {
    public int Value {get; set;}`
    public static bool operator == (Source x, Source y) ...
    // != and others
}

class Dest {
    public int Value {get; set;}
    //override operator "==", "!=" and related Euqals method
}

var config = new MapperConfiguratioin(cfg => {
    cfg.AddExpressionMapping();
    cfg.CreateMap<Source, Dest>();
});

var value = new Source {Value = 2};
Expression<Func<Source, bool>> expression = x=>x == value;
var mapper = config.CreateMapper();
var mapped = mapper.MapExpression<Expression<Func<Dest, bool>>>(expression);

Error occurred when call mapper.MapExpression with expression message:
System.InvalidOperationException : The operands for operator 'Equal' do not match the parameters of method 'op_Equality'.

Seems BinaryExpression will create new expression with old equal methodinfo, but with new parameter type.

Support for `contains` function in odata query

Hi.

When I try to use an odata query with contains an NotSupportedException is thrown with the message The expression (IIF((([10008].FirstName == null) OrElse False), null, Convert([10008].FirstName.Contains(\"John\"))) == True) is not supported..

I've created a reproducible example here:
TwoProjections.zip

Steps to reproduce:

  1. Open solution in Visual Studio 2017
  2. Restore NuGet packages for solution
  3. Build solution (Debug | Any CPU)
  4. Start both web applications in IIS Express (Set as startup project then Ctrl+F5)
  5. Run all tests.
    The GetPersonBsContains test should fail.
    Note: You may have to close and re-open the solution, and rebuild, for the tests to run.
    Note: The GetPersonBsTop10WithCount test will fail unless you apply the work-around in #10.

.NET Framework 4.6.1
AutoMapper 9.0.0
AutoMapper.Extensions.ExpressionMapping 3.1.0

This example doesn't use Entity Framework.

Thank you.

Argument types do not match if int is mapped to Enum

Version used is 3.1.1
Here are classes and mappings:

    public enum TestEnum
    {
        Prop0 = 0,
        Prop1 = 1
    }

    public class MyEntity
    {
        public int PropVal { get; set; }
    }

    public class MyModel
    {
        public TestEnum PropVal { get; set; }
    }

    public class TestProfile : Profile
    {
        public TestProfile()
        {
            CreateMap<MyEntity, MyModel>()
                .ForMember(e => e.PropVal, o => o.MapFrom(s => (TestEnum)s.PropVal))
                .ReverseMap()
                .ForMember(e => e.PropVal, o => o.MapFrom(s => (int)s.PropVal));
        }
    }

Here is my code:

    var model = new MyModel
    {
        PropVal = TestEnum.Prop1
    };

    var mappedModel = mapper.Map<MyEntity>(model); // works fine

    Expression<Func<MyModel, MyModel>> expr = m => new MyModel { PropVal = m.PropVal };

    var mappedExpr = mapper.MapExpression<Expression<Func<MyEntity, MyEntity>>>(expr); // -> Argument types do not match

can't map expression flattened by IncludeMembers()

In the following, I expect the expression is mapped to p => p.Stats.Speed, but it throws exception "Cannot find member Speed of type Player".

    public class Player
    {
        public string Name { get; set; }
        public Stats Stats { get; set; }
    }

    public class Stats
    {
        public int Speed { get; set; }
        public int Power { get; set; }
    }

    public class PlayerModel
    {
        public string Name { get; set; }
        public int Speed { get; set; }
        public int Power { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var mapper = new MapperConfiguration(c =>
            {
                c.AddExpressionMapping();
                c.CreateMap<Stats, PlayerModel>();
                c.CreateMap<Player, PlayerModel>()
                    .IncludeMembers(p => p.Stats);
            }).CreateMapper();

            Expression<Func<PlayerModel, int>> destExpr = m => m.Speed;
            var srcExpr = mapper.Map<Expression<Func<Player, int>>>(destExpr);
            Console.WriteLine(srcExpr.Body);
        }
    }

Local variable in expression not mapped using MapExpression

Models:

class ModelA
{
    public SubModelA Sub;
}
struct SubModelA
{
    public string Id;
    public static bool operator ==(SubModelA m1, SubModelA m2)
    {
        return m1.Id == m2.Id;
    }
    public static bool operator !=(SubModelA m1, SubModelA m2)
    {
        return !(m1 == m2);
    }
    public override bool Equals(object obj)
    {
        if(obj is SubModelA ma)
        {
            return this == ma;
        }
        return false;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}
class ModelB
{
    public SubModelB Sub;
}
struct SubModelB
{
    public string Id;
    public static bool operator ==(SubModelB m1, SubModelB m2)
    {
        return m1.Id == m2.Id;
    }
    public static bool operator !=(SubModelB m1, SubModelB m2)
    {
        return !(m1 == m2);
    }
    public override bool Equals(object obj)
    {
        if (obj is SubModelB mb)
        {
            return this == mb;
        }
        return false;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

Configure and reproduce:

var config = new MapperConfiguration(cfg =>
{
    cfg.AddExpressionMapping();
    cfg.CreateMap<ModelA, ModelB>().ReverseMap();
    cfg.CreateMap<SubModelA, SubModelB>().ReverseMap();
});
var mapper = config.CreateMapper();

SubModelA suba = new SubModelA { Id = "s1" };

Expression<Func<ModelA, bool>> filter = m => m.Sub == suba;
var mappedfilter = mapper.MapExpression<Expression<Func<ModelB, bool>>>(filter);

Description:
suba in "m => m.Sub == suba" is not mapped to type SubModelB.
the exception is
System.InvalidOperationException: 'The binary operator Equal is not defined for the types 'SubModelB' and 'SubModelA'.'

Reproduce using Automapper 9.0.0 and AutoMapper.Extensions.ExpressionMapping 3.1.1 and .Net Core3.1 .

MapExpression for sub-entity fails

https://stackoverflow.com/questions/61928827/automapper-mapexpression-for-sub-entity-fails
Classes:

public class CountyInfo
{
    public CountyInfo()
    {
        Citizens = new HashSet<Citizen>();
    }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public ICollection<Citizen> Citizens { get; set; }
}

public class Citizen
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ZipCode { get; set; }
}

public class CountyInfoDto
{
    public CountyInfoDto()
    {
        Citizens = new List<CitizenDto>();
    }
    public Guid Id { get; set; }
    public string Name { get; set; }
    public List<CitizenDto> Citizens { get; set; }
}

public class CitizenDto
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ZipCode { get; set; }
}

Mappings:

CreateMap<CountyInfo, CountyInfoDto>().ReverseMap();
CreateMap<Citizen, CitizenDto>().ReverseMap();

Use case:

Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
    Id = c.Id,
    Citizens = c.Citizens.Select(p => new CitizenDto
    {
    }).ToList()
};

var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);

This mapping fails with error Expression of type DTOs.CitizenDto cannot be used for return type Entities.Citizen however in CountyInfoDto property Citizens has type CitizenDto.
Issue reproduced with v3.0.6 since for test in v.3.1.0 it's blocked with #72.

Unflattening not working with UseAsDataSource or ProjectTo

Source/destination types

// source - EntityFramework Model types
public class Order
{
        public int ID { get; set; }

        public int customer_id { get; set; }
        public int customer_name { get; set; }
}

// destination - DTO
public class OrderDTO
{
        public int ID { get; set; }

        public customer customer { get; set; }
}

public class customer
{
        public int ID { get; set; }

        public string name { get; set; }
}

Mapping configuration

public class MyMapperProfile : Profile
{
         public MyMapperProfile(string profileName)
                : base(profileName)
        {
            this.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
            this.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();

             CreateMap<OrderDTO, Order>()
                        .ForMember(m => m.id, src => src.Ignore())
                        .ReverseMap();
        }
}

Versions:

AutoMapper 8.0.0
AutoMapper.Extensions.ExpressionMapping 2.0.0

Issue description

.UseAsDatasource and For<> or ProjectTo does not perform Unflattening as expected, whilst call of Mapper.Map<> on same Mapper configuration performs Unflattening.

Steps to reproduce

This is working code:

var _entities = this.IUnitOfWork.IDataRepository.orders.ToArray();

var _dtos = entities
                                .Select(s => this.IMapper.Map<Order, OrderDTO>(s))
                                .ToArray();

// customer is properly mapped using Unflattening
Assert.AreEqual<string>(_entities.First().customer_name,    _dtos.First().customer.name);

This is not working code:

var _dtos = this.IUnitOfWork.IDataRepository.orders.
                                .UseAsDataSource(this.IMapper.ConfigurationProvider)
                                .For<OrderDTO>()
                                .ToArray();

// Here it fails, customer object at any _dto is always null.
Assert.AreEqual<string>(_entities.First().customer_name,    _dtos.First().customer.name);

"Method not found" exception upgrading to AutoMapper v8.1.0.

I have upgraded to AutoMapper v8.1.0 and now see this exception:

Method not found: 'System.Linq.Expressions.LambdaExpression[] AutoMapper.QueryableExtensions.IExpressionBuilder.GetMapExpression(System.Type, System.Type, System.Collections.Generic.IDictionary`2<System.String,System.Object>, System.Reflection.MemberInfo[])'.

Reverting to AutoMapper v8.0.0 works.

The method looks unaltered in the code, and I think the exception is incorrect, but when trying to build the current master branch against AutoMapper v8.1.0, it fails as some static methods have been removed.

UseAsDataSource with GroupBy

AutoMapper: 7.0.1
AutoMapper.Extensions.ExpressionMapping: 1.0.2

I'm trying to make use of UseAsDataSource with GroupBy but it fails. Gist is here: gist
Where and OrderBy works like a charm. I have tried using GroupBy before UseAsDataSource with Entity objects, and it works. Is there a way to group data with UseAsDataSource ?

Note: Adding cfg.AddExpressionMapping(); does not help.

StackTrace:

at System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value, Type targetType) 
at System.Collections.Generic.List`1.System.Collections.IList.Add(Object item) 
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.Execute[TResult](Expression expression) 
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery`2.GetEnumerator()
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery`2.System.Collections.IEnumerable.GetEnumerator()
at UserQuery.Main(String[] args)
--

Message:

The value "System.Linq.Lookup`2+Grouping[System.Int32,UserQuery+Model]" is not of type "System.Linq.IGrouping`2[System.Int32,UserQuery+ModelDto]" and cannot be used in this generic collection.Parameter name: value
--

System.ArgumentException: "Argument types do not match" when mapping Expression.Condition filters.

To reproduce, add the following test case:

        [Fact]
        public void Map__object_including_child_and_grandchild_with_conditional_filter()
        {
            //Arrange
            ParameterExpression userParam = Expression.Parameter(typeof(UserModel), "s");
            MemberExpression accountModelProperty = Expression.MakeMemberAccess(userParam, PrimitiveHelper.GetFieldOrProperty(typeof(UserModel), "AccountModel"));
            MemberExpression branchModelProperty = Expression.MakeMemberAccess(accountModelProperty, PrimitiveHelper.GetFieldOrProperty(typeof(AccountModel), "Branch"));
            MemberExpression nameProperty = Expression.MakeMemberAccess(branchModelProperty, PrimitiveHelper.GetFieldOrProperty(typeof(BranchModel), "Name"));

            //{s => (IIF((IIF((s.AccountModel == null), null, s.AccountModel.Branch) == null), null, s.AccountModel.Branch.Name) == "Leeds")}
            Expression<Func<UserModel, bool>> selection = Expression.Lambda<Func<UserModel, bool>>
            (
                Expression.Equal
                (
                    Expression.Condition
                    (
                        Expression.Equal
                        (
                            Expression.Condition
                            (
                                Expression.Equal
                                (
                                    accountModelProperty,
                                    Expression.Constant(null, typeof(AccountModel))
                                ),
                                Expression.Constant(null, typeof(BranchModel)),
                                branchModelProperty
                            ),
                            Expression.Constant(null, typeof(BranchModel))
                        ),
                        Expression.Constant(null, typeof(string)),
                        nameProperty
                    ),
                    Expression.Constant("Park Row", typeof(string))
                ),
                userParam
            );

            Expression<Func<User, bool>> selectionMapped = mapper.Map<Expression<Func<User, bool>>>(selection);
            List<User> users = Users.Where(selectionMapped).ToList();

            //Assert
            Assert.True(users.Count == 1);
        }

Source and destination must have the same number of arguments.

I use Automaper 7.0 with .Net Core 2.1 When I map

// Expression<Func<ReferenceDomainModel, bool>> predicate
var predicate1 = _mapper.MapExpression<Expression<Func<Reference, bool>>>(predicate);

When execute method where map expression, raise exception:

{
  "error": {
    "code": "ArgumentException",
    "message": "Source and destination must have the same number of arguments.",
    "target": "AutoMapper.Extensions.ExpressionMapping.MapperExtensions.DoAddTypeMappings"
  }
}

Classes 'Reference' and 'ReferenceDomainModel' mapped in profile as

CreateMap<Reference, ReferenceDomainModel>();

In main project mapper configured:

private IMapper GetAutomapperInstance()
        {
            var profiles = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(x => x.DefinedTypes)
                .Where(type => typeof(Profile).IsAssignableFrom(type.AsType())
                               && !type.IsAbstract
                               && type.GetConstructor(Type.EmptyTypes) != null).ToList();

            // Initialize AutoMapper with each instance of the profiles found.
            var mapperConfiguration = new MapperConfiguration(a =>
            {
                a.AddExpressionMapping();
                profiles.ForEach(a.AddProfile);
            });

            return mapperConfiguration.CreateMapper();
        }

Type Binary and Conversion expressions are not being mapped as expected.

Hey, I'm trying to map an expression that does a type check and a cast involving derived types, but the types in the check and cast expressions are not being mapped. I've cloned the repo and created a new test based on ExpressionMappingPropertyFromBaseClass, altering the Because_of to below.

protected override void Because_of()
{
    //Arrange
    _source = new List<BaseEntity> {
        new Entity { Id = Guid.NewGuid(), Name = "Sofia" },
        new Entity { Id = Guid.NewGuid(), Name = "Rafael" },
        new BaseEntity { Id = Guid.NewGuid() }
    };

    // Act
    Expression<Func<BaseDTO, bool>> dtoQueryExpression = r => (r is DTO ? ((DTO)r).Name : "") == "Sofia";
    var entityQueryExpression = Mapper.Map<Expression<Func<BaseEntity, bool>>>(dtoQueryExpression);
    entityQuery = _source.AsQueryable().Where(entityQueryExpression);
}

I would expect the expression to map to the equivalent of this,

Expression<Func<BaseEntity, bool>> mapped = r => (r is Entity ? ((Entity)r).Name : "") == "Sofia";

However, the output is currently the equivalent of,

Expression<Func<BaseEntity, bool>> mapped = r => (r is DTO ? ((DTO)r).Name : "") == "Sofia";

I'll attempt a fix and PR but any thoughts or guidance would be appreciated. The full failing test can be seen in my fork.

ArgumentException for expression parameter on UseAsDataSource().OrderBy with nullable type in destination

When a DTO has a nullable property for a non-nullable source expression UseAsDataSource throws when trying to OrderBy on the destination property.

The following testcase reproduces this exception:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Shouldly;
using Xunit;

namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
{
    public class MappingToNullablePropertyUsingUseAsDataSource
    {
        [Fact]
        public void When_Apply_OrderBy_Clause_Over_Queryable_As_Data_Source()
        {
            // Arrange
            var mapper = CreateMapper();

            var models = new List<Model>()
            {
                new Model {Value = 1},
                new Model {Value = 2}
            };

            var queryable = models.AsQueryable();

            Expression<Func<DTO, int?>> dtoPropertySelector = (dto) => dto.Value;

            // Act
            var result = queryable.UseAsDataSource(mapper).For<DTO>().OrderBy(dtoPropertySelector).ToList();

            // Assert
            result.ShouldNotBeNull();
            result.Count.ShouldBe(2);
        }

        private static IMapper CreateMapper()
        {
            var mapperConfig = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Model, DTO>();
            });

            var mapper = mapperConfig.CreateMapper();
            return mapper;
        }

        private class Model
        {
            public int Value { get; set; }
        }

        private class DTO
        {
            public int? Value { get; set; }
        }

    }
}

The error occurs in ExpressionMapper.GetConvertedMethodCall, where parameter dto => dto.Value of type Func<DTO, Nullable<int>> gets converted to Func<Model, int> and the generic type replacement doesn't cover the return type of the Func.

I tried to fix this myself, but I think this requires matching the converted arguments and their types against the generic arguments of the method, for which I couldn't think of a generic approach that wouldn't involve mapping for known types or method signatures only.

UseAsDataSource with async/await operators

Hi guys,

I am using AutoMapper 7.0.1 with EF Core 2.1 and I would love to use UseAsDataSource with async/await operators. This is my code:

var _query = this.IUnitOfWork.IDataCrmRepository.contacts
                                .Where(w => w.org_id == config_org_id)
                                .UseAsDataSource(this.IMapper.ConfigurationProvider)
                                .For<DTO.contact>()
                                ;
// nov order by optional navigation property (address) which may be null    
_query = _query.OrderBy(o => o.address.city);

return _query.ToArray();

UseAsDataSource does the job very well, except I want to do SQL in async mode and await result, just like this:

return await _query.ToArrayAsync();

This code runs into exception:

System.InvalidOperationException: The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.  
  at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)   
  at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.CountAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

I could eventually use ProjectTo, but it leads to NullReferenceException because navigation properti address may be null:

System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method(Closure , TransparentIdentifier`2 )
   at System.Linq.EnumerableSorter`2.ComputeKeys(TElement[] elements, Int32 count)
   at System.Linq.EnumerableSorter`1.ComputeMap(TElement[] elements, Int32 count)
   at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)
   at System.Linq.OrderedEnumerable`1.GetEnumerator()+MoveNext()

How can I use UseAsDataSource with async/await?

Create profile like Classic AutoMapper

Hello @jbogard and @ALL,

I have two questions:

  1. How to create profiles (like classic AutoMapper profiles) for Expression Mapping? Can you provide an example?

  2. In classic AutoMapper, we can use .Map(object, sourceType, targetType). Is it possible to use this for ExpressionMapping too?

Thanks!

Select list property over IQueryable after UseAsDatasource

Source/destination types

namespace Simple
{
    public class A
    {
        public IList<AA> Aas { get; set; }
        public class AA
        {
            public string Str { get; set; }
        }
    }

    public class B
    {
        public IList<BA> Bas { get; set; }
        public class BA
        {
            public string Str { get; set; }
        }
    }
}

Mapping configuration

private static IMapper CreateMapper()
{
	var mapperConfig = new MapperConfiguration(cfg =>
	{
		cfg.CreateMap<A, B>().ForMember(b => b.Bas, expression => expression.MapFrom(a => a.Aas));
		cfg.CreateMap<B, A>().ForMember(a => a.Aas, expression => expression.MapFrom(b => b.Bas));

		cfg.CreateMap<A.AA, B.BA>();
		cfg.CreateMap<B.BA, A.AA>();

	});

	var mapper = mapperConfig.CreateMapper();
	return mapper;
}

Version: x.y.z

Automapper 7.0.1
Automapper.Extensions.ExpressionMapping 1.0.3

Expected behavior

The mapping should occur and the test should succeed

Actual behavior

The code throws an exception

System.ArgumentException : Expression of type 'System.Linq.Expressions.Expression\`1[System.Func\`2[Simple.A,System.Collections.Generic.IList\`1[Simple.A+AA]]]' cannot be used for parameter of type 'System.Linq.Expressions.Expression\`1[System.Func\`2[Simple.A,System.Collections.Generic.IList\`1[Simple.B+BA]]]' of method 'System.Linq.IQueryable\`1[System.Collections.Generic.IList\`1[Simple.B+BA]] Select[A,IList\`1](System.Linq.IQueryable\`1[Simple.A], System.Linq.Expressions.Expression\`1[System.Func\`2[Simple.A,System.Collections.Generic.IList\`1[Simple.B+BA]]])'
Parameter name: arg1
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable\`1 arguments)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, IEnumerable\`1 arguments)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node) 
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node) 
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider\`2.ConvertDestinationExpressionToSourceExpression(Expression expression) 
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider\`2.Execute[TResult](Expression expression) 
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery\`2.GetEnumerator() 
   at System.Collections.Generic.List\`1.AddEnumerable(IEnumerable\`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable\`1 source)
   at AutoMapper.Extensions.ExpressionMapping.UnitTests.QueryableSelectionWithUseAsDataSource.When_Select_List_Over_Queryable_As_Data_Source()

Steps to reproduce

using System.Collections.Generic;
using System.Linq;
using Shouldly;
using Simple;
using Xunit;

namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
{
    public class QueryableSelectionWithUseAsDataSource
    {
        [Fact]
        public void When_Select_List_Over_Queryable_As_Data_Source()
        {
            // Arrange
            var mapper = CreateMapper();

            var models = new List<A>()
            {
                new A {
                    Aas = new List<A.AA>
                    {
                        new A.AA { Str = "Hey"},
                        new A.AA { Str = "Hey"},
                        new A.AA { Str = "Hey"},
                        new A.AA { Str = "Hey"}
                    }
                },
                new A {
                    Aas = new List<A.AA>
                    {
                        new A.AA { Str = "Hoi"},
                        new A.AA { Str = "Hoi"},
                        new A.AA { Str = "Hoi"},
                        new A.AA { Str = "Hoi"}
                    }
                },
                new A {
                    Aas = new List<A.AA>
                    {
                        new A.AA { Str = "Hui"},
                        new A.AA { Str = "Hui"},
                        new A.AA { Str = "Hui"},
                        new A.AA { Str = "Hui"}
                    }
                },
                new A {
                    Aas = new List<A.AA>
                    {
                        new A.AA { Str = "Hai"},
                        new A.AA { Str = "Hai"},
                        new A.AA { Str = "Hai"},
                        new A.AA { Str = "Hai"}
                    }
                }
            };

            var queryable = models.AsQueryable();

            // Act
            var selectedQueryable = queryable
                .UseAsDataSource(mapper)
                .For<B>()
                .Select(dto => dto.Bas);

            var result = selectedQueryable.ToList();

            // Assert
            result.ShouldNotBeNull();

        }
    }
}

https://gist.github.com/JonLopezGarcia/9218cbd0a85523d781d3f901d0610fcf

Investigation

The code throws the exception when it tries to create the new method call expression, mapping from Select ( dto=> dto.Bas ) to Select( dto => dto.Aas ) at AutoMapper.Mappers.ExpressionMapper.cs -> GetConvertedMethodCall(MethodCallExpression node) -> line 71.

To convert the method argument types for the new Select method, it test that the actual method argument types (in this case B and IList<B.BA>) correspond to one generic argument of the parameters (in this case B[] and Expression<Func<B[], IList<B.BA>>).
In case of B, it find the same type on the generic argument of B[]. So it maps B[] to A[]. In case of IList<B.BA> it does not find the same type on the generic argument of Expression<Func<B[], IList<B.BA>> because it generic argument is Func<B[], IList<B.BA>> in that case it does not map IList<B.BA> and end up trying to create a method call expression of Select<A, IList<A.AA>(...) with A[] and Expression<Func<A, IList<B.BA>>

CreateQuery(Expression expression) produces wrong ElementType for Select queries

Hi,

Automapper: 9.0.0
AutoMapper.Extensions.ExpressionMapping: 3.1.0

We ran into an issue with dynamically generated Select queries.
The non-generic SourceInjectedQueryProvider.CreateQuery always uses the original TDestination type and this leads problems when paired with Select. Because the ElementType of the query will remain the same.

public IQueryable CreateQuery(Expression expression)
=> new SourceSourceInjectedQuery<TSource, TDestination>(this, expression, EnumerationHandler, _exceptionHandler);

For example instead of getting a list of Ids the result will be a list of DTOs with only the Id field populated.

I've looked into how other Queryables implement this and they take the generic type parameter of the given Expression as the Destination type.

I can create a PR with a fix if that's ok for you.

Here is a test with our use case to repro it:

// Result will be
// Enumerable | Expected: System.Int32 Actual: System.Int32
// DataSource | Expected: System.Int32 Actual: Test.MappingTests+TestDto
    
    public class Test { public int Number { get; set; } }
    public class TestDto { public int Number { get; set; } }

    [TestMethod]
    public void QueryTest()
    {
      var config = new MapperConfiguration(c => c.CreateMap<Test, TestDto>());

      var query = new List<Test> {new Test {Number = 1}}.AsQueryable();
      var enumerable = query.Select(x => new TestDto { Number = x.Number }).AsQueryable();
      var dataSource = query.UseAsDataSource(config).For<TestDto>();

      CompareTypes(enumerable, "Enumerable");
      CompareTypes(dataSource, "DataSource");
    }

    private void CompareTypes(IQueryable<TestDto> source, string messge)
    {
      var regular = source.Select(x => x.Number);
      var manual = MakeSelect(source, "Number");

      Console.WriteLine($"{messge} | Expected: {regular.ElementType} Actual: {manual.ElementType}");
      Assert.AreEqual(regular.ElementType, manual.ElementType, messge);
    }

    private IQueryable MakeSelect(IQueryable source, string name)
    {
      var thisExpression = Expression.Parameter(source.ElementType, "");
      var prop = Expression.Property(thisExpression, name);
      var selector = Expression.Lambda(prop, thisExpression);

      var methodCallExpression = Expression.Call(
          typeof(Queryable),
          nameof(Queryable.Select),
          new [] {source.ElementType, selector.Body.Type}, 
          source.Expression, Expression.Quote(selector)
      );

      return source.Provider.CreateQuery(methodCallExpression);
    }

NotSuportedException when mapping entities with complex types using Entity Framework

I am implementing an OData service, and use AutoMapper with ExpressionMapping to map the OData entities to our domain model (EF Code First).

When projecting OData entities containing complex types, i get a System.NotSupportedException.

I have configured the properties with complex types with the AllowNull option, and it is working fine when using ProjectTo, but not when using UseAsDataSource.For. I am preferring UseAsDataSource.For to allow usage of OnEnumerated callback.

The issue is previously described here, but for ProjectTo:
AutoMapper/AutoMapper#925

UseAsDataSource OData expand trows Argument types do not match exception

Hi!

I have simple project that uses odata. I decided to use an automapper because ist very simple. But i have problem with function UseAsDataSource(). When i use ProjectTo() all work fine but EF cannot evaluate filter expressions in db and go it locally. But when i use UseAsDataSource() its throw Argument types do not match exception when specifying $expand. I would like to use the functionality UseAsDataSource() due to performance.

Source/destination types

// DB Objects below

public abstract class BaseDomainObject
{
	[Key]
	public long Id { get; set; }
}

public class Order : BaseDomainObject
{
	public virtual ICollection<OrderPosition> Positions { get;  } = new List<OrderPosition>();
}

public class OrderPosition : BaseDomainObject
{
	public virtual Order Order { get; set; }

	public int Amount { get; set; }
}

// DTO Objects below

public abstract class ApiModel
{
	public long Id { get; set; }
}

public class OrderModel : ApiModel
{
	public IEnumerable<OrderPositionModel> Positions { get; set; }
}

public class OrderPositionModel : ApiModel
{
	public int Amount { get; set; }
}

Mapping configuration

// Profile code

public class OrderProfile : Profile
{
	public OrderProfile()
	{
		CreateMap<BaseDomainObject, ApiModel>();
		CreateMap<Order, OrderModel>().IncludeBase<BaseDomainObject, ApiModel>();
		CreateMap<OrderPosition, OrderPositionModel>().IncludeBase<BaseDomainObject, ApiModel>();
	}
}
	
// Startup code
	
public void ConfigureServices(IServiceCollection services)
{
	services.AddDbContext<OrdersContext>(o =>
	{
		o.UseInMemoryDatabase("test");
		o.UseLazyLoadingProxies();
	});

	services.AddOData();
	services.AddAutoMapper( GetType());
	
	services.AddMvc(options => { options.EnableEndpointRouting = false; }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

AutoMapper Version: 9.0.0

EF Version: 2.2.6

Expected behavior

Expected list expansion when specifying $expand=positions

Actual behavior

Throws Argument types do not match

Steps to reproduce

I create demonstration project

Simply call http://localhost:5000/api/orders?$expand=positions

Best regards. Vitaly

OData integration fail with navigation property

Hi there,

I am using AutoMapper 7.0.1 with EF Core 2.1 and I am trying to write a basic OData integration. This is my working code:

var _query = this.IUnitOfWork.IDataCrmRepository.contacts
                                .Where(w => w.org_id == config_org_id)
                                .UseAsDataSource(this.IMapper.ConfigurationProvider)
                                .For<DTO.contact>()
                                ;
// now order by optional navigation property (address) which may be null    
_query = _query.OrderBy(o => o.address.city);

return _query.ToArray();

So far so good. When I replace hardcoded OrdeBy with ODataQueryOptions like this:

// _query = _query.OrderBy(o => o.address.city);
_query = options.ApplyTo(_query) as IQueryable<DTO.contact>;

When using with first-level property like GET /api/contacts?$orderby=fullname
still working.

Problem comes, when I request GET /api/contacts?$orderby=address/city
I get error:

System.InvalidOperationException: Nullable object must have a value.
   at lambda_method(Closure , QueryContext , TransparentIdentifier`2 )
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) in C:\projects\EFCore.PG\src\EFCore.PG\Storage\Internal\NpgsqlExecutionStrategy.cs:line 51
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery`2.GetEnumerator()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)

Address is optional and in some cases null. However, interesting is, when I manually update database to ensure that all contact have a address (for testing purposes), request does not fail and I get properly sorted result (by address.city)

Thanks for any advice!

Exception Mapping expression with IEnumerable<T>Contains method call on a local variable

Steps to reproduce:

       [Fact]
       public void TestMethod()
       {
           var config = new MapperConfiguration(cfg =>
           {
               cfg.AddExpressionMapping();

               cfg.CreateMap<Source, SourceDto>()
                   .ForMember(o => o.Items, config => config.MapFrom(p => p.Items.Select(s => s.Name)));
           });

           var mapper = config.CreateMapper();

           var items = new string[] { "item1", "item2" };
           Expression<Func<SourceDto, bool>> expression = o => items.Contains("");

           //exception thrown on MapExpression
           var mapped = mapper.MapExpression<Expression<Func<Source, bool>>>(expression);
       }

       public class Source { public ICollection<SubSource> Items { get; set; } }

       public class SubSource { public int ID { get; set; } public string Name { get; set; } }

       public class SourceDto { public string[] Items { get; set; } }
Result StackTrace:	
at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)
   at System.Linq.Expressions.Expression.Call(Type type, String methodName, Type[] typeArguments, Expression[] arguments)
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.<>c__DisplayClass29_0.<VisitMethodCall>g__GetStaticExpression|3() in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\XpressionMapperVisitor.cs:line 391
   at AutoMapper.Extensions.ExpressionMapping.XpressionMapperVisitor.VisitMethodCall(MethodCallExpression node) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\XpressionMapperVisitor.cs:line 381
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__MapBody|8_1[TDestDelegate](Dictionary`2 typeMappings, XpressionMapperVisitor visitor, <>c__DisplayClass8_0`1& ) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 100
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.<MapExpression>g__CreateVisitor|8_0[TDestDelegate](Dictionary`2 typeMappings, <>c__DisplayClass8_0`1& ) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 97
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IConfigurationProvider configurationProvider, LambdaExpression expression, Type typeSourceFunc, Type typeDestFunc, Func`3 getVisitor) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 94
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression, Func`3 getVisitor) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 77
   at AutoMapper.Extensions.ExpressionMapping.MapperExtensions.MapExpression[TDestDelegate](IMapper mapper, LambdaExpression expression) in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\src\AutoMapper.Extensions.ExpressionMapping\MapperExtensions.cs:line 67
   at AutoMapper.Extensions.ExpressionMapping.UnitTests.ShouldOnlyMapExistingTypeMaps.Issue85() in C:\CSharp\Code\.AutoMapper\AutoMapper.Extensions.ExpressionMapping\tests\AutoMapper.Extensions.ExpressionMapping.UnitTests\ShouldOnlyMapExistingTypeMaps.cs:line 26
Result Message:	System.InvalidOperationException : No generic method 'Contains' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.


ArgumentException: Argument types do not match, 4.0.1 regression

Hi,

We updated our package version to 4.0.1 and after the update, began to see ArgumentExceptions being thrown in our code.

I've traced the problem to change #86 implemented in 4.0.1, resolving #85.

We have more complex versions of expressions like this:

Expression<Func<SourceDto, bool>> expression1 = src => ((src != null ? src : null) != null) && src.Items.Any(x => x == "item1");

(the src != null part looks weird, in our code it's different but it reproduces the issue.)
We were previously able to successfully map these kind of expressions, but now the null constant is no longer mapped from it's SourceDto type to Source type.

I believe the reason is that the change to solve #85 has got the resolution for the type mappings switched around.
ConfigurationProvider.ResolveTypeMap(node.Type, newType) != null)
Should be
ConfigurationProvider.ResolveTypeMap(newType, node.Type) != null)

As noted in your code elsewhere:

//The destination becomes the source because to map a source expression to a destination expression,
//we need the expressions used to create the source from the destination

I've made the change and a unitTest to reproduce the scenario, the test fails with the pre-change code and succeeds after the change.
I'll make a PR with these changes.

Kind regards,

Steven.

Mapping of conditional expressions throws a null reference exception

Hi,

We are using your expression mapping to translate our mapping expressions from our DTO definitions into our entity definitions, however, we're running into a problem. This is

We are mapping a conditional expression on a nested class, as shown in the following code:

    public class TestEntity
    {
        public Guid Id { get; set; }
        public int ConditionField { get; set; }
        public int ToBeNestedField { get; set; }
    }

    public class TestDTO
    {
        public Guid Id { get; set; }
        public TestDTONestedClass NestedClass { get; set; }
    }
    public class TestDTONestedClass
    {
        public int? NestedField { get; set; }
    }

    public class TestMapperProfile : Profile
    {
        public TestMapperProfile()
        {
            CreateMap<TestEntity, TestDTO>()
                .ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
                .ForMember(dst => dst.NestedClass, opt => opt.MapFrom(src => src.ConditionField == 1 ? src : default));

            CreateMap<TestEntity, TestDTONestedClass>()
                .ForMember(dst => dst.NestedField, opt => opt.MapFrom(src => (int?)src.ToBeNestedField));
        }
    }

    [TestMethod]
    public void MapExpressionOnNestedClass()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new TestMapperProfile());
            cfg.AddExpressionMapping();
        });

        var mapper = new Mapper(config);

        Expression<Func<TestDTO, bool>> expr = x => x.NestedClass.NestedField == 1;

        var mappedExpression = mapper.Map<Expression<Func<TestEntity, bool>>>(expr); //throws

        Assert.IsNotNull(mappedExpression);
    }

When the code arrives at the GetMemberFullName() method, it tries to cast the Conditional Expression to a MemberExpression, which results in a null reference exception, as shown below:

	public static string GetMemberFullName(this LambdaExpression expr)
	{
		if (expr.Body.NodeType == ExpressionType.Parameter)
			return string.Empty;
	
		MemberExpression me;
		switch (expr.Body.NodeType) // NodeType == ExpressionType.Conditional
		{
			case ExpressionType.Convert:
			case ExpressionType.ConvertChecked:
				me = (expr.Body as UnaryExpression)?.Operand as MemberExpression;
				break;
			default:
				me = expr.Body as MemberExpression; //Results in me == null
				break;
		}
	
		return me.GetPropertyFullName();
	}
	
	public static string GetPropertyFullName(this Expression expression)
	{
		const string period = ".";
	
		//the node represents parameter of the expression
		switch (expression.NodeType) // throws NullReferenceException
		{
			case ExpressionType.MemberAccess:
				var memberExpression = (MemberExpression)expression;
				var parentFullName = memberExpression.Expression.GetPropertyFullName();
				return string.IsNullOrEmpty(parentFullName)
					? memberExpression.Member.Name
					: string.Concat(memberExpression.Expression.GetPropertyFullName(), period, memberExpression.Member.Name);
			default:
				return string.Empty;
		}
	}

Returning an empty string (as would be the default in GetPropertyFullName) seems to work well, but I can't tell if this would break anything else because of my lack of knowledge about the rest of the code in this package.

In our case, I think we're expecting the input expression to be mapped as follows:

Input: x => (x.NestedClass.NestedField == Convert(1))

Output: x => (Convert(x.ToBeNestedField) == Convert(1))

Would love to hear any advice! Thanks in advance.

AutoMapper and Immutable Value Objects - AutoMapper.AutoMapperConfigurationException is thrown when Mapping Expressions

AutoMapper can map expressions with no problem WHEN models/value objects are not immutable.

It works very well with the model/value object when they're immutable and the framework calls the correct constructors (and so on), smoothly.

The problem happens when we're mapping expressions that contains immutable models/value objects, like the example below:

using System;
using System.Linq.Expressions;

using AutoMapper;
using AutoMapper.Extensions.ExpressionMapping;

namespace AutoMapperImmutableModelTest.Domain
{
    public abstract class ModelBase
    {
        public Guid Id { get; protected set; }
        public bool Enabled { get; protected set; }
        public DateTime CreatedAt { get; protected set; }
        public DateTime? UpdatedAt { get; protected set; }
        public DateTime? DeletedAt { get; protected set; }

        public void SetId(Guid id) => 
            Id = id;

        public void SetEnabled(bool enabled) =>
            Enabled = enabled;

        public void SetCreatedAt(DateTime createdAt) =>
            CreatedAt = createdAt;

        public void SetUpdatedAt(DateTime? updatedAt) =>
            UpdatedAt = updatedAt;

        public void SetDeletedAt(DateTime? deletedAt) =>
            DeletedAt = deletedAt;
    }

    public class Person : ModelBase
    {
        public Person(string name) => Name = name; // the automapper doesn't use this guy here //
        public Person(string name, DateTime birthday, bool enabled)
        {
            Name = name;
            Birthday = birthday;
            Enabled = enabled;
        }

        public string Name { get; } // if I add a public set here it works //
        public DateTime Birthday { get; }
    }
}

namespace AutoMapperImmutableModelTest.Repository
{
    public abstract class ModelBase
    {
        public Guid Id { get; set; }
        public bool Enabled { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? UpdatedAt { get; set; }
        public DateTime? DeletedAt { get; set; }
    }

    public class Person : ModelBase
    {
        public Person(string name, DateTime birthday, bool enabled)
        {
            Name = name;
            Birthday = birthday;
            Enabled = enabled;
        }

        public string Name { get; }
        public DateTime Birthday { get; }
    }
}

namespace AutoMapperImmutableModelTest
{
    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Domain.ModelBase, Repository.ModelBase>().ReverseMap();
            CreateMap<Domain.Person, Repository.Person>().ReverseMap();

            // this guy below is really not necessary //
            //CreateMap<Expression<Func<Domain.Person, bool>>, Expression<Func<Repository.Person, bool>>>().ReverseMap();
        }
    }

    public class Container
    {
        public Container()
        {
            var mapperConfiguration = new MapperConfiguration(
                configuration =>
                {
                    configuration.AddExpressionMapping();

                    configuration.AllowNullCollections = true;
                    configuration.AddProfile<MappingProfile>();
                });

            this.Mapper = mapperConfiguration.CreateMapper();
            this.Mapper.ConfigurationProvider.AssertConfigurationIsValid();
        }

        public IMapper Mapper { get; set; }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var mapper = new Container().Mapper;

            var domainModel = new Domain.Person("name", DateTime.Now.AddYears(-30), true);

            domainModel.SetId(Guid.NewGuid());
            domainModel.SetCreatedAt(DateTime.Now.AddDays(-30));
            domainModel.SetUpdatedAt(DateTime.Now.AddDays(-15));
            domainModel.SetDeletedAt(DateTime.Now.AddDays(-5));

            var repositoryModel = mapper.Map<Repository.Person>(domainModel);

            const string name = "My Name";

            Expression<Func<Domain.Person, bool>> domainWhere = person =>
                person.Name.Equals(name, StringComparison.OrdinalIgnoreCase);

            // the exception is thrown here //
            var repositoryWhere = mapper.MapExpression<Expression<Func<Repository.Person, bool>>>(domainWhere);

            // this way, if we use the framework [https://github.com/AgileObjects/ReadableExpressions] we can't read the debugview and the parameters properties //
            var repositoryWhereObject = mapper.Map(domainWhere, domainWhere.GetType(),
                typeof(Expression<Func<Repository.Person, bool>>));
        }
    }
}

In time: AutoMapper v9.0.0, AutoMapper.Extensions.ExpressionMapping v3.0.2, .NET Core 3.0.100

Am I missing something here? Thanks a lot!

System.InvalidOperationException: 'The source and destination types must be the same for expression mapping between literal types. Source Type: ObjectId, Source Description: Id, Destination Type: String, Destination Property: Id.'

When I map expression I have error:
System.InvalidOperationException: 'The source and destination types must be the same for expression mapping between literal types. Source Type: ObjectId, Source Description: Id, Destination Type: String, Destination Property: Id.'

Repository with error.
Thx.

Expression of type 'System.DateTime' cannot be used for constructor parameter of type 'System.DateTimeOffset'

I'm using Web API OData framework and using AutoMapper to map NHibernate domain objects to DTOs. When using the UseAsDataSource extension method, I get the following error:

System.ArgumentException
HResult=0x80070057
Message=Expression of type 'System.DateTime' cannot be used for constructor parameter of type 'System.DateTimeOffset'
Source=
StackTrace: at System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection1& arguments) at System.Linq.Expressions.Expression.New(ConstructorInfo constructor, IEnumerable1 arguments)
at AutoMapper.ConstructorMap.NewExpression(Expression instanceParameter)
at AutoMapper.QueryableExtensions.ExpressionBuilder.DestinationConstructorExpression(TypeMap typeMap, Expression instanceParameter)
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, TypeMap typeMap, LetPropertyMaps letPropertyMaps) at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps, TypeMap& typeMap)
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps) at AutoMapper.QueryableExtensions.Impl.MappedTypeExpressionBinder.BindMappedTypeExpression(IConfigurationProvider configuration, PropertyMap propertyMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps)
at AutoMapper.QueryableExtensions.Impl.MappedTypeExpressionBinder.Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps) at AutoMapper.QueryableExtensions.ExpressionBuilder.<>c__DisplayClass17_0.<CreateMemberBindings>g__CreateMemberBinding|0(PropertyMap propertyMap) at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMemberBindings(ExpressionRequest request, TypeMap typeMap, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps)
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, TypeMap typeMap, LetPropertyMaps letPropertyMaps) at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps, TypeMap& typeMap)
at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request, IDictionary2 typePairCount, LetPropertyMaps letPropertyMaps) at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request) at AutoMapper.LockingConcurrentDictionary2.<>c__DisplayClass2_1.<.ctor>b__1()
at System.Lazy1.CreateValue() at System.Lazy1.LazyInitValue()
at System.Lazy1.get_Value() at AutoMapper.QueryableExtensions.ExpressionBuilder.GetMapExpression(Type sourceType, Type destinationType, IDictionary2 parameters, MemberInfo[] membersToExpand)
at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](IDictionary2 parameters, IEnumerable1 memberPathsToExpand)
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider2.Execute[TResult](Expression expression) at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery2.GetEnumerator()
at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery`2.System.Collections.IEnumerable.GetEnumerator()
at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSet(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)

I have created explicit mappings for DateTime? to DateTimeOffset?
c.CreateMap<DateTime?, DateTimeOffset?>().ConvertUsing(x => (DateTimeOffset?)x);

This error only comes up when using the UseAsDataSource (and I would assume the ProjectTo methods as well). If I retrieve the full source entity list, the mapping works flawlessly.

No coercion operator is defined between types 'Entity' and 'Dto'

There are two types: Entity and Dto. They are identical mapped CreateMap<Entity, Dto>().ReverseMap();

Expression<Func<Dto, Dto>> selector = c => new Dto{}; var resEx = mapper.MapExpression<Expression<Func<Entity, Dto>>>(selector);

In AutoMapper.Extensions.ExpressionMapping v3.0.6 that works fine, but in latest v3.1.0 fails with error:
No coercion operator is defined between types 'Entity' and 'Dto'

OData Expand Issue with 1-N Relation DTO & Entity

I'm facing an issue with OData Expand. Have basic two entities, one is Parent and the other is Children entity. Relationship between Parent to Child is 1-N.

Also have same mapper for its corresponding dtos. .NetCore APi application has EFcore, automapper. All are of the latest version. Sample is available here at https://github.com/shoguns6/ODataIssue

The issue: APi works fine if it gets the Parent dto. But the moment i specify the $expand=children in the Api, it gives the ever famous 'Arguments do not match' error.

The expectation: Parent and its children to be retrieved and displayed to user/browser.

Have seen many post related to the same issue and they claim to have given the solution. But with the latest version of all (EFCore, Autommaper, .Netcore) the issue still exist.

Could you please let me what mistake am making here.

Unusual cast required after upgrading to version 4.0.0

Hi

After upgrading to version 4.0.0 (AutoMapper 10) we received the following error:

The binary operator Equal is not defined for the types 'System.DateTime' and 'System.Nullable`1DateTime

Example map:

Expression = x => (firstReleaseDate == null || x.StartDate >= firstReleaseDate) &&
                                      (lastReleaseDate == null || x.StartDate <= lastReleaseDate)

Where firstReleaseDate and lastReleaseDate are DateTime? and x.StartDate is DateTime

"Weird" cast map that solved the issue:

Expression = x => ((DateTime?)firstReleaseDate == null || (DateTime?)x.StartDate >= (DateTime?)firstReleaseDate) &&
                              ((DateTime?)lastReleaseDate == null || (DateTime?)x.StartDate <= (DateTime?)lastReleaseDate)

UseAsDataSource in ODataController with "$count=true" query produces "System.ArgumentException: must be reducible node"

Hi.

Thank you for the AutoMapper Expression Mapping Extension.

When calling UseAsDataSource in an ODataController and the client passes $count=true, a System.ArgumentException is thrown with the message must be reducible node.

I've created a reproducible example here:
TwoProjections.zip

Steps to reproduce:

  1. Open solution in Visual Studio 2017
  2. Restore NuGet packages for solution
  3. Build solution (Debug | Any CPU)
  4. Start both web applications in IIS Express (Set as startup project then Ctrl+F5)
  5. Run all unit tests
    The GetPersonBsTop10WithCount unit test should fail, but the others should pass.
    Note: You may have to close and re-open the solution, and rebuild, for the tests to get discovered.

.NET Framework 4.5.2
AutoMapper 7.0.1
AutoMapper.Extensions.ExpressionMapping 1.0.3

This example doesn't use Entity Framework.

Thank you.

IAsyncEnumerable support?

I just added UseAsDataSource() to some of my code and found that all the async code fails with the following exception:

Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.

For now, I am changing ToListAsync().. and CountAsync() to their non-async versions, but it seems like Async support should be added.

ArgumentException when mapping flattens a many-to-many collection

Here is a project with a test that fails, and some similar usages which pass.

My mapping contains a collection which maps a many-to-many relationship into a collection property on the DTO that looks like a one-to-many relationship, "flattening out" the LibraryBook join table model:

class Library
{
    . . .

    public List<LibraryBook> LibraryBooks { get; set; }
}

class LibraryBook
{
    . . .

    public Library Library { get; set; }

    public Book Book { get; set; }
}

class LibraryDto
{
    . . .

    public List<BookDto> Books { get; set; }
}

cfg.CreateMap<Library, LibraryDto>()
      .ForMember(
            dst => dst.Books,
            opt => opt.MapFrom(src => src.LibraryBooks.Select(lb => lb.Book)));

This mapping works until attempting a query expression which references the flattened collection:

db.Set<Library>()
    .UseAsDataSource(this.Mapper)
    .For<LibraryDto>()
    .Where(l => l.Books.Any())
    .ToList();

In this case an exception is thrown during expression mapping:

 System.ArgumentException : 
Expression of type 'System.Func`2[automapper_flattened_datasource.Book,automapper_flattened_datasource.Book]' cannot be used for 
parameter of type 'System.Func`2[automapper_flattened_datasource.LibraryBook,automapper_flattened_datasource.Book]' 
of method 'System.Collections.Generic.IEnumerable`1[automapper_flattened_datasource.Book] Select[LibraryBook,Book]
(System.Collections.Generic.IEnumerable`1[automapper_flattened_datasource.LibraryBook], System.Func`2[automapper_flattened_datasource.LibraryBook,automapper_flattened_datasource.Book])'
Parameter name: arg1
Stack Trace:
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitLambdaExpression[T](Expression`1 expression)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.GetConvertedMethodCall(MethodCallExpression node)
   at AutoMapper.Mappers.ExpressionMapper.MappingVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.ConvertDestinationExpressionToSourceExpression(Expression expression)
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceInjectedQueryProvider`2.Execute[TResult](Expression expression)
   at AutoMapper.Extensions.ExpressionMapping.Impl.SourceSourceInjectedQuery`2.GetEnumerator()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)

If I read the exception message correctly, it seems that it is not passing the expression I supply for MapFrom when dynamically building a new Select expression.

Using an .Any() call like this works with .UseAsDataSource() when using a mapping that maps from a simple collection on the domain object to a collection on the DTO. This also works when using .ProjectTo<LibraryDto>() instead of .UseAsDataSource(), but in that case EF cannot translate the resulting projection expression to SQL.

Argument exception: Source and destination must have same number of arguments

Hi,

I am upgrading from an old version of automapper (4.2.1) to latest. We had expression mapping in place between DTO and entities. I am now getting this exception when trying to map an expression from a dto that has extra collection properties that are not present on the entity.
This was working before upgrade.

Is it possible to handle this scenario like it was before. I looked over the source code and could not find any way to configure this.

Thanks in advance,
Mihai

Error while mapping expression - InvalidOperationException: No coercion operator is defined between types

AddExpressionMapping() not working for Expression with open generic mapping.

Source/destination types

    public class ReportS
    {
        public IList<MenuS<OptionS, Func<OptionS>>> Menu { get; set; }
    }

    public class ReportT
    {
        public IList<MenuT<OptionT, Func<OptionT>>> Menu { get; set; }
    }

    public class MenuT<T, TV>
    {
        public Expression<TV> LambdaExpression { get; set; }
        public T Option { get; set; }
    }

    public class MenuS<T, TV>
    {
        public Expression<TV> LambdaExpression { get; set; }
        public T Option { get; set; }
    }

    public class OptionS
    {
    }

    public class OptionT
    {
    }

Mapping configuration

 Mapper.Initialize(
                cfg =>
                    {
                        cfg.AddExpressionMapping();
                        cfg.CreateMap<OptionS, OptionT>();
                        cfg.CreateMap(typeof(MenuS<,>), typeof(MenuT<,>));
                        cfg.CreateMap<ReportS, ReportT>();
                    });

Version: x.y.z

Automapper 7.0.1
AutoMapper.Extensions.ExpressionMapping 1.0.3

Expected behavior

Expression should have mapped to target type

Actual behavior

InvalidOperationException: No coercion operator is defined between types 'OptionS' and 'OptionT'.

Steps to reproduce

  var source = new ReportS
                             {
                                 Menu = new List<MenuS<OptionS, Func<OptionS>>>
                                            {
                                                new MenuS<OptionS, Func<OptionS>>
                                                    {
                                                        Option = new OptionS(), LambdaExpression = () => new OptionS()
                                                    }
                                            }
                             };
            var target = Mapper.Map<ReportT>(source);

InvalidOperationException when mapping UnaryExpressions

        public enum SourceEnum
        {
            Foo,
            Bar
        }

        public enum DestEnum
        {
            Foo,
            Bar
        }

        public class SourceWithEnum : Source
        {
            public SourceEnum Enum { get; set; }
        }

        public class DestWithEnum : Dest
        {
            public DestEnum Enum { get; set; }
        }

        [Fact]
        public void Can_map_even_it_would_change_the_meaning_of_the_operation()
        {
            var config = new MapperConfiguration(cfg =>
            {
                cfg.AddExpressionMapping();
                cfg.CreateMap<SourceEnum, DestEnum>();
                cfg.CreateMap<DestWithEnum, SourceWithEnum>();
            });

            Expression<Func<SourceWithEnum, bool>> expr = s => s.Enum == SourceEnum.Bar;

            var mapped = config.CreateMapper().MapExpression<Expression<Func<SourceWithEnum, bool>>, Expression<Func<DestWithEnum, bool>>>(expr);

            var items = new[]
           {
                new DestWithEnum {Enum = DestEnum.Foo},
                new DestWithEnum {Enum = DestEnum.Bar},
                new DestWithEnum {Enum = DestEnum.Bar}
            };

            var items2 = items.AsQueryable().Select(mapped).ToList();
        }

The test throws an InvalidOperationException.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.