flatten a list using GroupBy [duplicate] - c#

Please be kind to a LINQ dummy...
Say I have 8 rows data like this:
+-------------------+
| ID WORKTYPEDESC|
+-------------------+
| 1 plumber |
| 1 carpenter |
| 1 electrician |
| 2 juggler |
| 2 mime |
| 3 writer |
| 3 actor |
+-------------------+
As an 8-item IList<Worktype> collection where Worktype looks like
public class Worktype
{
public int Id { get; set; }
public string WorktypeDesc { get; set; }
}
And what I want is to aggregate by Id to get a list with 3 Worktypes, each with a WorktypeDesc being a sorted, semicolon-delimited list, like this:
+-------------------------------------+
| ID WORKTYPEDESC |
+-------------------------------------+
| 1 carpenter;electrician;plumber |
| 2 juggler;mime |
| 3 actor;writer |
+-------------------------------------+
??

Just group by Id, use string.Join to aggregate within each group.
IEnumerable<Worktype> source = ...;
var query = source.GroupBy(
x => x.Id,
(id, g) => new Worktype
{
Id = id,
WorktypeDesc = string.Join(";", g.Select(x => x.WorktypeDesc).OrderBy(x => x))
});

Related

Column getting lost in LINQ with Method Syntax after group by

I'm pretty new to LINQ and trying to figure it out. I have the following statement:
Context.dataset1
.Join(
Context.dataset2,
r => r.ID, o => o.ID,
(r, o) => new { PartID = r.PartID, Quantity = r.Quantity1 - r.Quantity2, Date = o.Date })
.GroupBy(
column => new { column.Date },
(key, group) => new {Date = key.Date, Quantity = group.Sum(g => g.Quantity) })
.Where(x => x.Quantity > 0);
the return data set looks like this
| Date | Quantity |
| ------------- | ---------|
| 2022-01-01 | 333 |
| 2022-01-02 | 444 |
| 2022-03-03 | 444 |
what i want it to look like is
| PartID | Date | Quantity |
|--------| ------------- | ---------|
|1 | 2022-01-01 | 333 |
|1 | 2022-01-02 | 444 |
|2 | 2022-03-03 | 444 |
Basically it seems that when I do the groupby I lose access to the PartId column since i'm no specifying it inside the groupby. I'm not sure how to make it appear without grouping by it which I don't want to do.
Any help would be great. Thanks.
What if two different part ids exist for the same date? What part id would it show? If you really want the part id, then you need to include the part id in your group by. For example:
column => new { column.PartID, column.Date }
This will mean that if you have multiple part ids for the same date, you will have as many rows for that date as you have distinct part ids. Based on your comments, this seems like what you're after.

How to merge multiple list by id and get specific data?

i have 3 lists with common IDs. I need to group by object in one list, and extract data from other two. Will give example for more understanding
table for groupNames:
| Id | Name |
|--------------|
| 1 | Hello |
| 2 | Hello |
| 3 | Hey |
| 4 | Dude |
| 5 | Dude |
table for countId:
| Id | whatever |
|---------------|
| 1 | test0 |
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
| 3 | test4 |
table for lastTime:
| Id | timestamp |
|-----------------|
| 1 | 1636585230 |
| 1 | 1636585250 |
| 2 | 1636585240 |
| 3 | 1636585231 |
| 3 | 1636585230 |
| 5 | 1636585330 |
and I'm expecting result in list like this
| Name | whateverCnt | lastTimestamp |
|---------------------------------------|
| Hello | 3 | 1636585250 |
| Hey | 2 | 1636585231 |
| Dude | 0 | 1636585330 |
for now i had something like this, but it doesnt work
return groupNames
.GroupBy(x => x.Name)
.Select(x =>
{
return new myElem
{
Name = x.Name,
lastTimestamp = new DateTimeOffset(lastTime.Where(a => groupNames.Where(d => d.Name == x.Key).Select(d => d.Id).Contains(a.Id)).Max(m => m.timestamp)).ToUnixTimeMilliseconds(),
whateverCnt = countId.Where(q => (groupNames.Where(d => d.Name == x.Key).Select(d => d.Id)).ToList().Contains(q.Id)).Count()
};
})
.ToList();
Many thanks for any advice.
I think I'd mostly skip LINQ for this
class Thing{
public string Name {get;set;}
public int Count {get;set;}
public long LastTimestamp {get;set;}
}
...
var ids = new Dictionary<int, string>();
var result = new Dictionary<string, Thing>();
foreach(var g in groupNames) {
ids[g.Id] = g.Name;
result[g.Name] = new Whatever { Name = n };
}
foreach(var c in counts)
result[ids[c.Id]].Count++;
foreach(var l in lastTime){
var t = result[ids[l.Id]];
if(t.LastTimeStamp < l.Timestamp) t.LastTimeStamp = l.TimeStamp;
}
We start off making two dictionaries (you could ToDictionary this).. If groupNames is already a dictionary that maps id:name then you can skip making the ids dictionary and just use groupNames directly. This gives us fast lookup from ID to Name, but we actually want to colelct results into a name:something mapping, so we make one of those too. doing result[name] = thing always succeeds, even if we've seen name before. We could skip on some object creation with a ContainsKey check here if you want
Then all we need to do is enumerate our other N collections, building the result. The result we want is accessed from result[ids[some_id_value_here]] and it always exists if groupnames id space is complete (we will never have an id in the counts that we do not have in groupNames)
For counts, we don't care for any of the other data; just the presence of the id is enough to increment the count
For dates, it's a simple max algorithm of "if known max is less than new max make known max = new max". If you know your dates list is sorted ascending you can skip that if too..
In your example, the safest would be a list of the last specified object and just LINQ query the other arrays of objects for the same id.
So something like
public IEnumerable<SomeObject> MergeListsById(
IEnumerable<GroupNames> groupNames,
IEnumerable<CountId> countIds,
IEnumerable<LastTime> lastTimes)
{
IEnumerable<SomeObject> mergedList = new List<SomeObject>();
groupNames.ForEach(gn => {
mergedList.Add(new SomeObject {
Name = gn.Name,
whateverCnt = countIds.FirstOrDefault(ci => ci.Id == gn.Id)?.whatever,
lastTimeStamp = lastTimes.LastOrDefault(lt => lt.Id == gn.Id)?.timestamp
});
});
return mergedList;
}
Try it in a Fiddle or throwaway project and tweak it to your needs. A solution in pure LINQ is probably not desired here, for readability and maintainability sake.
And yes, as the comments say do carefully consider whether LINQ is your best option here. While it works, it does not always do better in performance than a "simple" foreach. LINQ's main selling point is and always has been short, one-line querying statements which maintain readability.
Well, having
List<(int id, string name)> groupNames = new List<(int id, string name)>() {
( 1, "Hello"),
( 2, "Hello"),
( 3, "Hey"),
( 4, "Dude"),
( 5, "Dude"),
};
List<(int id, string comments)> countId = new List<(int id, string comments)>() {
( 1 , "test0"),
( 1 , "test1"),
( 2 , "test2"),
( 3 , "test3"),
( 3 , "test4"),
};
List<(int id, int time)> lastTime = new List<(int id, int time)>() {
( 1 , 1636585230 ),
( 1 , 1636585250 ),
( 2 , 1636585240 ),
( 3 , 1636585231 ),
( 3 , 1636585230 ),
( 5 , 1636585330 ),
};
you can, technically, use the Linq below:
var result = groupNames
.GroupBy(item => item.name, item => item.id)
.Select(group => (Name : group.Key,
whateverCnt : group
.Sum(id => countId.Count(item => item.id == id)),
lastTimestamp : lastTime
.Where(item => group.Any(g => g == item.id))
.Max(item => item.time)));
Let's have a look:
Console.Write(string.Join(Environment.NewLine, result));
Outcome:
(Hello, 3, 1636585250)
(Hey, 2, 1636585231)
(Dude, 0, 1636585330)
But be careful: List<T> (I mean countId and lastTime) are not efficient data structures here. In the Linq query we have to scan them in order to get Sum and Max. If countId and lastTime are long, turn them (by grouping) into Dictionary<int, T> with id being Key

How do I find a row that matches all fields

I have a model
public void CarModel()
{
public string Name {get; set;}
public string Brand {get; set;}
public string Color {get; set;}
}
And I have a database table called Vehicles
id | Name | Brand | Color | ...
---+---------+----------+--------+--------
1 | carA | A | Green |
2 | carB | C | Red |
3 | carC | D | Orange |
4 | carA | C | White |
And then I wanted to find this one specifically
---+---------+----------+--------+--------
3 | carC | D | Orange |
Without knowing the ID just by knowing the Name, Brand & Color.
I want to fetch all the entries if that's the correct term using SQL syntax.
Here is solution with Linq syntax:
var vehicles = _db.Vehicles.Where(r=>r.Name=="carC" && r.Brand="D" && r.Color=="Orange").ToList();
You can try below -
select * from Vehicles
where name='carC' and brand='D' and color='Orange'

Group by in linq for email alert

I have a class:
public class CustomerEmailAlert
{
public string EmailId { get; set; }
public string Interest1 {get;set;}
public string Interest2 {get;set;}
}
The result from sql is something like this:
+---------------+-----------+-----------+
| Email | Interest1 | Interest2 |
+---------------+-----------+-----------+
| abc#gmail.com | Burrito | Apple |
| abc#gmail.com | Pizza | Milk |
| abc#gmail.com | Apple | Burrito |
| def#gmail.com | Milk | Banana |
+---------------+-----------+-----------+
I have mapped the result using Dapper to List<CustomerEmailAlert>
List<CustomerEmailAlert>= con.Query<CustomerEmailAlert>("getalerts", commandType: CommandType.StoredProcedure).ToList();
My question is: How do I group Customers by Email so that the email they receive contains their interests (they should receive only 1 copy of email)
I have tried this: Group by in LINQ
You could group this way:
var result= from e in list
group new {e.Interest1,e.Interest2} by e.Email into g
select new {Email=g.Key, Interests=g.ToList()};

LINQ grouping by nullable child and parent

everyone!
I've just faced a problem with timing out in my LINQ query.
I have 3 tables: Work, Projects and Subprojects.
Projects:
+--------+
| Id |<--+<--+
| Name | | |
+--------+ | |
SubProjects: | |
+--------+ | |
+->| Id | | |
| | Name | | |
| | ProjId |---+ |
| +--------+ |
| Work: |
| +------------+ |
| | Id | |
| | Type | |
| | ProjId |---+
+--| SubProjId | (nullable)
+------------+
I need to create a report based on Subprojects:
Group by subproject Id,
if subproject Id is null -> group by project Id
I've solved it by making two queries and then merging them, but when sometimes it times out.
I was doing it with
result1.AddRange(result2);
because
var temp = result1.Concat(result2);
is throwing an Exception:
Internal .NET Framework Data Provider error 1004, 0, Unresolvable Var used in Command: VarType=Computed, Id=2090.
Can somebody help me with creating it in one query?
I'm not sure what your code looks like so this might not be perfect but you could try something like this:
var result = from work in works
group work by work.SubProjId ?? work.ProjId into groupedWorks
select groupedWorks.ToList();
or
var result = works.GroupBy(work => work.SubProjId ?? work.ProjId).ToList();
try this query
var itemlist =contex.Work.where(x=>x.SubProjId !=null).Groupby(x=>x.SubProjId).Concat(Contex.Work.where(x=>x.SubProjId ==null).Groupby(x=>x.ProjId)).ToList();
I'm guessing this is what you need:
var groups = from work in ctx.Works // the work table
group work // we want to group whole work "rows"
// we are grouping by project id and subproject id
by new { ProjId = work.ProjId, SubProjId = work.SubProjId }
into g // and we are calling the grouping 'g'
select g; // select the group
// example of doing something with the groupings
foreach (var group in groups)
{
var key = group.Key; // gets a { ProjId, SubProjId } tuple
foreach (var work in group)
{
// each work is a row in the Work-table
}
}

Categories

Resources