foreach loop data index as the key - c#

I have been trying to make for each loop with the index as the key
this case i want to made a logic if the input user is match with index and i will show foreach all of the data which has index as the key
I made two class like this
class DataContainer
{
public DataContainer()
{
}
public int index { get; set; }
public List<DataValue> DataValue { get; set; }
}
class DataValue
{
public DataValue()
{
IntegerValues = new List<int>();
}
public string name { get; set; }
public List<int> IntegerValues { get; set; }
}
after that i try to make list of datacontainer like this
List<DataContainer> harakatSininilMabsutoh = new List<DataContainer>(){
new DataContainer{index = 2015 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {9,55,18,11}},
new DataValue{name = "second" ,IntegerValues = {5,54,18,11}},
new DataValue{name = "third" ,IntegerValues = {40,26,14,11}},
new DataValue{name = "four" ,IntegerValues = {22,0,5,10}},
new DataValue{name = "fifth" ,IntegerValues = {46,44,17,0}},
}
},
new DataContainer{index = 2013 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {26,49,8,11}},
new DataValue{name = "second" ,IntegerValues = {19,42,8,11}},
new DataValue{name = "third" ,IntegerValues = {55,3,12,11}},
new DataValue{name = "fourth" ,IntegerValues = {27,4,23,8}},
new DataValue{name = "fifth" ,IntegerValues = {43,22,7,1}},
}
},
new DataContainer{index = 2001, DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {35,44,27,10}},
new DataValue{name = "second" ,IntegerValues = {24,41,27,10}},
new DataValue{name = "third" ,IntegerValues = {36,30,26,10}},
new DataValue{name = "fourth" ,IntegerValues = {59,24,8,6}},
new DataValue{name = "fifth" ,IntegerValues = {29,27,26,1}},
}
}
};
and then i made a logic like this
int years = (this is user input);
if(years == 2015)
{
///How to for each this which has index 2015
}
else if (years = 2013)
{
//how to foreach this which has index 2013
}
else if (years = 2001)
{
//how to foreach this which has index 2001

The simplest is by using LINQ FirstOrDefault like this
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
foreach (DataValue val in requested.DataValue)
Console.WriteLine(val.name + ": " + string.Join(", ", val.IntegerValues));
Edit 2:
If you only need all the integers, without name, without anything else, then you could either do this to get the List<List<int>>:
int userinput = 2015;
List<List<int>> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists
or do this to get List<int> (flattened):
int userinput = 2015;
List<int> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.SelectMany(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in single list
As FirstOrDefault may return null if the userinput is not found, note that, if you are not using C#6, you may want to consider two steps LINQ:
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
List<List<int>> intValuesOnly = requested
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists

Firstly, note that in this line you have tried to use a type name as a property name:
public List<DataValue> DataValue { get; set; }
I've renamed this property to 'DataValues' as shown:
public List<DataValue> DataValues { get; set; }
You have a list ('harakatSininilMabsutoh'), each element of which is a DataContainer. Each DataContainer in the list has two properties: an index and a list of 'DataValues' (NB renamed from 'DataValue' in your post).
The looping logic you want will therefore be something like this:
var matchingYears = harakatSininilMabsutoh.Where(h => h.index == years);
foreach (var dataContainer in matchingYears)
{
foreach (var item in dataContainer.DataValues)
{
// Action required on each DataValue:
Debug.Print(item.name + " " + string.Join(",", item.IntegerValues));
}
}
You'll need to add the following 'using' statement to your class, since 'Where' is a LINQ extension method:
using System.Linq;
If you know that there will be exactly one matching year, you could add First() and remove the outer foreach loop. If you know there will be at most one matching year (but there could be zero), you can still remove the outer foreach loop but you should use FirstOrDefault() instead and test for null.

Related

Get first item of IGrouping

IEnumerable<IGrouping<long, MyClass>> datas = list.GroupBy(x => x.PropertyXYOfMyClass);
// get all items from each group
foreach (var grouping in datas)
{
long groupKey = groupingByMyClass.Key;
//iterating through values
foreach (var item in groupingByMyClass)
{
long key = item.PropertyIntOfClassA;
string property = item.PropertyA;
}
}
Each group contains some items, wow to get values from first item of each group?
UPDATE
void Extract()
{
List<DataHolder> data = new List<DataHolder>();
List<DateTime> randomTimes = new List<DateTime>();
Random r = new Random();
DateTime d = new DateTime(2019, 9, 19, 7, 0, 0);
for (int i = 0; i < 100; i++)
{
DataHolder dh = new DataHolder();
TimeSpan t = TimeSpan.FromSeconds(r.Next(0, 14400));
dh.OID = i;
dh.Value = r.Next(50);
dh.Snapshottime = d.Add(t);
data.Add(dh);
}
data.OrderBy(o => o.Snapshottime).ToList();
List<DataHolder> SortedList = data.OrderBy(o => o.Snapshottime).ToList();
TimeSpan interval = new TimeSpan(0, 15, 0);
var result = SortedList.GroupBy(x => x.Snapshottime.Ticks / interval.Ticks) .OrderBy(x => x.Key);
}
public class DataHolder
{
public int OID { get; set; }
public double Value { get; set; }
public DateTime Snapshottime { get; set; }
}
Here from result i need to take first item from each group.
try this:
var finalResult = result.Select(gpr=>grp.First());
or if you want the earliest/Latest/etc you could order by first:
var finalResult = result.Select(gpr=>grp.OrderBy(x=>x.SnapShotTime).First());
You've already done the heavy lifting. Make a simple loop over the result:
var result = SortedList.GroupBy(x => x.Snapshottime.Ticks / interval.Ticks) .OrderBy(x => x.Key);
var resultList = new List<DataHolder>();
foreach(var group in result)
{
resultList.Add(group.First());
}
I hope this helps.

c# linq add a column to a new list from another single column list

My project is MVC5. I have a table with multiple rows for the same day, I need to get the total of this entry for each day, I use the following:
var days = db.Nutrition.Where(x => x.Day >= fromDate
&& x.Day <= toDate).DistinctBy(x => x.Day).AsEnumerable().ToList();
List<double?> calories = new List<double?>();
foreach (var item in days)
{
calories.Add(days.Where(c => c.Day==item.Day).Select(x => x.Calories).Sum());
}
I get a list containing the totals. Now I need to make a new list that has two columns.
I made the following model:
public class Consumption
{
public virtual double? Calories { get; set; }
public virtual string Name { get; set; }
}
I tried to use the following to generate the new list:
List<Consumption> newList = new List<Consumption>();
var name = new Consumption { Name = "name" };
foreach (var item in calories)
{
newList.Add(name, Calories = (double)item.Value);
}
I get the following error:
The name 'Calories' does not exist in the current context
Edit
Thanks to Stephen's comment:
I just used one line to achieve same result
var results = db.Nutrition.Where(x => x.Day >= fromDate && x.Day <= toDate).GroupBy(l => l.Day)
.Select(cl => new { Name = "name", Calories = cl.Sum(c => c.Calories)}).ToList();
Try with:
List<Consumption> newList = new List<Consumption>();
var name = new Consumption { Name = "name" };
foreach (var item in calories)
{
var cal = new Consumption{ Name = "name", Calories = (double)item.Value });
newList.Add(cal);
}
You received this compiler error
The name 'Calories' does not exist in the current context
because the List<Consumption>.Add(Comsumption item) method on your variable newList only accepts one argument of type Consumption.
Regarding your intentions, and the discussion in your comments with #StephenMuecke, it became clear that your intention is to Sum a property double Calories, and GroupBy by property DateTime Day and then project that into a List<Consumption>.
var dateTimeFormat = "yyyy-dd-MM";
var results = db.Nutrition.Where(x => x.Day >= fromDate && x.Day <= toDate)
.GroupBy(x => x.Day)
.Select(groupedX => new Consumption
{
Name = groupedX.Key.ToString(dateTimeFormat),
Calories = groupedX.Sum(y => y.Calories)
}).ToList();

Best algorithm to determine added and removed items when comparing to collections

I am looking for the best algorithm to compare 2 collections and determine which element got added and which element got removed.
private string GetInvolvementLogging(ICollection<UserInvolvement> newInvolvement, ICollection<UserInvolvement> oldInvolvement)
{
//I defined the new and old dictionary's for you to know what useful data is inside UserInvolvement.
//Both are Dictionary<int, int>, because The Involvement is just a enum flag. Integer. UserId is also Integer.
var newDict = newInvolvement.ToDictionary(x => x.UserID, x => x.Involvement);
var oldDict = oldInvolvement.ToDictionary(x => x.UserID, x => x.Involvement);
//I Want to compare new to old -> and get 2 dictionaries: added and removed.
var usersAdded = new Dictionary<int, Involvement>();
var usersRemoved = new Dictionary<int, Involvement>();
//What is the best algoritm to accomplish this?
return GetInvolvementLogging(usersAdded, usersRemoved);
}
private string GetInvolvementLogging(Dictionary<int, Involvement> usersAdded, Dictionary<int, Involvement> usersRemoved)
{
//TODO: generate a string based on those dictionaries.
return "Change in userinvolvement: ";
}
Added elements are only in newDict removed only in oldDict
var intersection = newDict.Keys.Intersect(oldDict.Keys);
var added = newDict.Keys.Except(intersection);
var removed = oldDict.Keys.Except(intersection);
EDIT
I modify your base function, dictionaries is no neded.
Example UserInvolvement implementation
class UserInvolvement
{
public int UserId;
public string Name;
public string OtherInfo;
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
UserInvolvement p = obj as UserInvolvement;
if ((System.Object)p == null)
{
return false;
}
return (UserId == p.UserId) && (Name == p.Name) && (OtherInfo == p.OtherInfo);
}
public override string ToString()
{
return $"{UserId} - {Name} - {OtherInfo}";
}
}
And example function:
private static string GetInvolvementLogging(ICollection<UserInvolvement> newInvolvement,
ICollection<UserInvolvement> oldInvolvement)
{
var intersection = newInvolvement.Select(x => x.UserId).Intersect(oldInvolvement.Select(x => x.UserId));
var addedIds = newInvolvement.Select(x => x.UserId).Except(intersection);
var removedIds = oldInvolvement.Select(x => x.UserId).Except(intersection);
List<UserInvolvement> modifiedUI = new List<UserInvolvement>();
foreach (var i in intersection)
{
var ni = newInvolvement.First(a => a.UserId == i);
var oi = oldInvolvement.First(a => a.UserId == i);
if (!ni.Equals(oi))
{
modifiedUI.Add(ni);
}
}
List<UserInvolvement> addedUI = newInvolvement.Where(x => addedIds.Contains(x.UserId)).Select(w => w).ToList();
List<UserInvolvement> removedUI = oldInvolvement.Where(x => removedIds.Contains(x.UserId)).Select(w => w).ToList();
StringBuilder sb = new StringBuilder();
sb.AppendLine("Added");
foreach (var added in addedUI)
{
sb.AppendLine(added.ToString());
}
sb.AppendLine("Removed");
foreach (var removed in removedUI)
{
sb.AppendLine(removed.ToString());
}
sb.AppendLine("Modified");
foreach (var modified in modifiedUI)
{
sb.AppendLine(modified.ToString());
}
return sb.ToString();
}
And my test function:
static void Main(string[] args)
{
List<UserInvolvement> newUI = new List<UserInvolvement>()
{
new UserInvolvement()
{
UserId = 1,
Name = "AAA",
OtherInfo = "QQQ"
},
new UserInvolvement()
{
UserId = 2,
Name = "BBB",
OtherInfo = "123"
},
new UserInvolvement()
{
UserId = 4,
Name = "DDD",
OtherInfo = "123ert"
}
};
List<UserInvolvement> oldUI = new List<UserInvolvement>()
{
new UserInvolvement()
{
UserId = 2,
Name = "BBBC",
OtherInfo = "123"
},
new UserInvolvement()
{
UserId = 3,
Name = "CCC",
OtherInfo = "QQ44"
},
new UserInvolvement()
{
UserId = 4,
Name = "DDD",
OtherInfo = "123ert"
}
};
string resp = GetInvolvementLogging(newUI, oldUI);
WriteLine(resp);
ReadKey();
WriteLine("CU");
}
Result is:
Added
1 - AAA - QQQ
Removed
3 - CCC - QQ44
Modified
2 - BBB - 123
You could try with Linq:
var usersAdded = newDict.Except(oldDict);
var usersRemoved = oldDict.Except(newDict);
If you need dictionaries as a result you can cast:
var usersAdded = newDict.Except(oldDict).ToDictionary(x => x.Key, x => x.Value);
var usersRemoved = oldDict.Except(newDict).ToDictionary(x => x.Key, x => x.Value);
Think best algorithm will be
foreach (var newItem in newDict)
if (!oldDict.ContainsKey(newItem.Key) || oldDict[newItem.Key]!=newItem.Value)
usersAdded.Add(newItem.Key, newItem.Value);
foreach (var oldItem in oldDict)
if (!newDict.ContainsKey(oldItem.Key) || newDict[oldItem.Key]!=oldItem.Value)
usersRemoved.Add(oldItem.Key, oldItem.Value);
Finally this is my implementation of GetInvolvementLogging:
(the implementation of the string builder method is irrelevant for my question here)
private string GetInvolvementLogging(ICollection<UserInvolvement> newInvolvement, ICollection<UserInvolvement> oldInvolvement)
{
//I defined the new and old dictionary's to focus on the relevant data inside UserInvolvement.
var newDict = newInvolvement.ToDictionary(x => x.UserID, x => (Involvement)x.Involvement);
var oldDict = oldInvolvement.ToDictionary(x => x.UserID, x => (Involvement)x.Involvement);
var intersection = newDict.Keys.Intersect(oldDict.Keys); //These are the id's of the users that were and remain involved.
var usersAdded = newDict.Keys.Except(intersection);
var usersRemoved = oldDict.Keys.Except(intersection);
var addedInvolvement = newDict.Where(x => usersAdded.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
var removedInvolvement = oldDict.Where(x => usersRemoved.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
//Check if the already involved users have a changed involvement.
foreach(var userId in intersection)
{
var newInvolvementFlags = newDict[userId];
var oldInvolvementFlags = oldDict[userId];
if ((int)newInvolvementFlags != (int)oldInvolvementFlags)
{
var xor = newInvolvementFlags ^ oldInvolvementFlags;
var added = newInvolvementFlags & xor;
var removed = oldInvolvementFlags & xor;
if (added != 0)
{
addedInvolvement.Add(userId, added);
}
if (removed != 0)
{
removedInvolvement.Add(userId, removed);
}
}
}
return GetInvolvementLogging(addedInvolvement, removedInvolvement);
}

Entity Framework query improved

I'm trying to make an android synchronization between client and ASP.NET MVC server. The logic is simple, my next method receives a data dictionary, where key = idGroup and value = LastMessageIdKnown, in the end I should get the next messages for each group what Id is higher than the LastMessageIdKnown (the value of my dictionary).
Right now I am iterating the map, for each key I do a query to my SQL database but this is inefficient, if I got N keys you can imagine what implying.
This is my current method
public Dictionary<int, List<Messages>> SynchronizedChatMessages(Dictionary<int, int> data)
{
Dictionary<int, List<Messages>> result = new Dictionary<int, List<Messages>>();
foreach(int item in data.Keys){
var idMessage= data[item];
var listMessages= _context.Messages.Where(x => x.Grupo_ID == item && x.ID > idMessage).ToList();
result.Add(item,listMessages);
}
return result;
}
How can I improve this query to get all what I need in an only and optimal way?
Thank you.
Here's an attempt that uses Predicates to make it so that there is only one Where against the whole collection of messages.
Note that I mocked this up without a database, so I am passing a List into the SynchronizedChatMessages function, whereas you have the context available.
What remains to be proven is that this way of doing things only generates one query to the database (since I did it in objects only). The whole program is further, below, but first, just the function showing use of predicates to achieve firing the Where only once.
public static Dictionary<int, List<Message>> SynchronizedChatMessages(List<Message> messages, Dictionary<int, int> data)
{
List<Predicate<Message>> predList = new List<Predicate<Message>>();
//Built of list of indivIdual predicates
foreach (var x in data)
{
var IdMessage = x.Key;
var lastMessageId = x.Value;
Predicate<Message> pred = m => m.IdGroup.Id == IdMessage && m.Id > lastMessageId;
predList.Add(pred);
}
//compose the predicates
Predicate<Message> compositePredicate = m =>
{
bool ret = false;
foreach (var pred in predList)
{
//If any of the predicates is true, the composite predicate is true (OR)
if (pred.Invoke(m) == true) { ret = true; break; }
}
return ret;
};
//do the query
var messagesFound = messages.Where(m => compositePredicate.Invoke(m)).ToList();
//get the individual distinct IdGroupIds
var IdGroupIds = messagesFound.Select(x => x.IdGroup.Id).ToList().Distinct().ToList();
//Create dictionary to return
Dictionary<int, List<Message>> result = new Dictionary<int, List<Message>>();
foreach (int i in IdGroupIds)
{
result.Add(i, messagesFound.Where(m => m.IdGroup.Id == i).ToList());
}
return result;
}
Here is the whole thing:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication20
{
public class Program
{
public class Message
{
public int Id { get; set; }
public IdGroup IdGroup { get; set; }
}
public class IdGroup
{
public int Id { get; set; }
public List<Message> Messages { get; set; }
}
public static Dictionary<int, List<Message>> SynchronizedChatMessages(List<Message> messages, Dictionary<int, int> data)
{
List<Predicate<Message>> predList = new List<Predicate<Message>>();
//Built of list of indivIdual predicates
foreach (var x in data)
{
var IdMessage = x.Key;
var lastMessageId = x.Value;
Predicate<Message> pred = m => m.IdGroup.Id == IdMessage && m.Id > lastMessageId;
predList.Add(pred);
}
//compose the predicates
Predicate<Message> compositePredicate = m =>
{
bool ret = false;
foreach (var pred in predList)
{
//If any of the predicates is true, the composite predicate is true (OR)
if (pred.Invoke(m) == true) { ret = true; break; }
}
return ret;
};
//do the query
var messagesFound = messages.Where(m => compositePredicate.Invoke(m)).ToList();
//get the individual distinct IdGroupIds
var IdGroupIds = messagesFound.Select(x => x.IdGroup.Id).ToList().Distinct().ToList();
//Create dictionary to return
Dictionary<int, List<Message>> result = new Dictionary<int, List<Message>>();
foreach (int i in IdGroupIds)
{
result.Add(i, messagesFound.Where(m => m.IdGroup.Id == i).ToList());
}
return result;
}
public static void Main(string[] args)
{
var item1 = new IdGroup { Id = 2, Messages = new List<Message>() };
var item2 = new IdGroup { Id = 45, Messages = new List<Message>() };
var item3 = new IdGroup { Id = 36, Messages = new List<Message>() };
var item4 = new IdGroup { Id = 8, Messages = new List<Message>() };
var message1 = new Message { Id = 3, IdGroup = item1 };
var message2 = new Message { Id = 7, IdGroup = item1 };
var message3 = new Message { Id = 9, IdGroup = item1 };
item1.Messages.Add(message1);
item1.Messages.Add(message2);
item1.Messages.Add(message3);
var message4 = new Message { Id = 4, IdGroup = item2 };
var message5 = new Message { Id = 10, IdGroup = item2 };
var message6 = new Message { Id = 76, IdGroup = item2 };
item2.Messages.Add(message4);
item2.Messages.Add(message5);
item2.Messages.Add(message6);
var message7 = new Message { Id = 6, IdGroup = item3 };
var message8 = new Message { Id = 32, IdGroup = item3 };
item3.Messages.Add(message7);
item3.Messages.Add(message8);
var message9 = new Message { Id = 11, IdGroup = item4 };
var message10 = new Message { Id = 16, IdGroup = item4 };
var message11 = new Message { Id = 19, IdGroup = item4 };
var message12 = new Message { Id = 77, IdGroup = item4 };
item4.Messages.Add(message9);
item4.Messages.Add(message10);
item4.Messages.Add(message11);
item4.Messages.Add(message12);
List<IdGroup> items = new List<IdGroup> { item1, item2, item3, item4 };
List<Message> messages = new List<Message> { message1, message2, message3, message4, message5, message6,message7, message8, message9, message10, message11, message12};
Dictionary<int, int> lastMessagesPerItem = new Dictionary<int, int> { { 2, 3 }, { 45, 10 }, { 36, 6 }, { 8, 11 } };
var result = SynchronizedChatMessages(messages, lastMessagesPerItem);
var discard = Console.ReadKey();
}
}
}
Well it would be nice if this would work, but I doubt it that can be translated to a SQL statement in one go:
var toInsert =
from msg in _context.Messages
group msg by msg.Grupo_ID into g
where data.Keys.Contains(g.Key)
select new {
Item = g.Key,
Messages = g.Where(x => x.ID > data[g.Key])
};
I don't think the second Where clause x => x.ID > data[g.Key] can be translated.
So you may need to do this in two passes, like this:
// This is a single SQL query.
var groups =
from msg in _context.Messages
group msg by msg.Grupo_ID into g
where data.Keys.Contains(g.Key)
select new {
Item = g.Key,
// ordering helps us when we do the in-memory part.
Messages = g.OrderByDescending(x => x.ID).ToList()
};
// This iterates the result set in memory
foreach (var g in groups)
result.Add(
g.Item,
// input is ordered, we stop when an item is <= data[g.Item].
g.Messages.TakeWhile(m => m.ID > data[g.Item]).ToList())

Filtering and grouping items in a .NET List<>

List<MailingList> myGroup = lst.GroupBy(t => new {t.userId, t.userName,t.email,t.reportTypeId})
.Select(g => new MailingList
{
userId = g.Key.userId,
Acrynom = g.SelectMany(t => t.Acrynom).ToArray(),
userName = g.Key.userName,
email = g.Key.email,
reportTypeId = g.Key.reportTypeId
}).ToList();
foreach (var mailingList in myGroup.Distinct())
{
StringBuilder AcrynomsList1 = new StringBuilder();
foreach (var item in mailingList.Acrynom)
{
if (Acrynoms.Length > 0)
{
Acrynoms.Append(", ");
}
AcrynomsList1.Append(item);
}
}
What i want to achieve is filter and group myGroup by reportTypeId. reportTypeId can either be 1 or 2, so i want to have a variable StringBuilder AcrynomsList1 where reportTypeId = 1 and then another variable StringBuilder AcrynomsList2 where reportTypeId = 2.
My current StringBuilder varibale AcrynomsList1 has all reportTypeId 1 & 2 values.
This should give you what you want:
var list = lst.GroupBy(x => x.reportTypeId)
.Select(x => new
{
reportTypeId = x.Key,
Acronyms = x.SelectMany(t => t.Acrynom).ToArray()
}).ToList();
var acronymList1 = string.Join(", ", list[0].Acronyms);
var acronymList2 = string.Join(", ", list[1].Acronyms);
You can group it straight into a string without using a StringBuilder:
class Program
{
static void Main(string[] args)
{
var list = new List<MailingList>();
var grouped = list
.GroupBy(m => m.ReportTypeID)
.Select(g => new
{
ReportTypeID = g.Key,
Items = string.Join(", ", g.Where(s => !string.IsNullOrEmpty(s.Acronym)).Select(m => m.Acronym))
});
}
}
class MailingList
{
public int ReportTypeID { get; set; }
public string Acronym { get; set; }
}
The GroupBy extension method returns a number of enumerables of MailingList for you, which has a Key to expose the key you grouped by. The Distinct is part of the GroupBy, so you don't need it.

Categories