Nested GroupBy using Linq - c#

I'm trying to perform a nested GroupBy using Linq and I'm not able to get it to work. My code is the following:
var summaryFile = new RemittanceCenterFilesSummaryListModel
{
RemittanceFilesSummary = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord.GroupBy(x => new { x.FileId, x.SourceFileName })
.Select(x => new RemitanceCenterFileSummarizedModel
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
Batches = x.ToList().GroupBy(b => new { b => b.BatchCode })
.Select(c => new RemittanceCenterBatchSummarizedModel
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
BatchCode = c.Key,
BatchType = c.Key,
DetailRecordCountAdc = x.Count(y => y.BillingSystemCode == BillingSystemCode.Adc),
DetailRecordCountNotAdc = x.Count(y => y.BillingSystemCode == BillingSystemCode.Exceed),
AmountAdc = x.Where(y => y.BillingSystemCode == BillingSystemCode.Adc).Sum(y => y.PaymentAmount),
AmountNotAdc = x.Where(y => y.BillingSystemCode == BillingSystemCode.Exceed).Sum(y => y.PaymentAmount),
AmountTotal = x.Sum(y => y.PaymentAmount),
});
ScannedBatchCount = x.Count(y => y.BatchType == "S"),
ScannedBatchAmount = x.Where(y => y.BatchType == "S").Sum(y => y.PaymentAmount),
NonScannedBatchCount = x.Count(y => y.BatchType != "S"),
NonScannedBatchAmount = x.Where(y => y.BatchType != "S").Sum(y => y.PaymentAmount),
}).ToList()
};
The first GroupBy is working correctly, however when I try to GroupBy the Batches field I'm getting the following build error:
Error 76 Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.
The error is highlighted here:
Batches = x.ToList().GroupBy(b => new { b => b.BatchCode })
Any suggestions ?

You mean
Batches = x.ToList().GroupBy(b => b.BatchCode)
There is no need to create an anonymous type if you want to group by only one property.
If you need an anonymous type, the syntax would be
Batches = x.ToList().GroupBy(b => new { b.BatchCode })
you used another lambda operator (b => new {b => b.BatchCode}) inside the anonymous type, which was invalid.

Related

Linq Group by with nested collection throwing Cannot perform an aggregate function

I want to do something like this:
var projectHistory = await Context.Tasks.GroupBy(x => x.ProjectId).Select(x => new ProjectHistoryStatModel
{
ProjectId = x.Key,
CompletedTasks = x.Where(y => y.StatusId == 4).Count(),
InProgressTasks = x.Where(y => y.StatusId == 3).Count(),
DelayedTasks = x.Where(y => y.EndDate < DateTime.Now && y.StatusId != 4).Count(),
DependentTasks = x.Where(y => y.Dependents.Any()).Count(),
TotalTasks = x.Count()
}).ToListAsync();
But DependentTasks property DependentTasks = x.Where(y => y.Dependents.Any()).Count(), is throwing:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Well y.Dependents is a Collection that is why its throwing the problem, I also tried this: DependetTasks = Context.TaskDependencies.Where(y => x.Any(z => z.Id == y.TaskId)).Count(), and it throws the same error.
Can you guys show me a way of doing this in the same request to the DB?
Regards
Assuming that you are using EF Core < 6.0, you can try to rewrite your query in the following way:
var query =
from t in Context.Tasks
group new { t, HasDependents = t.Dependents.Any() } by t.ProjectId into g
select new ProjectHistoryStatModel
{
ProjectId = g.Key,
CompletedTasks = g.Where(y => y.t.StatusId == 4).Count(),
InProgressTasks = g.Where(y => y.t.StatusId == 3).Count(),
DelayedTasks = g.Where(y => y.t.EndDate < DateTime.Now && y.t.StatusId != 4).Count(),
DependentTasks = g.Where(y => y.HasDependents).Count(),
TotalTasks = g.Count()
};
var projectHistory = await query.ToListAsync();
EF Core up to 6.0 do not support translating navigation properties after GroupBy.

LINQ error: System.NotSupportedException on GroupBy Selection

When var items = q3.ToList(); executes from the code snippet below, it throws exception System.NotSupportedException. The aim is to get the list of items after the grouping.
Exception:
Unable to create a constant value of type 'AppDB.Stage.Rules'. Only primitive types or enumeration types are supported in this context.
var valuations = context.stage
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList(); //error here
Your database doesn't have any idea of what your in-memory rules actually is, and in-turn cant convert this statement to SQL
The simplest solution will be to leave it as an IQueryable and don't use ToList,
context.stage
.Where(q => q.stageID == stageID && !rules.Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
However, if it is already in memory, then you will have to send the values as a primitive list
var type1s = rules.Select(x => x.type1);
var type2s = rules.Select(x => x.type2);
context.stage
.Where(q => q.stageID == stageID && !type1s.Contains(q.type1) && !type2s.Contains(q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
Because rules.ToList() makes results in memory, you can't use it inside an IQueryable that executes over SQL. You should first bring your data into memory and then narrow it by other in-memory object.
var valuations = context.stage.ToList()
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList();

Select multiple child columns with the same filter

I have this Linq lambda expression which generates abnormally complex SQL select to database. Is it somehow possibility to simplify it?
var devices = db.Devices
.Where(a => a.active == true)
.Select(a => new DeviceToDisplay
{
Id = a.Id,
serialNumber = a.serialNumber,
deviceRegion = a.deviceRegion,
activeIP = a.IPaddresses.Where(b => b.active == true).Select(b => b.IPaddress1).FirstOrDefault(),
Wip = a.IPaddresses.Where(b => b.active == true).Select(b => b.W_IP).FirstOrDefault(),
Sip = a.IPaddresses.Where(b => b.active == true).Select(b => b.S_IP).FirstOrDefault(),
model = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).Select(c => c.model).FirstOrDefault(),
firmware = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).Select(c => c.firmware).FirstOrDefault(),
lastMPteamActivity = a.activityLogs.OrderByDescending(c => c.updatedDate).Select(c => c.updatedDate).FirstOrDefault(),
country = a.MPPinformations.Select(c => c.country).FirstOrDefault()
});
For a start, your linq query looks very complicated. Imagine how you would implement this by writing a SQL query for example.
A suggestion: you are writing things like:
a.IPaddresses.Where(b => b.active == true).
and
a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).
in multiple places.
Instead you could create an anonymous type. For example,
var foo = from x in sb.Devices.Where(a=> a.active)
select new { Id = x.ID,
IPAddress = a.IPaddresses.Where(b => b.active), ... }
You can then use foo to create your Devices object.
See if this is any better:
var devices = db.Devices
.Where(a => a.active == true)
.Select(a => new DeviceToDisplay {
Id = a.Id,
serialNumber = a.serialNumber,
deviceRegion = a.deviceRegion,
activeIP = a.IPaddresses.Where(b => b.active == true).FirstOrDefault(),
SPdata = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).FirstOrDefault(),
lastMPteamActivity = a.activityLogs.OrderByDescending(c => c.updatedDate).Select(c => c.updatedDate).FirstOrDefault(),
country = a.MPPinformations.Select(c => c.country).FirstOrDefault()
})
.Select(a=> new DeviceToDisplay {
Id=a.Id,
serialNumber=a.serialNumber,
deviceRegion=a.deviceRegion,
activeIP=a.activeIP.IPaddress1,
Wip=a.activeIP.W_IP,
Sip=a.activeIP.S_IP,
model=a.SPdata.model,
firmware=a.SPdata.firmware,
lastMPteamActivity=a.lastMPteamActivity,
country=a.county
});

The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities

var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId,
dayState = r.o.day_desc.Split('[', ']')[1]
}).ToList();
I get an exception :
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to
Entities
How i could find an alternative to Split('[', ']')[1] in this query
You must commit the query and do the split after loading the data:
var residenceRep =
ctx.ShiftEmployees
.Include(s => s.UserData.NAME)
.Include(s => s.ResidenceShift.shiftName)
.Join(ctx.calc,
sh => new { sh.empNum, sh.dayDate },
o => new { empNum = o.emp_num, dayDate = o.trans_date },
(sh, o) => new { sh, o })
.Where(s => s.sh.recordId == recordId && s.o.day_flag.Contains("R1"))
.OrderBy(r => r.sh.dayDate)
.Select(r => new
{
dayDate = r.sh.dayDate,
empNum = r.sh.empNum,
empName = r.sh.UserData.NAME,
shiftId = r.sh.shiftId,
shiftName = r.sh.ResidenceShift.shiftName,
recordId = r.sh.recordId,
dayState = r.o.day_desc,
})
.ToList()//Here we commit the query and load data
.Select(x=> {
var parts = x.dayState.Split('[', ']');
return new {
x.dayDate,
x.empNum,
x.empName,
x.shiftId,
x.shiftName,
x.recordId,
dayState = parts.Length > 1 ?parts[1]:"",
};
})
.ToList();
I had this Issue and the approach that I've chose was that get all element I wanted and save them into a List and then filter the actual data on that list.
I know this is not the best answer but it worked for me.

C# - LINQ to Entity query attaching the value of the properties to one variable

I have this query:
var sole = SoleService.All().Where(c => c.Status != 250)
.Select(x => new {
ID = x.ID, Code = new {CodeName = x.Code + x.Country},
Name = x.Name
})
.Distinct()
.OrderBy(s => s.Code);
Like this it gives me an error. What I want is to combine Code and Country like concat strings so I can use the new variable for my datasource. Something like - 001France
P.S
What I am using and is working right now is this :
var sole = SoleService.All().Where(c => c.Status != 250)
.Select(x => new {
ID = x.ID, Code = x.Code, Name = x.Name, Country = x.Country })
.Distinct()
.OrderBy(s => s.Code);
So what I need is to modify this query so I can use Code +Country as one variable. Above is just my try that I thought would work.
Sound like:
var sole = SoleService.All().Where(c => c.Status != 250)
.AsEnumerable()
.Select(x => new {
ID = x.ID,
Code = x.Code + x.Country,
Name = x.Name
})
.Distinct()
.OrderBy(s => s.Code);
You don't need inner anonymous type at all. If you are working on EF, sine string + is not supported, call AsEnumerable before doing select.
You can't sort by s.Code because it's an instance of an anonymous type. I'd go with
var sole = SoleService.All().Where(c => c.Status != 250)
.Select(x => new { ID = x.ID, Code = new {CodeName = x.Code + x.Country}, Name = x.Name })
.Distinct()
.OrderBy(s => s.Code.CodeName);
Try to add .toString() because the problem should be the + operator believe you try to add numeric properties...
Like that :
var sole = SoleService.All().Where(c => c.Status != 250)
.Select(x => new { ID = x.ID, Code = new {CodeName = x.Code.ToString() + x.Country.ToString()}, Name = x.Name })
.Distinct()
.OrderBy(s => s.Code);

Categories