Home > OS >  Linq group by and sum using 3 entitites
Linq group by and sum using 3 entitites

Time:01-14

I Have some problem with proper translating by Runtime when using linq and group by.

I Have following entities:

Drivers, Settlements, Payments

Drivers has relation one to many to Settlements Payments has realtion one to many to Settlements

I decide as starting point to Group by some driver information from Settlement entity and sum payments with some constrains.

With code something like this linq cannot translate it

var settlementsGrouped = await _databaseContext.Settlements
    .ExcludeDeleted()
    .Where(s => s.DriverId == request.Id && s.DateStart >= request.DateStart && s.DateEnd <= request.DateEnd)
    .GroupBy(s => new {s.Driver.Id, s.Driver.FullName, s.Driver.Address, s.Driver.PostalCode, s.Driver.Town, s.Driver.IdNumber})
    .Select(s => new
    {
        s.Key.FullName,
        s.Key.Address,
        s.Key.PostalCode,
        s.Key.Town,
        s.Key.IdNumber,
        PaymentType = s.Sum(r => r.Payments.Where(p => p.IsActive && p.Type == PaymentEntityTypeEnum.Common).Sum(p => p.Amount)),
        AdvancePaymentType = s.Sum(r => r.Payments.Where(p => p.IsActive && p.Type == PaymentEntityTypeEnum.Advance).Sum(p => p.Amount)),
    }).ToListAsync(cancellationToken);
The LINQ expression 'GroupByShaperExpression:
KeySelector: new { 
    Id = d.Id, 
    FullName = d.FullName, 
    Address = d.Address, 
    PostalCode = d.PostalCode, 
    Town = d.Town, 
    IdNumber = d.IdNumber
 }, 
ElementSelector:EntityShaperExpression: 
    EntityType: Settlement
    ValueBufferExpression: 
        ProjectionBindingExpression: EmptyProjectionMember
    IsNullable: False

    .Sum(r => r.Payments
        .Where(p => p.IsActive && (int)p.Type == 1)
        .Sum(p => p.Amount))' 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.

Is it possible to get this information by one query?

I need result something like i write on .Select . I know that problem is with sum, but i cant find the way to solve this problem.

CodePudding user response:

Try the following query:

var query = 
    from s in _databaseContext.Settlements
        .ExcludeDeleted()
        .Where(s => s.DriverId == request.Id && s.DateStart >= request.DateStart && s.DateEnd <= request.DateEnd)
    from p in s.Payments
    where p.Isactive
    group p by new { s.Driver.Id, s.Driver.FullName, s.Driver.Address, s.Driver.PostalCode, s.Driver.Town, s.Driver.IdNumber } into g
    select new
    {
        g.Key.FullName,
        g.Key.Address,
        g.Key.PostalCode,
        g.Key.Town,
        g.Key.IdNumber,
        PaymentType = g.Sum(x => x.Type == PaymentEntityTypeEnum.Common ? x.Amount : 0),
        AdvancePaymentType = g.Sum(x => x.Type == PaymentEntityTypeEnum.Common ? x.Amount : 0),
    };

var settlementsGrouped = await query.ToListAsync(cancellationToken);

CodePudding user response:

Yes, your code above works, thank you very much :). Also i provide another way to achieve this solution and translate your to method style

            var driverInfo = await _databaseContext.Payments
            .Where(p => !p.Settlement.Driver.IsDeleted)
            .Where(p => p.Settlement.DateStart >= request.DateStart && p.Settlement.DateEnd <= request.DateEnd)
            .Where(p => p.IsActive)
            .Where(p => p.Settlement.DriverId == request.Id)
            .GroupBy(p => new
            {
                p.Settlement.Driver.Id, p.Settlement.Driver.FullName, p.Settlement.Driver.Address,
                p.Settlement.Driver.PostalCode, p.Settlement.Driver.Town, p.Settlement.Driver.IdNumber
            })
            .Select(p => new
            {
                p.Key.FullName,
                p.Key.Address,
                p.Key.PostalCode,
                p.Key.Town,
                p.Key.IdNumber,
                PaymentsSum = p.Where(r => r.Type == PaymentEntityTypeEnum.Common).Sum(r => r.Amount),
                AdvancePaymentsSum = p.Where(r => r.Type == PaymentEntityTypeEnum.Advance).Sum(r => r.Amount),
            }).FirstOrDefaultAsync(cancellationToken);


        var test2 = await _databaseContext.Settlements
            .ExcludeDeleted()
            .Where(s => s.DriverId == request.Id && s.DateStart >= request.DateStart &&
                        s.DateEnd <= request.DateEnd)
            .SelectMany(s => s.Payments, (settlement, payment) => new { settlement, payment })
            .Where(x => x.payment.IsActive)
            .GroupBy(x => new
            {
                x.settlement.Driver.Id,
                x.settlement.Driver.FullName,
                x.settlement.Driver.Address,
                x.settlement.Driver.PostalCode,
                x.settlement.Driver.Town,
                x.settlement.Driver.IdNumber
            }, x => x.payment)
            .Select(x => new
            {
                x.Key.FullName,
                x.Key.Address,
                x.Key.PostalCode,
                x.Key.Town,
                x.Key.IdNumber,
                PaymentType = x.Sum(r => r.Type == PaymentEntityTypeEnum.Common ? r.Amount : 0M),
                AdvancePaymentType = x.Sum(r => r.Type == PaymentEntityTypeEnum.Advance ? r.Amount : 0M),
            }).FirstOrDefaultAsync(cancellationToken);

Could you explain me why we need this selectMany function to achieve the result?
And also i understand that linq selectMany does not materialize query until FirstOrDefault is called?

  • Related