Using LINQ and lambda expressions, I am trying to write data that I have pulled to a text file.
using (var contextDb = new TimoToolEntities())
{
using (var writeFile = new StreamWriter(saveTo))
{
var randomData = contextDb.WorkCenter_Operations.Where(d => d.Job_Number >= 1 && d.Part_Number.Length >= 1 && d.Oper_Number >= 1 )
.OrderBy(d => d.Oper_Number)
.GroupBy(d => d.Job_Number , d => d.Part_Number ).ToList();
foreach (var record in randomData)
{
Console.WriteLine(record.Job_Number + "," + record.Part_Number); // error here
}
}
Console.ReadLine();
}
I am getting the error the 'IGrouping does not contain a definition for 'name' and no extension method 'name' accepting a first argument of type 'IGrouping' could be found.
I have looked around and believe that the objects are anonymous, but I haven't been able to find a fix that will work.
When you use this overload of GroupBy
.GroupBy(d => d.Job_Number , d => d.Part_Number )
the first lambda is a key selector (you group by Job_Number) and the second one is a value selector. Your record will be a collection of Part_Number with Job_Number as a key.
This MSDN example illustrates the basic usage:
// 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);
}
Your intent is not 100% clear, so just in case you actually wanted to group by multiple fields, use a different overload like this:
.GroupBy(d => new { d.Job_Number, d.Part_Number })
Then your record will be a collection of whatever your data is and will have an anonymous key where you can access for example record.Key.Job_Number
Related
I am trying to understand the code below:
Color32[] colors = mesh.colors32;
IEnumerable<IGrouping<byte, int>> hierarchyMap = colors.Select((color, index) => new { color, index }).GroupBy(c => c.color.g, c => c.index);
I used google and only found some tutorials for GroupBy(xxx)(only one parameter inside brackets), which means xxx is the key for the group. What if there were two parameters inside the brackets?
Technically, the accepted answer is trying to group by using two keys. It doesn't explain what if there are two parameters inside the GroupBy bracket.
If there are two parameters inside the bracket, it will group the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
Let say we have an Employee class
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
}
And then the code logic is below.
var employees = new List<Employee>
{
new Employee { Name="Dave", Age=25 },
new Employee { Name="John", Age=23 },
new Employee { Name="Michael", Age=30 },
new Employee { Name="Bobby", Age=30 },
new Employee { Name="Tom", Age=25 },
new Employee { Name="Jane", Age=21 }
};
var query = employees.GroupBy(employee => employee.Age, employee => employee.Name);
foreach (IGrouping<int, string> employeeGroup in query)
{
Console.WriteLine(employeeGroup.Key);
foreach (string name in employeeGroup)
{
Console.WriteLine($"=> {name}");
}
}
The output will be:
25
=> Dave
=> Tom
23
=> John
30
=> Michael
=> Bobby
21
=> Jane
Reference from MSDN
Update
When using two parameters in the GroupBy method, the two parameters represents,
keySelector Func<TSource,TKey>
A function to extract the key for each element.
elementSelector Func<TSource,TElement>
A function to map each source element to an element in the
IGrouping.
What it does it is that it groups the sequence based on the first parameters, and projects each element of the group using the function specificied as second parameter.
Groups the elements of a sequence according to a specified key selector function and projects the elements for each group by using a specified function.
Grouping with Two Keys
If your intension is to group by two keys, then could use an anonymous type for grouping by multiple keys
.GroupBy(c => new {c.color.g, c.index})
For example, from the code in OP
colors.Select((color, index) => new { color, index })
.GroupBy(c => new {c.color.g, c.index});
So I have a collection of objects who have multiple properties, two of these are groupname and personname. Now I need to count in the collection how many of each object belong to a certain group and person. So in other words, I need to group by groupname, then personname and then count how many objects have this combination. First I created this
public MultiKeyDictionary<string, string, int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
List<string> groups = gr;
groups.Add("");
List<string> names = na;
names.Add("");
List<Home> Filtered = homes.ToList();
Filtered.ForEach(h => h.RemoveNull());
var result = new MultiKeyDictionary<string, string, int>();
int counter1 = 0;
foreach (var g in groups)
{
int counter2 = 0;
foreach (var n in names)
{
int counter3 = 0;
foreach (Home h in Filtered)
{
if (h.GroupName == g && h.PersonName == n)
{
counter3++;
if (counter3 > 100)
break;
}
}
if (counter3 > 0)
{
result.Add(g,n,counter3);
}
counter2++;
}
counter1++;
}
Which may look good, but the problem is that the "home" parameter can contain more than 10000 objects, with more than 1500 unique names and around 200 unique groups. Which causes this to iterate like a billion times really slowing my program down. So I need an other way of handling this. Which made me decide to try using linq. Which led to this creation:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName })
.Select(y => (MultiKeyDictionary<string, string, int>)result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
Which gives an error "Cannot convert type 'void' to 'MultiKeyDictionary<string,string,int>' and I have no idea how to solve it. How can I make it so that the result of this query gets stored all in one MultikeyDictionary without having to iterate over each possible combination and counting all of them.
Some information:
MultiKeyDictionary is a class I defined (something I found on here actually), it's just a normal dictionary but with two keys assosiated to one value.
The RemoveNull() method on the Home object makes sure that all the properties of the Home object are not null. If it is the case the value gets sets to something not null ("null", basic date, 0, ...).
The parameters are:
homes = a list of Home objects received from an other class
gr = a list of all the unique groups in the list of homes
na = a list of all the unique names in the list of homes
The same name can occur on different groups
Hopefully someone can help me get further!
Thanks in advance!
Select must return something. You are not returning but only adding to an existing list. Do this instead:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName }):
var result = new MultiKeyDictionary<string, string, int>);
foreach(var y in newList)
{
result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
}
The reason you are getting error below:
"Cannot convert type 'void' to 'MultiKeyDictionary'
is because you are trying to cast the returned value from Add which is void to MultiKeyDictionary<string,string,int> which clearly cannot be done.
If MultiKeyDictionary requires the two keys to match in order to find a result, then you might want to just use a regular Dictionary with a Tuple as a composite type. C# 7 has features that make this pretty easy:
public Dictionary<(string, string), int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
return Filtered.GroupBy(x => (x.GroupName, x.PersonName))
.ToDictionary(g => g.Key, g => g.Count);
}
You can even associate optional compile-time names with your tuple's values, by declaring it like this: Dictionary<(string groupName, string personName), int>.
Your grouping key anonymous object should work fine as a standard Dictionary key, so no reason to create a new type of Dictionary unless it offers special access via single keys, so just convert the grouping to a standard Dictionary:
var result = Filtered.GroupBy(f => new { f.GroupName, f.PersonName })
.ToDictionary(fg => fg.Key, fg => fg.Count());
I have written this query:
private IDictionary<string, ReftableCache> Lookup(List<string> list)
{
var query = (from r in ReftableCached
where list.Contains(r.Description)
select new ReftableCache
{
RefTableName = r.RefTableName,
Reftable_K = r.Reftable_K,
Description = r.Description
});
return query.ToDictionary(r => r.Reftable_K, ????);
}
But I have problem with the part in ????
My goal is two have a dictionary, keyed by Reftable_K and the value pair of the key to be the object we created for it, but I can't figure out the syntax.
The ToDictionary() method takes two lambda expressions, the key and the value.
For both expressions you get the respective object from the collection as context.
So, you could try this:
private IDictionary<string, ReftableCache> Lookup(List<string> list)
{
// execute the query
var result = (from r in ReftableCached
where list.Contains(r.Description)
select new ReftableCache
{
RefTableName = r.RefTableName,
Reftable_K = r.Reftable_K,
Description = r.Description
})
.ToList();
// convert to dictionary
return result.ToDictionary(r => r.Reftable_K /*key*/
, v => v /*value*/);
}
You could also use the new object in the value part, for example:
return result.ToDictionary(r => r.Reftable_K /*key*/
, v => new ReftableCache
{
RefTableName = r.RefTableName,
Reftable_K = r.Reftable_K,
Description = r.Description
} /*value*/);
It must work for you: (If you want to set the object itself as the value)
return query.ToDictionary(r => r.Reftable_K);
This overload of the ToDictionary takes an anonymous method as input. This anonymous method is actually a key selector that will select the key from the list and set it as the key in the dictionary object. For example, Reftable_K from the ReftableCache is selected as the key for the result in the dictionary and the value is the ReftableCache object itself.
Additional:
May be this method has confused you a bit. To make things more clear I want to explain one thing more. While creating a new dictionary we must specify the Key and the Value. In the example given above, the CLR automatically returns the instance of the object as value. If you want to specify the value as you want, you can add second anonymous function as input to the method. Like that:
return query.ToDictionary(r => r.Reftable_K, r => r);
return query.ToDictionary(r => r.Reftable_K, r => r.Column2);
// and so on...
You can use:
return query.ToDictionary(r => r.Reftable_K, r => r);
You can also just use the overload that automatically returns the item as the value:
return query.ToDictionary(r => r.Reftable_K);
This makes Reftable_K the key, and the ReftableCache instance the value.
I am confused by the groupby behavior in LINQ to objects. Lets assume for this I have the following class;
public class person
{
public string name { get; set; }
public int age { get; set; }
}
Lets say I have a list or type person; List<person> people
Now, I want to produce an IEnumerable<T> with an IGrouping or anonymous type that has two properties 1) the name (the key) and 2) the sum of all of the ages for people with that name.
Here are a couple of examples of things I've tried (unsuccessfully);
people.GroupBy(x => x.name, x => x, (key, value) => value.Aggregate((c, n) => c + n));
That won't compile with the error cannot convert type "int" to "namespace.person"
Before that I was trying something more along the lines of;
people.GroupBy(x => x.name).Select(g => new { g.Key, g.Aggregate((c, n) => c + n)) } );
Which essentially gives the same error. I'm basically struggling to understand what the value returned by GroupBy really is. At first I thought that basic overload was giving me a key value pair where x.Key was the key I specified with my delegate and x.Value would be an IEnumerable<T> where the typeof T would be the type of x. Of course, if that were the case my second example would work like a charm. My question is somewhat open ended but can someone explain 2 things, firstly; How do I accomplish my end goal with LINQ? And Secondly, why isn't the result of GroupBy more along the lines of what I describe here? What is it? I feel like a key value pair where the value is a collection of objects that match that key is far more intuitive than what is actually returned.
var grouped = people.GroupBy(x => x.name)
.Select(x => new
{
Name = x.Key,
Age = x.Sum(v => v.age),
Result = g.Aggregate(new Int32(), (current, next) => next.age + next.age)
});
If you want you can group the result of that again by Name and it will be a grouping with Key as the name and Age as the value
you can do it with expression syntax
var results = from p in persons
group p.car by p.name into g
select new { name = g.Key, age = g.Sum(c=>.age };
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
UPDATE:
Sorry, I think I omitted an important section in regards to the above. The list is contained within another list i.e. List> myitemList; which will contain other irrelevant fields which may require further filtering. I'm not sure???
NOTE: Sorry formatting is messed up once again!
To give a bit of context to this:
Item1 (of type List)
Item Name Value
(Item 1) Type 1
(Item 2) Description Test
(Item 3) Code A
(Item 4) Net 100.00
Item2 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test1
(Item 3) Code B
(Item 4) Net 95.55
Item3 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test2
(Item 3) Code A
(Item 4) Net 35.95
As you can see each list of type List contains 4 Field entries where my Field is defined with Name (String) and Value (Object)
Each of these list is then added to a main list. So I need to loop through the main list and in turn I want to end up with a dictionary what will contain the "Code" and sum of "Net" for each list. So at the end, I should just end up with
A, 135.95
B, 95.55
I don't know if the above make sense. I hope it does!
UPDATE
The fact that I'm dealing with a list> actually didn't make a different as I actually wanted to sum up one list at the time, so provide answer is correct! Thank you!
The code that you posted is almost what you need - for using it on the list you need to simplify it slightly:
var result = myList // No flattening
.GroupBy(x => x.Code) // Group the items by the Code
.ToDictionary(g => g.Key, g => g.Sum(v => v.Value)); // Total up the values
var list = new List<Field>
{
new Field { Code = "A", Value = 10 },
new Field { Code = "A", Value = 20 },
new Field { Code = "B", Value = 30 },
};
var dic = list
.GroupBy(z => z.Code)
.ToDictionary(z => z.Key, z => z.Sum(f => f.Value));
You can do:
Dictionary<string, int> dictionary =
list.GroupBy(r => r.ID)
.ToDictionary(grp => grp.Key, grp => grp.Sum(r => r.Value));
Considering you have class like:
public class MyClass
{
public string ID { get; set; }
public int Value { get; set; }
}