MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.31k stars 328 forks source link

ProjectToType with GroupBy - LINQ expression can not be translated #493

Closed Tantol closed 1 year ago

Tantol commented 1 year ago

Hi. I stumbled upon strange issue. Whenever I try to use GroupBy I get error:

The LINQ expression 'DbSet<SomeObjectFilter>()
    .Where(c1 => EF.Property<long?>(EntityShaperExpression: 
        SomeObject
        ValueBufferExpression: 
            ProjectionBindingExpression: EmptyProjectionMember
        IsNullable: False
    , "Id") != null && object.Equals(
        objA: (object)EF.Property<long?>(EntityShaperExpression: 
            SomeObject
            ValueBufferExpression: 
                ProjectionBindingExpression: EmptyProjectionMember
            IsNullable: False
        , "Id"), 
        objB: (object)EF.Property<long?>(c1, "SomeObjectId")))
    .Join(
        inner: DbSet<Filter>(), 
        outerKeySelector: c1 => EF.Property<long?>(c1, "FilterId"), 
        innerKeySelector: a => EF.Property<long?>(a, "Id"), 
        resultSelector: (o, i) => new TransparentIdentifier<SomeObjectFilter, Filter>(
            Outer = o, 
            Inner = i
        ))
    .LeftJoin(
        inner: DbSet<FilterGroup>(), 
        outerKeySelector: c1 => EF.Property<long?>(c1.Inner, "FilterGroupId"), 
        innerKeySelector: a0 => EF.Property<long?>(a0, "Id"), 
        resultSelector: (o, i) => new TransparentIdentifier<TransparentIdentifier<SomeObjectFilter, Filter>, FilterGroup>(
            Outer = o, 
            Inner = i
        ))
    .GroupBy(c1 => c1.Inner.Id)
    .OrderBy(g => g
        .AsQueryable()
        .Select(e => e.Outer.Outer.Filter.FilterGroup.Order)
        .First())' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Entities

class SomeObject {
    public long Id { get; set; }

    public virtual ICollection<SomeObjectFilter> SomeObjectFilters{ get; protected set; }
    (...)
}
class Filter {
    public long Id { get; set; }

    public long? FilterGroupId { get; set; }

    public virtual FilterGroup? FilterGroup { get; set; }
    (...)
}
class FilterGroup {
    public long Id { get; set; }
    public long Order { get; set; }

    public virtual ICollection<Filter> Filters { get; protected set; } = new List<Filter>();
    (...)
}

class SomeObjectFilter
{
    public long SomeObjectId { get; set; }
    public long FilterId { get; set; }

    public virtual SomeObject? SomeObject { get; set; }
    public virtual Filter? Filter { get; set; }
}

Dtos:

class SomeObjectDto {
    public long Id { get; set; }

    public IEnumerable<FilterGroupDto> FilterGroups { get; init; } = Enumerable.Empty<FilterGroupDto>();
    (...)
}

class FilterGroupDto {
    public long Id { get; set; }

    public IEnumerable<FilterDto> Filters { get; init; } = Enumerable.Empty<FilterDto>();
    (...)
}

Execute:

var someObjects = repository.query
     .Where(x => x.Id == 12);

var result = someObjects.ProjectToType<SomeObjectDto>().First();

SomeObject map configuration:

config
    .NewConfig<SomeObject, SomeObjectDto>()
    .IgnoreNullValues(true)
    .PreserveReference(true)
    .Map(
        d => d.FilterGroups,
        s => s.SomeObjectFilters.Select(x => x.Filter.FilterGroup)
            .GroupBy(x => x.Id)
            .OrderBy(x => x.First().Order)
            .Select(x => x.First())
    );

In order to that, belove execution with Adapt and Includes works:

var someObjects = repository.query
     .Where(x => x.Id == 12)
     .Include(x => x.SomeObjectFilters)
     .ThenInclude(x => x.Filter)
     .ThenInclude(x => x.FilterGroup);

var result = someObjects.Select(x => x.Adapt<SomeObjectDto>());

Mapster version 7.3.0 Microsoft.EntityFrameworkCore version 6.0.10

andrerav commented 1 year ago

The error message gives you all the info you need, I believe :) Materialize the query before the call to ProjectToType, like this:

var someObjects = repository.query
     .Where(x => x.Id == 12);

var result = someObjects.ToList().ProjectToType<SomeObjectDto>().First();