Grouping and aggregating using linq - c#

Hi I have a List below that needs to be grouped and aggregated using Linq method syntax.
| id |Code|Descr | Number | Expiry |
|---------|----|------|--------|-----------|
| guidId1 | A | Desc1| Number1| 2017-03-18|
| guidId2 | A | Desc1| Number1| 2017-03-18|
| guidId3 | B | Desc2| Number1| 2017-03-18|
| guidId4 | B | Desc2| Number1| 2017-03-18|
| guidId5 | C | Desc3| Number1| 2017-03-18|
| guidId6 | A | Desc1| Number2| 2020-05-20|
| guidId7 | A | Desc1| Number2| 2020-05-20|
| guidId8 | A | Desc1| Number2| 2020-05-20|
| guidId9 | B | Desc2| Number2| 2020-05-20|
| guidId10| C | Desc3| Number2| 2020-05-20|
| guidId11| C | Desc3| Number2| 2020-05-20|
I have tried this but am not sure how to include the count:
myList.GroupBy(s => new { s.Number, s.Code, s.Expiry});
The output I want from the list:
{Code = "A",Descr = "Desc1",Number = "Number1",Expiry = "2017-03-18", Count = 2}
{Code = "B",Descr = "Desc2",Number = "Number1",Expiry = "2017-03-18", Count = 2}
{Code = "C",Descr = "Desc3",Number = "Number1",Expiry = "2017-03-18", Count = 1}
{Code = "A",Descr = "Desc1",Number = "Number2",Expiry = "2020-05-20", Count = 3}
{Code = "B",Descr = "Desc2",Number = "Number2",Expiry = "2020-05-20", Count = 1}
{Code = "C",Descr = "Desc3",Number = "Number2",Expiry = "2020-05-20", Count = 2}
Thanks in advance

You've grouped the Number, Code, and Expiry, but your result needs the Descr as well. You need to include that in your group, then get the count of the groups.
var query =
from s in myList
group 1 by new { s.Code, s.Descr, s.Number, s.Expiry } into g
select new { g.Key.Code, g.Key.Descr, g.Key.Number, g.Key.Expiry, Count = g.Count() };

Related

LINQ to Object - How to implement WHERE clause `If at least one of the elements is` for sub-group

I have a type of non-ordered records:
public class Row {
public string Client { get; set; }
public string Account { get; set; }
public string Status { get; set; }
}
and its instantiation is:
List<Row> accounts = new List<Row>() {
new Row() { Client = "123", Account = "123.def", Status = "Open" },
new Row() { Client = "456", Account = "456.def", Status = "Closed" },
new Row() { Client = "123", Account = "123.abc", Status = "Open" },
new Row() { Client = "789", Account = "789.abc", Status = "Open" },
new Row() { Client = "456", Account = "456.abc", Status = "Closed" },
new Row() { Client = "789", Account = "789.ghi", Status = "Open" },
new Row() { Client = "789", Account = "789.def", Status = "Closed" },
new Row() { Client = "789", Account = "789.jkl", Status = "Open" },
};
and output is:
+--------+---------+--------+
| Client | Account | Status |
+--------+---------+--------+
| 123 | 123.def | Open |
+--------+---------+--------+
| 456 | 456.def | Closed |
+--------+---------+--------+
| 123 | 123.abc | Open |
+--------+---------+--------+
| 789 | 789.abc | Open |
+--------+---------+--------+
| 456 | 456.abc | Closed |
+--------+---------+--------+
| 789 | 789.ghi | Open |
+--------+---------+--------+
| 789 | 789.def | Closed |
+--------+---------+--------+
| 789 | 789.jkl | Open |
+--------+---------+--------+
After that, for further manipulation of the object, I enter the following additional types:
public class Client {
public string Code { get; set; }
public List<Account> Accounts { get; set; }
}
public class Account {
public string Number { get; set; }
public string Status { get; set; }
}
and doing the select-projection of rows grouped by Client-field:
List<Client> clients = accounts
.GroupBy(x => x.Client)
.Select(y => new Client() {
Code = y.Key,
Accounts = y.GroupBy(z => z.Account)
.Select(z => new Account() {
Number = z.First().Account,
Status = z.First().Status
}).ToList()
}).ToList();
and I get the output:
+------+------------------+
| Code | Accounts |
| +---------+--------+
| | Number | Status |
+------+---------+--------+
| 123 | 123.def | Open |
| +---------+--------+
| | 123.abc | Open |
+------+---------+--------+
| 456 | 456.def | Closed |
| +---------+--------+
| | 456.abc | Closed |
+------+---------+--------+
| 789 | 789.abc | Open |
| +---------+--------+
| | 789.ghi | Open |
| +---------+--------+
| | 789.def | Closed |
| +---------+--------+
| | 789.jkl | Open |
+------+---------+--------+
But, my question is:
How can I implement WHERE-clause for filtered Accounts sub-group?
For example:
• How where-clause for logic: get clients, all of whose accounts is open for getting this?:
+------+------------------+
| Code | Accounts |
| +---------+--------+
| | Number | Status |
+------+---------+--------+
| 123 | 123.def | Open |
| +---------+--------+
| | 123.abc | Open |
+------+---------+--------+
• How where-clause for logic: get clients, who have at least one account is open? for getting this?:
+------+------------------+
| Code | Accounts |
| +---------+--------+
| | Number | Status |
+------+---------+--------+
| 123 | 123.def | Open |
| +---------+--------+
| | 123.abc | Open |
+------+---------+--------+
| 789 | 789.abc | Open |
| +---------+--------+
| | 789.ghi | Open |
| +---------+--------+
| | 789.def | Closed |
| +---------+--------+
| | 789.jkl | Open |
+------+---------+--------+
Full interactive code listing at dotnetfiddle
Query for: get clients, all of whose accounts is open
var ClientsWithAllAccountsAsOpen = clients
.Where(c => c.Accounts
.All(a => a.Status == "Open"));
// Add ToList() or equivalent if you need to materialise.
Query for: get clients, who have at least one account is open
var ClientsWithAtLeastOneOpenAccounts = clients
.Where(c => c.Accounts
.Any(a => a.Status == "Open"));
// Add ToList() or equivalent if you need to materialise.
get clients, all of whose accounts is open:
var openAccountsByClient = accounts
.Where(e => e.Status == "Open")
.GroupBy(x => x.Client)
.Select(y => new Client() {
...
}).ToList();
get clients, who have at least one account is open?:
var accountsByClientsWithOneAccount = accounts
.GroupBy(x => x.Client)
.Where(x => x.Any(e => e.Status == "Open"))
.Select(y => new Client() {
...
}).ToList();

foreach loop does not display the correct results

I am having issues with my nested foreach loop. I'm trying to populate data from by database to my list with information about car information (company, different car models). My issue has to do with my inner loop, and not being able to continue populating my list.
The results that I'm expecting is this:
"CompanyId": 1,
"CompanyName": "Toyota"
"ParentVehicleId": 2,
"ParentVehicleName": "Camry",
"ChildVehicleId": 4,
"ChildVehicleName":"Camry/Scepter"
"CompanyId": 1,
"CompanyName": "Toyota"
"ParentVehicleId": 4,
"ParentVehicleName": "Crown"
"ChildVehicleId": 0,
"ChildVehicleName":"N/A"
"CompanyId": 12,
"CompanyName": "Hyundai"
"ParentVehicleId": 13,
"ParentVehicleName": "Accent",
"ChildVehicleId": 0,
"ChildVehicleName":"N/A"
etc...
But what I'm actually getting is only these two:
"CompanyId": 1,
"CompanyName": "Toyota"
"ParentVehicleId": 2,
"ParentVehicleName": "Camry",
"ChildVehicleId": 3,
"ChildVehicleName":"Camry/Vista"
"CompanyId": 1,
"CompanyName": "Toyota"
"ParentVehicleId": 2,
"ParentVehicleName": "Camry",
"ChildVehicleId": 4,
"ChildVehicleName":"Camry/Scepter"
This is a snippet of my db table:
Vehicle Table
|----------------------------------------------|
| VehicleId | ManufactId | BrandName |
|----------------------------------------------|
| 1 | 1 | Toyota |
|----------------------------------------------|
| 2 | 1 | Camry |
|----------------------------------------------|
| 3 | 2 | Camry/Vista |
|----------------------------------------------|
| 4 | 2 | Camry/Scepter |
|----------------------------------------------|
| 5 | 4 | Crown |
|----------------------------------------------|
| 6 | 5 | Supra |
|----------------------------------------------|
C# code
public List<VehicleListModel>> VehicleMethod()
{
List<VehicleListModel> vehicleList = new List<VehicleListModel>();
foreach (var item in companyInfo)
{
var parentInfo = _context.VehicleTable.Where(y => item.VehicleId == y.ManufactId).ToList();
foreach (var item2 in parentInfo)
{
var childInfo = _context.VehicleTable.Where(y => item2.VehicleId == y.ManufactId).ToList();
foreach (var item3 in childInfo)
{
VehicleListModel vehList = new VehicleListModel();
//if ChildVehicleId does not exist, 0 & N/A are
//returned
vehList.CompanyId = item.VehicleId;
vehList.CompanyName = item?.BrandName ?? "N/A";
vehicleList.Add(vehList);
}
}
}
return vehicleList;
}
The problem is basically how your data is connected.
Let's take Toyota:
|----------------------------------------------|----------------|
| VehicleId | ManufactId | BrandId | BrandName |
|----------------------------------------------|----------------|
| 1 | null | 1 | Toyota |
|----------------------------------------------|----------------|
| 2 | 1 | 1 | Camry |
|----------------------------------------------|----------------|
| 3 | 2 | 1 | Camry/Vista |
|----------------------------------------------|----------------|
| 4 | 2 | 1 | Camry/Scepter |
As you can see, the model Camry is the relationship between the versions and the company.
When VehicleId is 2 (from Camry) you look for records where ManufactId is 2 (Vista and Scepter).
For Nissan instead:
|----------------------------------------------|----------------|
| VehicleId | ManufactId | BrandId | BrandName |
|----------------------------------------------|----------------|
| 9 | null | 9 | Nissan |
|----------------------------------------------|----------------|
| 10 | 9 | 9 | Datsun |
|----------------------------------------------|----------------|
| 11 | 9 | 9 | Datsun 13T |
Datsun doesn't have childs (no record have ManufactId equal to 10). Update Datsun 13 T record to ManufactId 10 to see it.
The same goes for the rest.
Moreover, because you hydrate the objects of the list inside the innermost foreach loop (and you never reach that code) you don't even get the empty objects.
If the data is wrong and you can't do anything about it, one possible way to handle these cases is to generate objects with the available info:
....
List<VehicleListModel> vehicleList = new List<VehicleListModel>();
var companies = _context.Where(x => x.ManufactId == null).ToList();
foreach (var company in companies)
{
var models = _context.Where(y => company.VehicleId == y.ManufactId).ToList();
if (models.Any())
{
foreach (var model in models)
{
var versions = _context.Where(y => model.VehicleId == y.ManufactId).ToList();
if (versions.Any())
{
foreach (var version in versions)
{
VehicleListModel vehList = new VehicleListModel();
vehList.CompanyId = company.VehicleId;
vehList.CompanyName = company?.BrandName ?? "N/A";
vehList.ParentVehicleId = model?.VehicleId ?? 0;
vehList.ParentVehicleName = model?.BrandName ?? "N/A";
vehList.ChildVehicleId = version?.VehicleId ?? 0;
vehList.ChildVehicleName = version?.BrandName ?? "N/A";
vehicleList.Add(vehList);
}
}
else
{
VehicleListModel vehList = new VehicleListModel();
vehList.CompanyId = company.VehicleId;
vehList.CompanyName = company.BrandName;
vehList.ParentVehicleId = model.VehicleId;
vehList.ParentVehicleName = model.BrandName;
vehList.ChildVehicleId = 0;
vehList.ChildVehicleName = "N/A";
vehicleList.Add(vehList);
}
}
}
else
{
VehicleListModel vehList = new VehicleListModel();
vehList.CompanyId = company.VehicleId;
vehList.CompanyName = company.BrandName;
vehList.ParentVehicleId = 0;
vehList.ParentVehicleName = "N/A";
vehList.ChildVehicleId = 0;
vehList.ChildVehicleName = "N/A";
vehicleList.Add(vehList);
}
}
....
Also, as #Yair suggested, you need to change Crown to ManufactId = 1
You are overriding the vehList instance in each iteration. Instead, you should move its initialization to the inner most loop, so a new instance is added to the list in each iteration:
foreach (var item in companyInfo)
{
var parentInfo = _context.VehicleTable.Where(y => item.VehicleId == y.ManufactId).ToList();
foreach (var item2 in parentInfo)
{
// This should be removed from the code:
// VehicleListModel vehList = new VehicleListModel();
var childInfo = _context.VehicleTable.Where(y => item2.VehicleId == y.ManufactId).ToList();
foreach (var item3 in childInfo)
{
// Instead, it's initialized here:
VehicleListModel vehList = new VehicleListModel();
//if ChildVehicleId does not exist, 0 & N/A are
//returned
vehList.CompanyId = item.VehicleId;
vehList.CompanyName = item?.BrandName ?? "N/A";
vehList.ParentVehicleId = item2?.VehicleId ?? 0;
vehList.ParentVehicleName = item2?.BrandName ?? "N/A";
vehList.ChildVehicleId = item3?.VehicleId ?? 0;
vehList.ChildVehicleName = item3?.BrandName ?? "N/A";
vehicleList.Add(vehList);
}
}
}

LINQ get columns in result by which query was grouped by

I have a problem with getting grouped columns in LINQ.
My class:
public class DTO_CAORAS
{
public int? iORAS_KEY_CON { get; set; }
public int? iMERC_KEY {get;set;}
public double? decD_ORAS_QUA {get;set;}
}
LINQ query:
var results =
from oras in listCAORAS_Delivered
group oras by new
{
oras.iORAS_KEY_CON,
oras.iMERC_KEY
}
into orasGroup
select new
{
decD_ORAS_QUA = orasGroup.Sum(x => x.decD_ORAS_QUA)
};
List results is filled only with one column - decD_ORAS_QUA. I don't know how to get columns, by which query is grouped - IORAS_KEY_CON and iMERC_KEY? I would like to fill results with iORAS_KEY_CON, iMERC_KEY and decD_ORAS_QUA.
Input data:
+---------------+-----------+---------------+
| iORAC_KEY_CON | iMERC_Key | decD_ORAS_QUA |
+---------------+-----------+---------------+
| 1 | 888 | 1 |
| 1 | 888 | 2 |
| 1 | 888 | 4 |
+---------------+-----------+---------------+
Desired output:
+---------------+-----------+---------------+
| iORAC_KEY_CON | iMERC_Key | decD_ORAS_QUA |
+---------------+-----------+---------------+
| 1 | 888 | 7 |
+---------------+-----------+---------------+
To also show the keys:
var results = from oras in listCAORAS_Delivered
group oras by new { oras.iORAS_KEY_CON, oras.iMERC_KEY } into g
select new DTO_CAORAS {
iORAS_KEY_CON = g.Key.iORAS_KEY_CON,
iMERC_KEY = g.Key.iMERC_KEY,
decD_ORAS_QUA = g.Sum(x => x.decD_ORAS_QUA)
};
As you are only grouping one column you can also:
var results = from oras in listCAORAS_Delivered
group oras.decD_ORAS_QUA by new { oras.iORAS_KEY_CON, oras.iMERC_KEY } into g
select new DTO_CAORAS {
iORAS_KEY_CON = g.Key.iORAS_KEY_CON,
iMERC_KEY = g.Key.iMERC_KEY,
decD_ORAS_QUA = g.Sum()
};

Join 2 datatable on difference columns

I have 2 DataTable. I want to use LINQ to join the 2 datatable on difference columns. How to do that?
Table A:
+--------+-------+-------+
| ACol1 | ACol2 | ACol3 |
+--------+-------+-------+
| 1 | tbA12 | tbA13 |
| 2 | tbA22 | tbA23 |
| 3 | tbA32 | tbA33 |
| 4 | tbA42 | tbA43 |
| 5 | tbA52 | tbA53 |
+--------+-------+-------+
Table B:
+-------+-------+-------+
| BCol1 | BCol2 | BCol3 |
+-------+-------+-------+
| 1 | XX | tbB13 |
| XX | 1 | tbB23 |
| XX | 2 | tbB33 |
| 4 | XX | tbB43 |
+-------+-------+-------+
SQL Query:
SELECT a.*, b.BCol3
FROM tableA a
JOIN tableB b ON a.ACol1=b.BCol1 OR a.ACol1=b.BCol2
Expected Result:
+--------+-------+-------+-------+
| ACol1 | ACol2 | ACol3 | BCol3 |
+--------+-------+-------+-------+
| 1 | tbA12 | tbA13 | tbB13 |
| 1 | tbA12 | tbA13 | tbB23 |
| 2 | tbA22 | tbA23 | tbB33 |
| 4 | tbA42 | tbA43 | tbB43 |
+--------+-------+-------+-------+
Currently my LINQ query are below:
var query1= from rowA in tableA.AsEnumerable()
join rowB in tableB.AsEnumerable()
on rowA["ACol1"].ToString() equals rowB["BCol1"].ToString()
select new
{
rowA["ACol1"],
rowA["ACol2"],
rowA["ACol3"],
rowB["BCol3"]
};
var query2= from rowA in tableA.AsEnumerable()
join rowB in tableB.AsEnumerable()
on rowA["ACol1"].ToString() equals rowB["BCol2"].ToString()
{
rowA["ACol1"],
rowA["ACol2"],
rowA["ACol3"],
rowB["BCol3"]
};
var result=query1.Union(query2);
Any better idea how to solve this?
LINQ support for JOIN with non-trivial conditions is very limited. You could do a cross join + move your condition to where clause.
var query1= from rowA in tableA.AsEnumerable()
from rowB in tableB.AsEnumerable()
where rowA["ACol1"].ToString() == rowB["BCol1"].ToString()
|| rowA["ACol1"].ToString() == rowB["BCol2"].ToString()
select new
{
rowA["ACol1"],
rowA["ACol2"],
rowA["ACol3"],
rowB["BCol3"]
};
Try this:-
var result = from a in tableA.AsEnumerable()
from b in tableB.AsEnumerable()
where a.Field<string>("ACol1") == b.Field<string>("BCol1")
|| a.Field<string>("ACol1") == b.Field<string>("BCol2")
select new
{
a["ACol1"],
a["ACol2"],
a["ACol3"],
b["BCol3"]
};
Here is the complete working Fiddle, you can copy paste the same in your editor and test because its not supporting AsEnumerable in DotNetFiddle.

Join with count and multiple conditions - LINQ C#

I have a property database and I am trying to get all properties added by an user. The main table is called 'Property' and there are other tables which are 'PropertyPhotos', 'City' etc. A sample database is as follows:
'Property' table
PropertyId| Area| State| UserId | ...
1 | 1 | 1 | AAA | ...
2 | 2 | 3 | BBB | ...
3 | 1 | 1 | AAA | ...
'PropertyPhotos'
PropertyPhotoId| PropertyId| FileName | MainPic
1 | 1 | x1.jpg | 1
2 | 1 | X2.jpg | 0
3 | 2 | x3.jpg | 1
4 | 3 | x4.jpg | 1
5 | 3 | x5.jpg | 0
6 | 3 | x6.jpg | 0
'AreaLookUp'
AreaLookUpId | AreaDescription
1 | London
2 | Birmingham
3 | Manchester
I am trying to write a LINQ query to get information on property added by a particular user. But I am stuck when trying to retrieve the 'FileName' of the MainPic and also get count. See code below with comments.
So, for the data above, this query should return the following for "UserId = AAA"
PropertyId | ... | MainPicSrc | PhotoCount
1 | ... | x1.jpg | 2
3 | ... | xr4jpg | 3
Please help!
public IEnumerable<PropertyExcerptViewModel> GetAddedPropertyVmByUserId(string userId)
{
var addedProperties = from p in db.Property where p.UserId == userId
join pp in db.PropertyPhotos on p.PropertyId equals pp.PropertyId
join a in db.AreaLookUp on p.Area equals a.AreaLookUpId
select new PropertyExcerptViewModel
{
PropertyId = p.PropertyId,
PropertyType = p.PropertyType,
TransactionType = p.TransactionType,
IsPropertyDisabled = p.IsPropertyDisabled,
IsPropertyVerified = p.IsPropertyVerified,
IsPropertyNotified = p.IsPropertyNotified,
MainPicSrc = pp.FileName, // How to put where condition to only get FileName of just the Main Pic
PhotoCount = pp.Count(), // How to get count of all pics with a particular proprtyId
Price = p.Price,
NoOfBedrooms = p.NoOfBedrooms,
Area = a.AreaLookUpDescription,
ShortDescription = (p.Description.Length > 300) ? p.Description.Substring(0,300) : p.Description
};
return addedProperties.ToList();
}
I think where statement might be easier if you care about clear match
var data=(from c in db.Property from v in db.PropertyPhotos from
n in db.AreaLookUpId
where c.PropertyId==v.PropertyId && c.Area==n.AreaLookUpId && c.UserId=="AAA"
// the rest is your select
PhotoCount = v.Where(j=>j. PropertyId==c.PropertyId).Count()
This also works - I ended up doing it this way
var addedProperties = from p in db.Property
join ppic in db.PropertyPhotos on p.PropertyId equals ppic.PropertyId into pp
join a in db.AreaLookUp on p.Area equals a.AreaLookUpId
join cal in db.CalendarEvent on p.PropertyId equals cal.PropertyId into c
where p.UserId == userId
select new PropertyExcerptViewModel
{
PropertyId = p.PropertyId,
PropertyType = p.PropertyType,
PropertyCategoryDescription = pc.PropertyCategoryDescription,
TransactionType = p.TransactionType,
IsPropertyDisabled = p.IsPropertyDisabled,
IsPropertyVerified = p.IsPropertyVerified,
IsPropertyNotified = p.IsPropertyNotified,
MainPicSrc = pp.Where(e => e.MainPic == true).FirstOrDefault().PhotoLocation,
PhotosCount = pp.Count(),
Price = p.Price,
NoOfBedrooms = p.NoOfBedrooms,
Area = a.AreaLookUpDescription,
ShortDescription = (p.Description.Length > 300) ? p.Description.Substring(0, 300) : p.Description,
LatestCalendarEvent = c.OrderByDescending(e => e.DateSaved).FirstOrDefault()
};
return addedProperties.ToList();

Categories