Skip to content

Expression.Call passed arguments with incorrect type when using MapExpression #158

@wbuck

Description

@wbuck

Synopsis

When attempting to convert an Expression generated from an OData query a System.InvalidOperationException is thrown:

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.

I've been able to reproduce the error below.

It happens when the Type (in this case an enum) argument for a collection needs to be mapped to a different Type.
For example: List<SimpleEnum> -> List<SimpleEnumModel>

Entities

public enum SimpleEnum
{
    Value1,
    Value2,
    Value3
}

public record Entity
{
    public int Id { get; init; }
    public SimpleEnum SimpleEnum { get; init; }
}

Models

public enum SimpleEnumModel
{
    Value1,
    Value2,
    Value3
}

public record EntityModel
{
    public int Id { get; init; }
    public SimpleEnumModel SimpleEnum { get; init; }
}

Mappings

public class EntityProfile : Profile
{
    public EntityProfile()
    {
        CreateMap<EntityModel, Entity>();                            
    }
}

Expression using List<T>.Contains

List<SimpleEnum> enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };

Expression<Func<Entity, bool>> filter = e => enums.Contains(e.SimpleEnum);

// Throws here.
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);

Expression using Enumerable.Contains<T> extension method

List<SimpleEnum> enums = new() { SimpleEnum.Value1, SimpleEnum.Value2 };

ParameterExpression parameter = Expression.Parameter(typeof(Entity));

MemberExpression memberExpression = Expression.MakeMemberAccess
(
    parameter,
    parameter.Type.GetProperty(nameof(Entity.SimpleEnum))
);

Type elementType = enums.GetType().GenericTypeArguments[0];
MethodCallExpression callExpression = Expression.Call
(
    typeof(Enumerable),
    nameof(Enumerable.Contains),
    new Type[] { elementType },
    Expression.Constant(enums),
    memberExpression
);

Expression<Func<Entity, bool>> filter = Expression.Lambda<Func<Entity, bool>>
(
    callExpression, 
    parameter
);

// Throws here.
Expression<Func<EntityModel, bool>> mappedFilter = mapper.MapExpression<Expression<Func<EntityModel, bool>>>(filter);

The exception is getting thrown here

Cause of exception when mapping List<T>.Contains

The reason the exception is thrown in this case is because the MethodCallExpression node arguments Method property has the wrong MethodInfo information. That MethodInfo is passed to Expression.Call.

node.Method == List<SimpleEnum>.Contains this causes a problem as the ConstantExpression.Type == SimpleEnumModel.


Cause of exception when mapping Enumerable.Contains<T>

The reason the exception is thrown in this case is because the listOfArgumentsForNewMethod contains an argument with an incorrect Type .

  1. ConstantExpression.Type == List<SimpleEnum>
  2. PropertyExpression.Type == SimpleEnumModel

In this case the first argument is incorrect and should be a constant Expression with the Type == List<SimpleEnumModel>.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions