Linq Grouping the result of group by query - c#

I have the following exemple :
Model mod1 = new Model { Header = "A", canDelete = true,pax=1 };
Model mod2 = new Model { Header = "B", canDelete = true,pax=1 };
Model mod3 = new Model { Header = "A", canDelete = true,pax=2 };
Model mod4 = new Model { Header = "B", canDelete = false,pax=2 };
Model mod5 = new Model { Header = "A", canDelete = true,pax=3 };
Model mod6 = new Model { Header = "B", canDelete = false,pax=3 };
Model mod7 = new Model { Header = "A", canDelete = false,pax=4 };
Model mod8 = new Model { Header = "B", canDelete = true,pax=4 };
I added these models to a listMod
I want to group first by pax number, so I used :
var resultQuery = listMod.GroupBy(p=>p.pax);
How can I re-group the result of my resultQuery by Header and canDelete ?
The aim is to have 3 groups :
1st group : mod1 and mod2
2nd group : mod3 , mod4 , mod5 and mod6
3rd group : mod7 and mod8

Well, there may be a better way, but this one should work, assuming you have always two items for each pax number.
The "trick" is to concatenate the first and second canDelete / Header pair of items grouped by pax, and to group on that value.
Than the list inside the groups are flattened (using SelectMany)
listMod.GroupBy(m => m.pax)
.Select(m => new
{
valuePair = string.Format("{0}-{1}/{2}-{3}", m.First().canDelete, m.First().Header, m.Last().canDelete, m.Last().Header),
value = m.Select(x => x)
})
.GroupBy(m => m.valuePair)
.Select(g => g.SelectMany(x => x.value))
//.ToList();
if you wanna avoid this bad concatenation, you can also do
var result = listMod
.GroupBy(m => m.pax)
.Select(m => new
{
a1 = m.First().canDelete,
a2 = m.First().Header,
b1 = m.Last().canDelete,
b2 = m.Last().Header,
value = m.Select(x => x)
})
.GroupBy(m => new {m.a1, m.a2, m.b1, m.b2})
.Select(g => g.SelectMany(x => x.value))
//.ToList();

Related

Child list in join statement - LINQ

I have 3 collections
List<Brand> brands= new List<Brand>
{
new Brand { ID = 5, Name = "Toyota"},
new Brand { ID = 6, Name = "Mazda"},
new Brand { ID = 7, Name = "Seat"}
};
ModelParam par1 = new ModelParam() { Id = 1, fuelType = "Petrol", enginePower =110, engineTorque = 130 };
ModelParam par2 = new ModelParam() { Id = 2, fuelType = "Petrol", enginePower = 170, engineTorque = 290 };
ModelParam par3 = new ModelParam() { Id = 3, fuelType = "Diesel", enginePower = 140, engineTorque = 280 };
ModelParam par4 = new ModelParam() { Id = 4, fuelType = "Diesel", enginePower = 190, engineTorque = 320 };
List<Model> models = new List<Model>
{
new Model { Id = 1, Name = "CX5", brandId = 6, parameters = new List<ModelParam> {par1, par3} },
new Model { Id = 2, Name = "Corolla", brandId = 5, parameters = new List<ModelParam> {par2, par3} },
new Model { Id = 3, Name = "Leon", brandId = 7, parameters = new List<ModelParam> {par2, par4} }
};
As a result I want to have a query with combination of:
Brand - Model - FuelType - EnginePower.
I wrote this to join brands and model lists:
var result = models.Join(brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => new { b.Name, mod.Name })
.ToList();
The problem is I can't extract fuelType and enginePower from ModelParams from params property in models List. Any tips?
Can this approach work for you?
var result = models.Join(
brands,
mod => mod.brandId,
b => b.ID,
(mod, b) => mod.parameters.Select(parameter => new {
Brand = b.Name,
Model = mod.Name,
FuelType = parameter.fuelType,
EnginePower = parameter.enginePower
}))
.SelectMany(pr => pr)
.ToList();
You should update your result selector Func a little bit and get the required values fuelType and enginePower from parameters collection in Model class. The following snippet will select only the first fuelType from parameters and max enginePower.
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = mod.parameters.Select(p => p.fuelType).FirstOrDefault(),
EnginePower = mod.parameters.Max(p => p.enginePower)
})
.ToList();
However, there a situation when model can have multiple values for fuelType and enginePower.
If you need to choose all combinations, just look through the parameters collection and then flatten a result using SelectMany
var result = models
.Join(brands, mod => mod.brandId, b => b.ID,
(mod, b) => mod.parameters.Select(p => new
{
Brand = b.Name,
Model = mod.Name,
FuelType = p.fuelType,
EnginePower = p.enginePower
}))
.SelectMany(_ => _)
.ToList();

How to sort linq with fixed values and show all the rest with another sorting [duplicate]

This question already has answers here:
LINQ OrderBy versus ThenBy
(4 answers)
Closed 5 years ago.
IQueryable<Employee> query = ((IEnumerable<Employee>)employeeList)
.Select(x => x)
.AsQueryable();
var strListEmployees = input.MustIncludeIdsInPage.Split(",").ToList();
//the list of employee is dynamic, it'd return 3, 4, 5 or more data
var entities = query
.OrderBy(item => strListEmployees.IndexOf(item.Id.ToString()))
.PageBy(input)
.ToList();
example data
What I want is something like this in order:
by employee name
D
F
A
B
C
E
G
H
Employee D, F, A on top (fix value in List) and show the rest with name sorting (order by).
As M. Wiśnicki mentioned, this is easily solveable as You got only 3 elements. But to dynamically resolve this, I would stick to some function, where You would enter the List (or IEnumerable) of the objects and also the Names, based on which You want to filter them.
The code below is recursion, which will go through the array and select the 1st element (from array) and add the rest. Rest is calling the same function without the 1st name & without the element we have already added.
Something like:
public IEnumerable<Employee> GetOrderedPrefered(IEnumerable<Employee> aList, string[] aNames)
{
if (aNames.Length == 0) return aList.OrderBy(a => a.Name).ToList();
var lRes = new List<Employee>()
{
aList.FirstOrDefault(a => a.Name == aNames[0])
};
lRes.AddRange(
GetOrderedPrefered(
aList.Where(a => a.Name != aNames[0]),
aNames.Where(a => a != aNames.First()
).ToArray()
));
return lRes;
}
Usage:
var lRes = GetOrderedPrefered(persons, names);
foreach (var item in lRes)
Console.WriteLine(item.Name);
> D
> F
> A
> B
> C
> E
> G
You can use OrderBy() and ThenBy()
List<Test> tests = new List<Test>()
{
new Test() {EmployeeID = "1", Name = "A"},
new Test() {EmployeeID = "2", Name = "B"},
new Test() {EmployeeID = "3", Name = "C"},
new Test() {EmployeeID = "4", Name = "D"},
new Test() {EmployeeID = "5", Name = "E"},
new Test() {EmployeeID = "6", Name = "F"},
new Test() {EmployeeID = "7", Name = "G"},
new Test() {EmployeeID = "8", Name = "H"},
};
var x = tests.OrderBy(name => name.Name != "D")
.ThenBy(name => name.Name != "F")
.ThenBy(name => name.Name != "A")
.ThenBy(name => name.Name)
.ToList();
Result is: First D,F,A and others names
Edit:
string[] filtr = new[] {"D", "F", "A"};
var fdata = tests.Where(d => filtr.Contains(d.Name)).OrderBy(z=>z.Name).ToList();
var odata = tests.Where(d => !filtr.Contains(d.Name)).OrderBy(z => z.Name).ToList();
fdata.AddRange(odata);
var set = Enumerable.Range(0, 8)
.Select(i => new {
Name = new string(new[] { (char)('A' + i) })
});
var before = string.Join(",", set.Select(i => i.Name)); //A,B,C,D,E,F,G,H
var priorities = "D,F".Split(',').Select((v, i) => new { Value = v, Index = i });
var query = from s in set
join p in priorities on s.Name equals p.Value into m
from x in m.DefaultIfEmpty(new { Value = s.Name, Index = int.MaxValue })
orderby x.Index, s.Name
select s.Name;
var result = string.Join(",", query); //D,F,A,B,C,E,G,H

Linq how to query a list of items for a combined list of a child collection based on a property of the parent item

Hi say I have the objects:
public class InvoiceLine
{
}
and
public class InvoiceHeader
{
public char Group { get; set; }
public List<InvoiceLine> InvoiceLines { get; set; }
}
Data is set up for them as follows:
var invoiceLine1 = new InvoiceLine();
var invoiceLine2 = new InvoiceLine();
var invoiceLine3 = new InvoiceLine();
var invoiceLine4 = new InvoiceLine();
var invoiceLine5 = new InvoiceLine();
var invoiceLine6 = new InvoiceLine();
var invoiceLine7 = new InvoiceLine();
var invoiceLine8 = new InvoiceLine();
var invoiceHeader1 = new InvoiceHeader { Group = 'A', InvoiceLines = new List<InvoiceLine> { invoiceLine1, invoiceLine2 } };
var invoiceHeader2 = new InvoiceHeader { Group = 'A', InvoiceLines = new List<InvoiceLine> { invoiceLine3, invoiceLine4 } };
var invoiceHeader3 = new InvoiceHeader { Group = 'B', InvoiceLines = new List<InvoiceLine> { invoiceLine5, invoiceLine6 } };
var invoiceHeader4 = new InvoiceHeader { Group = 'B', InvoiceLines = new List<InvoiceLine> { invoiceLine7, invoiceLine8 } };
var invoiceHeaders = new List<InvoiceHeader>
{
invoiceHeader1,
invoiceHeader2,
invoiceHeader3,
invoiceHeader4
};
What I want to get is a Lists of invoiceLines for each Group.
So I would like for group A:
invoice1, invoice2, invoice3 and invoice4
and for group B:
invoice5, invoice6, invoice7 and invoice8
The furthest I got was:
var invoiceLinesGroupA = invoiceHeaders.SelectMany(x => x.InvoiceLines);
which as far as I can tell will get all eight invoiceLines. Somehow I need to discrimate by group to just get the ones for groupA and do likewise for groupB.
Can anyone help with this?
You may just want to group the invoice headers by the group:
var groups = invoiceHeader.GroupBy(ih => ih.Group);
Then you can access the lines of the groups:
foreach(var group in groups)
{
Console.WriteLine("Group " + group.Group);
Console.WriteLine("Lines:");
Console.WriteLine(string.Join(", ", group.SelectMany(h => h.InvoiceHeader.InvoiceLines)));
}
Output would be something like
Group A
Lines:
invoice1, invoice2, invoice3, invoice4
Group B
Lines:
invoice5, invoice6, invoice7, invoice8
Look at this overload of Enumerable.SelectMany
var result = invoiceHeaders
.SelectMany(x => x.InvoiceLines,
(group, InvoiceLine)=>{group, InvoiceLine})
.Where(res => res.group='A');
This should do it:
var test = from h in invoiceHeaders
from i in h.InvoiceLines
group i by h.Group
into g
select new {key = g.Key, rows = g.ToArray()};
You can then access the items like this test.Where(x => x.key == 'A').rows
var q = from current in myInvoiceHeaders
join c in myInvoiceLines on current.Id equals c.Header.Id into linesByHeader
select new {
Header = current,
Lines = linesByHeader
};
should work under the premise that you have a criteria to join both entities.
var regrouped = invoiceHeaders
.SelectMany(
header => header.InvoiceLines,
(header, line) => new { header, line }
)
.GroupBy(
o => o.header.Group,
o => o.line,
(groupName, lines) => new InvoiceHeader
{
Group = groupName,
InvoiceLines = lines.ToList()
}
)
.ToList();

How to merge two lists while adding metadata indicating value source with LINQ statement?

Given, for instance:
var a = new List<int>(){ 1 , 2 , 50 };
var b = new List<int>(){ 9 , 7 , 2 };
I need to merge them together to one sorted list, while adding some data indicating their origin (a or b). An output for example would be something like:
mergedList = { {1,false},{2,false},{2,true},{7,true},{9,true},{50,false} }
(true means it comes from a).
Edit start...
mergedList =
{ {1,IsB=false},{2,IsB=false},{2,IsB=true},{7,IsB=true},{9,IsB=true},{50,IsB=false} }
...Edit end
How can I do it with LINQ, preferably in query statement form (from ... select ...) ?
Not query form, but should work.
var ans = a.Select(i => new { Value = i, FromA = true })
.Concat(b.Select(i => new { Value = i, FromA = false }))
.OrderBy(i => i.Value);
You could create an anonymous type with the additional property:
var a = new List<int>(){ 1 , 2 , 50 };
var b = new List<int>(){ 9 , 7 , 2 };
var ax = a.Select(i => new{ Num = i, FromB = false });
var bx = b.Select(i => new{ Num = i, FromB = true});
var merged = ax.Concat(bx).OrderBy(x => x.Num);
Note that Enumerable.Concat will not eliminate dulicates, but since you want to add the origin i assume that this is desired.
Output:
foreach(var x in merged)
Console.WriteLine("Num: {0} From-B? {1}", x.Num, x.FromB);
Demo
var aItems = from aa in a
select new {Value = aa, Indicator = true};
var bItems = from bb in b
select new {Value = bb, Indicator = false};
var result = aItems.Concat(bItems).OrderBy(t => t.Value);
And pure method syntax:
var aItems = a.Select(aa => new {Value = aa, Indicator = true});
var bItems = b.Select(bb => new {Value = bb, Indicator = false});
var result = aItems.Concat(bItems).OrderBy(t => t.Value);
I think you can do this with anonymous types:
var mergedList = a.Select(x => new {val = x, tag = true})
.Union(b.Select(x => new {val = x, tag = false}))
.OrderBy(x => x.val);

LINQ GroupBy and Select on different property

If I have the following collection:
var foos = new List<Foo>
{
new Foo{ Name = "A", Value = 1 },
new Foo{ Name = "B", Value = 1 },
new Foo{ Name = "B", Value = 2 },
new Foo{ Name = "C", Value = 1 },
};
And I want to end-up with:
A-1
B-2
C-1
Where in the case of the duplicate "B" I want to select the "B" with the highest Value?
Something like:
var filteredFoos = foos.GroupBy(x => x.Name).Select_Duplicate_With_Highest_Value
var query = from p in foos
group p by p.Name into g
select new
{
Name = g.Key,
Value = g.Max(a => a.Value)
};
var filteredFoos =
foos.GroupBy(x => x.Name)
.Select(x => new { Name = x.Key, Value = x.Max(f => f.Value) });
Try this query:
var filteredFoos = foos.GroupBy(x => x.Name)
.Select(p => new { p.Key, p.Max(x => x.Value) });
For anyone with more than 2 columns:
var subquery = from p in foos
group p by p.Name into g
select new
{
Name = g.Key,
Value = g.Max(a => a.Value)
};
var query = from f in foos
join s in subquery
on f.Name equals s.Name
where f.Value == s.Value
select f;
If this is against SQL, make sure Name is a primitive.

Categories