Linq Group By Into with additional parameters - c#

I have following sql result:
Table Result
Goal is to group this result by ProjectId and SequenceId. The later JSON Result should look like this:
[
{
"ProjectId": 1,
"ProjectName": "Testprojekt 1",
"Sequences": [
{
"SequenceId": 2,
"SequenceName": "ESN_Tauschen"
},
{
"SequenceId": 3,
"SequenceName": "Demontage"
}
]
},
{
"ProjectId": 2,
"ProjectName": "Testprojekt 2",
"Sequences": [
{
"SequenceId": 3,
"SequenceName": "Demontage"
}
]
}
]
My current linq expression gives me following result:
[
{
"ProjectId": 1,
"Sequences": [
2,
3
]
},
{
"ProjectId": 2,
"Sequences": [
3
]
}
]
var context = new ReworkPlace();
var result = from p in context.Projects
join rs in context.ReworkStations on p.ProjectId equals rs.ProjectId
join l in context.ReworkStationReworkConfigurationLinkSets on rs.ReworkStationId equals
l.ReworkStationId
join rc in context.ReworkConfigurations on l.ReworkConfigurationId equals rc.ReworkConfigurationId
join s in context.Sequences on rc.SequenceId equals s.SequenceId
group s.SequenceId by p.ProjectId into g
select new
{
ProjectId = g.Key,
Sequences = g.ToList()
};
return Json(result, JsonRequestBehavior.AllowGet);
I dont know how I have to adapt my linq expression to inlcude the not grouped properties like ProjectName, SequenceId and SequenceName into my json result.
Would be nice if somebody could help me.

To get the desired result without totally rewriting the query, replace the grouping part:
group s.SequenceId by p.ProjectId into g
select new
{
ProjectId = g.Key,
Sequences = g.ToList()
};
with something like this:
group new { p, s } by p.ProjectId into g
let p = g.FirstOrDefault().p
select new
{
ProjectId = g.Key,
ProjectName = p.Name,
Sequences =
(from e in g
group e.s by e.s.SequenceId into g2
let s = g2.FirstOrDefault()
select new
{
SequenceId = g2.Key,
SequenceName = s.Name
}).ToList()
};
The trick is to include between group and by the data that will be needed inside the grouping (in addition to the Key which is what you put after by).
To get additional fields, you either include them in the grouping key, or if they are one and the same for the defined grouping key, use the FirstOrDefault to get them from the first record in the grouping as in the above example.

Related

EF Core LINQ groups results of the query incorrectly

I have a entity like this:
public class Vehicle
{
public long Id { get; set; }
public string RegistrationNumber { get; set; }
public string Model { get; set; }
public string Code { get; set; }
//other properties
}
Which has a unique constraint on { RegistrationNumber, Model, Code, /*two other properties*/ }
I'm trying to query the database to get an object that's structured like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA",
"registrationNumbers": ["2", "3"]
},
{
"name":"BBB",
"registrationNumbers": ["3", "4"]
}
]
},
{
"name": "Model2",
"codes": [
{
"name": "BBB",
"registrationNumbers": ["4", "5"]
}
]
}
]
I.e. the list of Models, each models has a list of Codes that can co-appear with it, each code has a list of Registration Numbers that can appear with that Model and that Code.
I'm doing a LINQ like this:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m,
Codes = vehicles.Where(v => v.Model== m).Select(v => v.Code).Distinct().Select(c => new CodeFilterDTO()
{
Name = c,
RegistrationNumbers = vehicles.Where(v => v.Model == m && v.Code == c).Select(v => v.RegistrationNumber).Distinct()
})
}).ToListAsync();
Which gets translated into this SQL query:
SELECT [t].[Model], [t2].[Code], [t2].[RegistrationNumber], [t2].[Id]
FROM (
SELECT DISTINCT [v].[Model]
FROM [Vehicles] AS [v]
WHERE --additional filtering
) AS [t]
OUTER APPLY (
SELECT [t0].[Code], [t1].[RegistrationNumber], [t1].[Id]
FROM (
SELECT DISTINCT [v0].[Code]
FROM [Vehicles] AS [v0]
WHERE /* additional filtering */ AND ([v0].[Model] = [t].[Model])
) AS [t0]
LEFT JOIN (
SELECT DISTINCT [v1].[RegistrationNumber], [v1].[Id], [v1].[Code]
FROM [Vehicles] AS [v1]
WHERE /* additional filtering */ AND ([v1].[Model] = [t].[Model])
) AS [t1] ON [t0].[Code] = [t1].[Code]
) AS [t2]
ORDER BY [t2].[Id]
Running this query in the SQL Server gets me correct sets of values. But when I perform the LINQ, I get an object like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA",
"registrationNumbers": [/* every single registration number that is present among the records that passed the filters*/]
}
]
}
]
What is the problem may be, and how to fix it?
Edit: After playing with it for a bit, I'm even more confused than I was
This LINQ:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m
}).ToListAsync();
Gives the expected result:
[
{
"name": "Model1"
},
{
"name": "Model2"
},
...
]
Hovewer this LINQ:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m,
Codes = vehicles.Select(v=>v.Code).Distinct().Select(c => new CodeFilterDTO()
{
Name = c
})
}).ToListAsync();
Gives result like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA"
}
]
}
]
Open for yourself GroupBy operator. Using double grouping you can achieve desired result.
var rawData = await _context.Vehicles
.Where(/*some additional filters*/)
.Select(v => new
{
v.Model,
v.RegistrationNumber,
v.Code
})
.ToListAsync(); // materialize minimum data
// perform grouping on the client side
var result = rawData
.GroupBy(v => v.Model)
.Select(gm => new ModelFilterDTO
{
Name = gm.Key,
Codes = gm
.GroupBy(x => x.Code)
.Select(gc => new CodeFilterDTO
{
Name = gc.Key,
RegistrationNumbers = gc.Select(x => x.RegistrationNumber).ToList()
}).ToList()
})
.ToList();

Newtonsoft JArray LINQ - Group By array with identical values

I'm new to LINQ and having a problem I can't seem to solve. I have a JSON array/object like this:
[
{
"items": [
"pepperoni"
]
},
{
"items": [
"sausage"
]
},
{
"items": [
"sausage"
]
},
{
"items": [
"pepperoni",
"mushrooms",
"olives"
]
},
{
"items": [
"peppers",
"spinach"
]
},
{
"items": [
"peppers",
"spinach"
]
},
{
"items": [
"peppers",
"spinach"
]
}
]
I need to GROUP BY the items combinations and produce results like this:
peppers,spinach - 3
sausage - 2
pepperoni - 1
pepperoni,mushrooms,olives - 1
This is the Linq query I have (clearly doesn't work).
JArray jsonData= JsonConvert.DeserializeObject<JArray>(jsonString);
var queryResult =
from c in jsonData.Select(i => i["items"]).Values<string>()
group c by c
into g
orderby g.Count() descending
select new { Items = g.Key, Count = g.Count() };
I find examples for every scenario except this one.
You need to merge list items into string then Group items:
var queryResult = from c in jsonData.Select(i => String.Join(",", i["items"]).OrderBy(o => o))
group c by c
into g
orderby g.Count() descending
select new { Items = g.Key, Count = g.Count() };
It will give you desired output:
peppers,spinach-3
sausage-2
pepperoni-1
pepperoni,mushrooms,olives-1
There's another solution with the use of SequenceEqual and IEqualityComparer.
This solution is longer but more complete, since it uses proper custom equality comparer.
First we need a class implementing IEqualityComparer
public class ItemEqualityComparer : IEqualityComparer<IEnumerable<string>>
{
public bool Equals(IEnumerable<string> x, IEnumerable<string> y)
{
if (x == null && y == null)
return true;
else if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(IEnumerable<string> obj)
{
return obj.Select(o => o.GetHashCode()).Sum();
}
}
then we can use it to group the items correctly:
var itemsGroups = jsonData.Select(i => i["items"].Values<string>()).GroupBy(l => l, l => l, (key, values) => new
{
Key = key,
Count = values.Count()
}, new ItemEqualityComparer());
This solution keeps the items inside a list so that you don't have to resplit them from the joint string.
The drawback is that is slower becausxe it has to enumerate all the items of all the lists to check that there're equals.
I hope I was helpful.

Join Two Different Object DataType using Linq in C#

I am having two lists:
ListA:
[
{
Id = 1,
Name = "A",
Summary = ""
},
{
Id = 2,
Name = "B",
Summary = ""
}
]
ListB:
[
{
Id = 1,
Value = "SomeThing"
},
{
Id = 2,
Value = "EveryThing"
}
]
I want to join that two list using LINQ and want to return ListA which value is update as Below
[
{
Id = 1,
Name = "A",
Summary = "SomeThing"
},
{
Id = 2,
Name = "B",
Summary = "EveryThing"
}
]
I am joining ListA and ListB based on Id and assigning value to summary.
I tried below approach:
var query = from obj1 in ListA
join obj2 in ListB on obj1.Id equals obj2.Id
select obj1.Summary = obj2.Value, return obj1;
**=>so here i want assign data from obj2 to obj1 then want to return obj1 **
is that possible or how we can do this?
You could also update the existing ListA with a simple loop
foreach (var itemA in ListA)
{
itemA.Summary = ListB.FirstOrDefault(x => x.Id == itemA.Id)?.Value;
}
Join approach
var query = ListA.Join(ListB,
ia => ia.Id,
ib => ib.Id,
(ia, ib) => new aItem() //type of ListA here
{
Id = ia.Id,
Name = ia.Name,
Summary = ib.Value
});
You could try to join the two lists like this:
var listA = new List<ClassA>();
var listB = new List<ClassB>();
var list = listA.Join(listB, a => a.Id, b => b.Id, (a, b) =>
new ClassA
{
Id = a.Id,
Name = a.Name,
Summary = b.Value
});
Using method syntax Enumerable.Join is the easier one to use here:
var result = listA.Join(listB, // Join ListA and ListB
a => a.Id, // from every a in ListA take a.Id
b => b.Id, // from every b in ListB take b.Id
(a, b) => new // when they match, take the a and the b
{ // to create a new object with properties
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});
Note that the result is of an anonymous type, If you want a result with the same type as the items in ListA (let's say they are of class A), change the last part of the join:
(a, b) => new A() // when they match, take the a and the b
{ // to create a new object of class A
Id = a.Id,
Name = a.Name,
Summary = b.Value,
});

Linq query returns duplicate results

The following query returns duplicate results, in the second select query.
Country has 0..1 to * relationship with leagues.
Leagues have 1 to * relationship with userLeagues.
return from ul in userLeagues
select new Map.Country
{
id = ul.Country.CountryId,
name = ul.Country.Common_Name,
leagues = userLeagues.Where(x => x.CountryId.Value == ul.CountryId.Value)
.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};
I tried using Distinct with no luck.
It seems that either i have to use distinct or groupby countryId
The output is such as
[
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
},
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
}
]
You need to group it by CountryId and Common_Name to get expected results:
var result = from ul in userLeagues
group ul by new { ul.Country.CountryId, ul.Country.Common_Name } into g
select new Map.Country
{
id = g.Key.CountryId,
name = g.Key.Common_Name,
leagues = g.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};
Think about what you're doing: For each league in userLeagues, you're creating a Map.Country for the country that league belongs to. If three leagues are in France, that's three Frances. France is a wonderful country, but let's not go overboard.
Instead, you want to start with a distinct list of countries. For each one, create one Map.Country, and give that Map.Country a list of the leagues that should belong to it.
First, let's make Country implement IEquatable<Country> for Distinct purposes:
public class Country : IEquatable<Country>
{
public bool Equals(Country other)
{
return other.CountryID == CountryID;
}
Second, you want to start with a distinct list of countries, and then populate them with leagues.
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct()
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => x.Country == ctry)
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
I didn't recreate your Map.League and Map.Country classes, I just used anonymous objects, and I left it that way because this code definitely works just as it is. But filling in your class names is trivial.
If it's not practical to make Country implement IEquatable<T>, just write a quick equality comparer and use that:
public class CountryComparer : IEqualityComparer<Country>
{
public bool Equals(Country x, Country y)
{
return x.CountryID == y.CountryID;
}
public int GetHashCode(Country obj)
{
return obj.CountryID.GetHashCode();
}
}
...like so:
var cc = new CountryComparer();
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct(cc)
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => cc.Equals(x.Country, ctry))
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
This is logically equivalent to a GroupBy, which is probably a more respectable way to do it. But somebody else thought of that before I did, so he earned the glory.
I would say the you need to reverse your query. So instead of starting with userLeagues, start with country and include the child leagues.

LINQ group by query help: Select another related table with key

Please help me complete the following query:
var results = from species in db.Species
join cats in db.Categories on species.CategoryId equals cats.CategoryId
join groups in db.Groups on cats.GroupId equals groups.GroupId
group groups by groups into g
select new GroupWithCategoryChildren
{
Group = g.Key,
Categories = (cats from above query for this same g.Key group)???
};
I could do this really easily with SQL, but I'm struggling with LINQ. I want to end up with a List of each Group ALONG WITH the cats that joined to it in the above/main single query, ideally without re-querying for that data again in a separate query.
Thanks a lot
I'm not sure I understood correctly but I think this is all you need: Instead of group groups by groups do group cats by groups and then the Key will be the groups and the values of the group will be all the matching categories.
var results = from species in db.Species
join cats in db.Categories on species.CategoryId equals cats.CategoryId
join groups in db.Groups on cats.GroupId equals groups.GroupId
group cats by groups into g
select new GroupWithCategoryChildren
{
Group = g.Key,
Categories = g.ToList()
};
I used this to test it:
List<dynamic> Species = new List<dynamic>()
{
new { A = "1", CategoryId = 1 },
new { A = "2", CategoryId = 2 },
};
List<dynamic> Categories = new List<dynamic>
{
new { B = "1", CategoryId = 1, GroupId = 1 },
new { B = "2", CategoryId = 2, GroupId = 1 },
};
List<dynamic> Groups = new List<dynamic>
{
new { C = "1", GroupId = 1 }
};
//result: { Group: { C = "1", GroupId = 1 }, Categories (1,2)}

Categories

Resources