How do I Iterate Linq group result set? - c#

I'm getting some data from my database and using linq to calculate sums and counts and group the data.
This is what I have:
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
i now need to pass cs to a method in another class so that I can extract Comm, SIList and Count for each item in the group, what type do I pass it as? IEnumerable doesn't work. The actual linq group result type seems to be:
{System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<Model.Domain.MasterData
.MyItem,Model.Domain.SI<>f__AnonymousTyped<Model.Domain.MasterData.MyItem,System.Collections.Generic.List<Model.Domain.SI>,int>>}
Any ideas? I effectively want to pass cs as a variable and iterate through it there.

You'll need to create a type that matches the definition of your anonymous type, if it's going to be used in different scopes.
public class SomeClass {
public Comm Comm { get; set; }
public IList<String> SIList { get; set; }
public Int32 Count { get; set; }
}
var si = _repository.GetAllByDate(date);
var cs = from s in si
group s by s.Name into g
select new SomeClass { Comm = g.Key, SIList = g.ToList(), Count = g.Count() };
EDIT: I supposed we can assume that the list will be of String so I'm editing for that. If that's the wrong type you'll need to change the IList<T> definition accordingly.

The reason that you get such a complicated type is because the query uses lazy execution. You are looking at the type of the expression that returns the result, not the type of the result.
The type of the result is IEnumerable<_hidden_internal_class_name_>, i.e. as you are creating anonymous objects in the query, the result is a stream of objects of a class that the compiler creates internally.
It's pretty useless to pass on that result to another method, as it would need to use reflection to read the properties in the objects. You should create a named class for the objects in the result, so that it's easy to access its properties.

Creating a type is an excellent idea, but why do that when a returned Tuple can be done without creating a new class or struct? If the need is local and or internal and the class won't be reused, try using a Tuple instead.
Select new Tuple<Comm, IEnumerable<string>, Int32>( new Comm(), myStringList.AsEnumerable(), myCount )

class Pet
{
public string Name { get; set; }
public int Age { get; set; }
}
// Uses method-based query syntax.
public static void GroupByEx1()
{
// Create a list of pets.
List<Pet> pets =
new List<Pet>{ new Pet { Name="Barley", Age=8 },
new Pet { Name="Boots", Age=4 },
new Pet { Name="Whiskers", Age=1 },
new Pet { Name="Daisy", Age=4 } };
// Group the pets using Age as the key value
// and selecting only the pet's Name for each value.
IEnumerable<IGrouping<int, string>> query =
pets.GroupBy(pet => pet.Age, pet => pet.Name);
// Iterate over each IGrouping in the collection.
foreach (IGrouping<int, string> petGroup in query)
{
// Print the key value of the IGrouping.
Console.WriteLine(petGroup.Key);
// Iterate over each value in the
// IGrouping and print the value.
foreach (string name in petGroup)
Console.WriteLine(" {0}", name);
}
}
/*
This code produces the following output:
8
Barley
4
Boots
Daisy
1
Whiskers
*/

Pass it as object and in your foreach loop, use var as the iterator.

Related

Exception when using LINQ orderby: "Failed to compare two elements in the array"

Here is sample code to reproduce the exception:
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore;
namespace Demo
{
[Keyless]
public class Contact
{
public string Name { get; set; } = default!;
public string? Address { get; set; } = default!;
public int? CCode { get; set; } = 0;
public int OtherValue { get; set; } = 0;
}
public class Foo
{
public static void Main()
{
List<Contact> raw = new();
raw.Add(new Contact { CCode = 1, Name = "Foo", Address = "Bar" });
raw.Add(new Contact { CCode = 2, Name = "Foo", Address = "Bar" });
ProcessRawResults(raw);
}
public static void ProcessRawResults(List<Contact> raw)
{
var q = from i in raw
group i by new { i.CCode, i.Name, i.Address } into g
orderby g.Key
select g;
foreach (var group in q)
{
}
}
}
}
When executing this program, an exception is thrown when execution reaches foreach (var group in q):
System.InvalidOperationException: 'Failed to compare two elements in the array.'
Inner Exception
ArgumentException: At least one object must implement IComparable
I have looked at other SO questions about this error message occurring when trying to Sort a List; but in this code I'm not sure which operation needs the comparator. It seems like the orderby g.Key operation might need to compare the anonymous class in the group, but then shouldn't the anon class have a default comparator? Or if it doesn't, I'm not sure where to put the code to implement this.
Confusingly, if I take i.CCode out of the group i by new line, then the exception doesn't happen any more.
Background: My real project is a Blazor app using EFCore 6 , and am receiving a List<Contact> from a Stored Procedure result, so it has to be [Keyless]. I have to work with the existing Stored Procedure unmodified, so am performing a transformation of the result in my code. I hope to collapse the set of results so that all entries with the same (CCode, Name, Address) result in a single row, and I'll concatenate the OtherValue into a list within that single row.
I guess it's because int? is actually Nullable<int> and Nullable<T> doesn't implement IComparable. I just tested your code but changed the grouping to this:
group i by new { CCode = i.CCode.HasValue ? i.CCode.Value : (int?)null, i.Name, i.Address } into g
and it seemed to work. It didn't throw that exception, at least.
Anonymous type do not have comparator, specify oder by properties:
var q = from i in raw
group i by new { i.CCode, i.Name, i.Address } into g
orderby g.Key.CCode, g.Key.Name, g.Key.Address
select g;

Linq How to deflate list of class/self join

I have a list of the following class
class testdata {
public string code { get; set; }
public string[] values { get; set; }
}
how to perform join such that I get list of code and values, i.e values will repeat with every code, e.g if there are two items in list like
1."code1",{"value1","value2"}
2."code2",{"value1","value2","value3"}
I want the final to be in format like
Code1,Value1
code1,Value2
Code2,value1
code3,value2
code3,value3
I want to achieve it via LINQ and anonymous class, I am able to do it with loop and by creating a class as per final structure.
myList.SelectMany(td => td.values.Select(v => new { td.code, v } ))
SelectMany says "take many IEnumerables and concatenate their elements into a single IEnumerable"
td is a single element in your list. It is a single instance of testdata
So now you can do a Select over td.values to create the individual anonymous objects. Select will return an IEnumerable, but wrapped inside a SelectMany, many instances ofIEnumerable<anonymous object> will be flattened into a single IEnumerable<anonymous object>
Try this
List<TestData> testData = new List<TestData>() {
new TestData() { code = "Code1", values = new string[] {"Value1","Value2"}},
new TestData() { code = "Code2", values = new string[] {"value1"}},
new TestData() { code = "code3", values = new string[] {"value2","value3"}}
};
var results = testData.Select(x => x.values.Select(y => new { code = x.code, value = y })).SelectMany(y => y).ToList();

Can I initialize an object in one statement using lambda for each syntax?

If I have these two classes:
public class StudyClass
{
public string className { get; set; }
public List<Student> students { get; set; }
}
public class Student
{
public string studentName { get; set; }
}
Then I can initialize the StudyClass object like that:
var classObject = GetClassData(); // returns a big object with many properties that I don't need
var studyClass= new StudyClass() {
className = classObject.className
}
foreach(var student in classObject.students)
{
studyClass.students.add(new Student() {
studentName = student.Name
});
}
Is it possible to do it in a more simple way, by doing something like:
var classObject = GetClassData(); // returns a big object with many properties that I don't need
var studyClass= new StudyClass() {
className = classObject.className,
students = classObject.students.ForEach...// i am stuck here
}
If it's possible, is there any performance benefit or drawback by doing that ?
Yes, you can do this using the LINQ Select method followed by returning the results as a list using ToList:
var classObject = GetClassData();
var studyClass = new StudyClass {
className = classObject.className
students = classObject.students.Select(s => new Student { studentName = s.Name}).ToList()
};
This will enumerate classObject.students calling the lambda function once for each one, where the expression returns a new Student using the current value (s) to set the studentName property.
is there any performance benefit or drawback by doing that ?
It's unlikely to be any more performant; internally it still has to enumerate classObject.students and has to call the lambda method, while the original method has to make use of List.Add. You need to properly measure the timings to find out if it makes a worthwhile difference in your environment.
You can do it using linq:
var studyClass= new StudyClass() {
className = classObject.className,
students = classObject.students.Select(s => new Student { studentName = s.Name }).ToList();
}
There maybe a little performance improvement. You have to enumerate classObject.students in both variations, but Select and ToList may be faster than calling List.Add for each single student.
You can just project a list using Select() and ToList(), like this:
var studyClass= new StudyClass()
{
className = classObject.className,
students = classObject.students.Select(s=>new Student(){Name=s.Name}).ToList()
}
It looks like you are wanting to clone the students from one list and append them to another list.
You can do that like this:
studyClass.students.AddRange(
classObject.students.Select(student =>
new Student {studentName = student.studentName}));
If you actually want to replace the list with a new one, you can do this instead:
studyClass.students = new List<Student>(
classObject.students.Select(student =>
new Student {studentName = student.studentName}));
This is likely to be marginally more performant than other methods, but the difference will probably be so small as to be negligible.

Linq to iterate through a collection and add another collection to its member

I have a situation where I need to iterate through a collection and add another collection to one of its member using Linq.
For example I have this class
public class Product
{
public string Car { get; set; }
public IEnumerable<Part> Part { get; set; }
}
This class would be within a collection like
IEnumerable<Product> ProductList
How can I populate the Part-property for each Product using GetPartData() with Linq
private IEnumerable<IEnumerable<Part>> GetPartData()
{
return new List<List<Part>>() {
new List<Part>{
new Part(){PartType="11",PartValue=1},
new Part(){PartType="12",PartValue=2}
},
new List<Part>{
new Part(){PartType="21",PartValue=1},
new Part(){PartType="22",PartValue=2}
}
};
}
So ultimately, my ProductList[0].Part should be equal to GetPartData()[0]
If both sequences should be linked via index you can use Enumerable.Zip:
ProductList = ProductList.Zip(GetPartData()
, (product, part) => new Product
{
Car = product.Car,
Part = part
})
.ToList();
Basically, you need to enumerate two IEnumerable at a time to match items from both. The ProductList and the result of GetPartData.
// The two IEnumerable
var products = ProductList;
var parts = GetPartData();
foreach((product, part) in (products, parts)) // will not work :(
{
product.Part = part;
}
Solutions has been debated before.
The Zip method will do it.
// The two IEnumerable
var products = ProductList;
var parts = GetPartData();
products.Zip(parts, (product, part) => product.Part = part).ToList();
The ToList() is really important, to force the execution.
If you are not comfortable with the lambda, you can do it like this:
// The two IEnumerable
var products = ProductList;
var parts = GetPartData();
products.Zip(parts, ProductPartAssociation).ToList();
...
Product ProductPartAssociation(Product product, IEnumerable<Part> part)
{
product.Part = part;
return product; // Actually not used.
}
The result of the Zip is an IEnumerable of whatever the ProductPartAssociation function return. You don't care about it, because what you need is just to be sure that the ProductPartAssociation is executed.

LINQ select one field from list of DTO objects to array

I have DTO class that defines order line like this:
public class Line
{
public string Sku { get; set; }
public int Qty { get; set; }
}
A list of type Line is populated like so:
List<Line> myLines = new List<Line>();
myLines.Add(new Line() { Sku = "ABCD1", Qty = 1 });
myLines.Add(new Line() { Sku = "ABCD2", Qty = 1 });
myLines.Add(new Line() { Sku = "ABCD3", Qty = 1 });
What I want is to use LINQ to get an array of SKUs from the myLines List. How can I go about doing that?
I am currently doing it manually like this ...
// Get SKU List
List<string> mySKUs = new List<string>();
foreach (Line myLine in myLines)
mySKUs.Add(myLine.Sku);
string[] mySKUsArray = mySKUs.ToArray();
I was trying to google for a solution, but I wasn't sure how to word the question...
P.S. is there any benefit/performance gain in using LINQ method to achieve what I am currently doing with foreach?
You can use:
var mySKUs = myLines.Select(l => l.Sku).ToList();
The Select method, in this case, performs a mapping from IEnumerable<Line> to IEnumerable<string> (the SKU), then ToList() converts it to a List<string>.
Note that this requires using System.Linq; to be at the top of your .cs file.
This is very simple in LinQ... You can use the select statement to get an Enumerable of properties of the objects.
var mySkus = myLines.Select(x => x.Sku);
Or if you want it as an Array just do...
var mySkus = myLines.Select(x => x.Sku).ToArray();
I think you're looking for;
string[] skus = myLines.Select(x => x.Sku).ToArray();
However, if you're going to iterate over the sku's in subsequent code I recommend not using the ToArray() bit as it forces the queries execution prematurely and makes the applications performance worse. Instead you can just do;
var skus = myLines.Select(x => x.Sku); // produce IEnumerable<string>
foreach (string sku in skus) // forces execution of the query
You can select all Sku elements of your myLines list and then convert the result to an array.
string[] mySKUsArray = myLines.Select(x=>x.Sku).ToArray();
In the case you're interested in extremely minor, almost immeasurable performance increases, add a constructor to your Line class, giving you such:
public class Line
{
public Line(string sku, int qty)
{
this.Sku = sku;
this.Qty = qty;
}
public string Sku { get; set; }
public int Qty { get; set; }
}
Then create a specialized collection class based on List<Line> with one new method, Add:
public class LineList : List<Line>
{
public void Add(string sku, int qty)
{
this.Add(new Line(sku, qty));
}
}
Then the code which populates your list gets a bit less verbose by using a collection initializer:
LineList myLines = new LineList
{
{ "ABCD1", 1 },
{ "ABCD2", 1 },
{ "ABCD3", 1 }
};
And, of course, as the other answers state, it's trivial to extract the SKUs into a string array with LINQ:
string[] mySKUsArray = myLines.Select(myLine => myLine.Sku).ToArray();

Categories