2

I have an gRPC application with EF core as ORM. AutoMapper's ProjectTo method is used to construct DTOs. But I get error when constructing collection properties. I prepared the following example:

Database entities:

public sealed class Horse
{
    public Guid Id { get; init; }
    public ICollection<Workout> Workouts { get; } = new HashSet<Workout>();
}
public sealed class Workout
{
    public Guid Id { get; init; }
}

Context:

public sealed class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
    public DbSet<Horse> Horse { get; private init; } = null!;
}

DTOs:

public sealed class HorseDto
{
    public Guid Id { get; init; }
    public RepeatedField<WorkoutDto> Workouts { get; init; } = new RepeatedField<WorkoutDto>();
}
public sealed class WorkoutDto
{
    public Guid Id { get; init; }
}

Mapping:

var mapperConfiguration =
                new MapperConfiguration(_ =>
                {
                    _.CreateMap<Horse, HorseDto>();
                    _.CreateMap<Workout, WorkoutDto>();
                });

When I do:

var horseDto = await context.Horse
                   .Where(_ => _.Workouts.Count > 0)
                   .ProjectTo<HorseDto>(mapperConfiguration)
                   .FirstAsync();

I get:

System.ArgumentException: Argument types do not match
   at System.Linq.Expressions.Expression.Bind(MemberInfo member, Expression expression)
   at AutoMapper.QueryableExtensions.Impl.EnumerableExpressionBinder.BindEnumerableExpression(IConfigurationProvider configuration, PropertyMap propertyMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary`2 typePairCount, LetPropertyMaps letPropertyMaps)
   at AutoMapper.QueryableExtensions.Impl.EnumerableExpressionBinder.Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary`2 typePairCount, LetPropertyMaps letPropertyMaps)
   at AutoMapper.QueryableExtensions.ExpressionBuilder.<>c__DisplayClass10_0.<CreateMapExpressionCore>g__CreateMemberBinding|4(PropertyExpression propertyExpression, <>c__DisplayClass10_1& )
   at AutoMapper.QueryableExtensions.ExpressionBuilder.<>c__DisplayClass10_0.<CreateMapExpressionCore>g__CreateMemberBindings|1()
   at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary`2 typePairCount, TypeMap typeMap, LetPropertyMaps letPropertyMaps)
   at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpressionCore(ExpressionRequest request, Expression instanceParameter, IDictionary`2 typePairCount, LetPropertyMaps letPropertyMaps, TypeMap& typeMap)
   at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request, IDictionary`2 typePairCount, LetPropertyMaps letPropertyMaps)
   at AutoMapper.QueryableExtensions.ExpressionBuilder.CreateMapExpression(ExpressionRequest request)
   at AutoMapper.Internal.LockingConcurrentDictionary`2.<>c__DisplayClass2_1.<.ctor>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.get_Value()
   at AutoMapper.Internal.LockingConcurrentDictionary`2.GetOrAdd(TKey key)
   at AutoMapper.QueryableExtensions.ExpressionBuilder.GetMapExpression(Type sourceType, Type destinationType, Object parameters, MemberInfo[] membersToExpand)
   at AutoMapper.QueryableExtensions.ProjectionExpression.ToCore(Type destinationType, Object parameters, IEnumerable`1 memberPathsToExpand)
   at AutoMapper.QueryableExtensions.ProjectionExpression.ToCore[TResult](Object parameters, IEnumerable`1 memberPathsToExpand)
   at AutoMapper.QueryableExtensions.ProjectionExpression.To[TResult](Object parameters, Expression`1[] membersToExpand)
   at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Object parameters, Expression`1[] membersToExpand)
   at AutoMapper.QueryableExtensions.Extensions.ProjectTo[TDestination](IQueryable source, IConfigurationProvider configuration, Expression`1[] membersToExpand)
   at RepeatedFieldExample.Sandbox.Run()

Everything works fine if I replace RepeatedField<> with IEnumerable<>.

Workable direct LINQ Select:

var horseDto = await ctx.Horse
                        .Where(h => h.Workouts.Count > 0)
                        .Select(h => new HorseDto()
                        {
                            Id = h.Id,
                            Workouts = new RepeatedField<WorkoutDto>()
                            {
                                h.Workouts.Select(w => new WorkoutDto() {Id = w.Id})
                            }
                        })
                        .FirstAsync();

AutoMapper version is 10.1.1

Maksim
  • 45
  • 6
  • Try it in an EF Select query, without AutoMapper. – Lucian Bargaoanu Jul 27 '21 at 11:21
  • @LucianBargaoanu yes, this works. But I am interested in solution with AutoMapper. – Maksim Jul 27 '21 at 12:10
  • What's the version of AutoMapper? – Akshay G Jul 27 '21 at 13:01
  • @AkshayGaonkar version is 10.1.1 – Maksim Jul 27 '21 at 13:20
  • Show us the Linq query that works. – Lucian Bargaoanu Jul 27 '21 at 13:23
  • Include the query in your post and delete from comments – Akshay G Jul 27 '21 at 13:59
  • 1
    And that really translates to SQL? :) EF6 certainly doesn't support that! Create a map from `ICollection Workouts` to `RepeatedField` with that expression. – Lucian Bargaoanu Jul 27 '21 at 14:00
  • @LucianBargaoanu looks like this :) I am new to AutoMapper, could you share more info about how to create the mapping with that expression? – Maksim Jul 27 '21 at 15:45
  • Looks like this configuration enables projection for RepeatedField<>: var mapperConfiguration = new MapperConfiguration(_ => { _.CreateMap(); _.CreateMap, RepeatedField>().ConvertUsing(_ => new RepeatedField() {_.Select(x => new WorkoutDto() {Id = x.Id})}); }); But I don't like this, because we need to specify conversion for every property. Is there a way to do this implicitly? – Maksim Jul 28 '21 at 10:53
  • Yes, but it's difficult. You have to write your own version of [this](https://github.com/AutoMapper/AutoMapper/blob/v10.1.1/src/AutoMapper/QueryableExtensions/Impl/EnumerableExpressionBinder.cs) and use it like [this](https://github.com/AutoMapper/AutoMapper/blob/f31e398a596438a939bf19d5bb97ed218124731d/src/UnitTests/Projection/BindersAndResultConverters.cs#L16). – Lucian Bargaoanu Jul 29 '21 at 14:03
  • @LucianBargaoanu I am hitting the same issue and I was thinking open generics would work but it doesn't. e.g. I have `CreateMap(typeof(ICollection<>), typeof(RepeatedField<>), typeof(RepeatedFieldTypeConverter<,>))`... where `RepeatedFieldTypeConverter` implements `ITypeConverter, RepeatedField>` – Jan Paolo Go Aug 17 '21 at 16:08
  • 1
    https://docs.automapper.org/en/latest/Queryable-Extensions.html#supported-mapping-options – Lucian Bargaoanu Aug 17 '21 at 17:02

0 Answers0