GroupBy in lambda expressions - c#

from x in myCollection
group x by x.Id into y
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
How would you write the above as a lambda expression? I'm stuck on the group into part.

Query continuations (select...into and group...into, but not join...into) are equivalent to just splitting up the query expression. So I like to think of your example as:
var tmp = from x in myCollection
group x by x.Id;
var result = from y in tmp
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
Change those into dot notation:
var tmp = myCollection.GroupBy(x => x.Id);
var result = tmp.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Then you could combine them back:
var tmp = myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Once you work out what the C# compiler does with query expressions, the rest is relatively straightforward :)

myCollection
.GroupBy(x => x.Id)
.Select(x =>
new
{
Id = x.Key,
Quantity = x.Sum(y => x.Quantity
});

myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});

var mostFrequent =
lstIn.Where(i => !string.IsNullOrEmpty(i))
.GroupBy(s => s)
.OrderByDescending(g => g.Count())
.Select(s => s.Key)
.FirstOrDefault();

So, for most of the answers here, everyone seems to be dealing with getting a simple object of Id made from count of the group, and the Key itself which is group.Key.
Although thats probably the main useage of this. Didn't really satisfy my needs.
For my own case, I basically wanted to group by some object property, then fetch a specific object from that group. Here's a sample code.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var response = new List<ResponseClass>();
var listOfStudents = new List<Student>();
// Insert some objects into listOfStudents object.
listOfStudents.GroupBy(g => g.Class).ToList()
.ForEach(g => response.Add(g.OrderByDescending(s => s.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
})
.First()));
Console.WriteLine("This compiles and should work just fine");
}
class Student
{
public string StudentName { get; set; }
public int Age { get; set; }
public string Class { get; set; }
public DateTime CreatedOn { get; set; }
}
class ResponseClass
{
public string SName { get; set; }
public int SAge { get; set; }
public string SClass { get; set; }
public DateTime SCreatedOn { get; set; }
public string RandomProperty { get; set; }
}
}
If you would rather use a foreach loop (I prefer lambda as I find it easier to read), but if you do, you could do it like so.
foreach (IGrouping<string, Student> groupedStudents in listOfStudents.GroupBy(g => g.Class))
{
response.Add(groupedStudents.OrderByDescending(x => x.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
}).First());
}
Hope this helps someone. :)

Related

Display count of task for each id

I have page where i display list of tasks (with Jquery DataTable) and i want display Employee name with count of thire tasks. for example:
Smith (2) | John (1) | Thomas (1)
Tables:
AssignName:
Tasks (AssignId as ForeignKey):
Here what i did :
Here i get List of tasks and select some properties:
var Taskslist = db.Tasks.Select(g => new ServicetasksVM.ItemGroup
{
OpgaveServicesId = g.Id,
Opgaver = g.Opgaver,
Opgaveid = g.Id,
Opretteaf = g.Opretteaf,
OpretteDato = g.OpretteDato,
}).AsEnumerable();
Her iterating over the results to get count each id:
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
}
Put all code together (for simplicity i remove unneeded code):
public JsonResult ListofTasks() {
var Taskslist = db.Tasks.Select(g => new ServicetasksVM.ItemGroup
{
OpgaveServicesId = g.Id,
Opgaver = g.Opgaver,
Opgaveid = g.Id,
Opretteaf = g.Opretteaf,
OpretteDato = g.OpretteDato,
}).AsEnumerable();
var result = Taskslist.Skip(start).Take(length).ToList();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
}
JsonResult json = Json(new { data = result, draw = Request["draw"], recordsTotal = totalrows, recordsFiltered = totalrowsefterfiltering }, JsonRequestBehavior.AllowGet);
json.MaxJsonLength = int.MaxValue;
return json;
ViewModel:
public List<ItemGroup> ItemGroups { get; set; }
public class ItemGroup
{
public ItemGroup()
{
}
public int OpgaveServicesId { get; set; }
public string Opgaver { get; set; }
public string Opretteaf { get; set; }
public DateTime? OpretteDato { get; set; }
public int Opgaveid { get; set; }
public int OpgaveSmartid { get; set; }
public string Opgavestatus { get; set; }
public int? Assingid { get; set; }
public string EmployeeNames { get; set; }
}
In the end when I check the results both Assingid and EmployeeNames are null. Can anyone please help me :)
Scope of AssignCount variable is limited to foreach loop, that is the reason you are getting Assingid and EmployeeNames as a null at the end of program.
To fix this issue, store each AssignCount to the list, so that you can access all counts out of foreach loop
...
//Define list of List of ItemGroup, as AssignCount is of type List<ItemGroup>
List<List<ServicetasksVM.ItemGroup>> itemGroups = new List<List<ServicetasksVM.ItemGroup>>();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key })
.ToList(); //Convert To List
itemGroups.Add(AssignCount); //Add each `AssignCount` to itemGroups
}
//Now list of AssignCount(s) is(are) accessible outside foreach loop
...
Your for loop is doing the counting and assigning it to the variable AssignCount which only exists in the scope of the for loop. You need to capture that result in a variable that is outside the scope of the loop to pass that along to your view.
Something like:
var itemGroups = new List<ItemGroup>();
foreach (var item in result)
{
var AssignCount = db.AssignName.Where(c => c.Id == item.Assingid)
.GroupBy(x => x.Name)
.Select(b => new ServicetasksVM.ItemGroup { Assingid = b.Count(), EmployeeNames= b.Key });
itemGroups.Add(AssignCount);
}
variable AssignCount is local in the foreach loop. better create collection outside of the loop and keep on adding to it inside the loop.

C# LINQ : Summarise specific inner class properties from outer class collection

Drawing on Loop Through An Objects Properties In C# and Using LINQ to loop through inner class properties in outer class collection
Where you have objects (Phase) in a collection (PhaseRepo), I believe it is possible to specify propertiesOfInterest in the objects (Phase) and create a Dictionary to summarise the properties.
Please find below my attempt in LinqPad. Please assist with the syntax or advise an alternate approach.
Thank you
enum Dir {Up, Dn}
struct BmkKey
{
public Dir Direction;
public string DetailType;
}
class Phase
{
public Dir Direction { get; set; }
public double StartToTerminationBars { get; set; }
public double StartToTerminationPriceChange { get; set; }
public double StartToTerminationGa { get; set; }
public double NotRequiredProperty { get; set; }
}
class PhaseRepo
{
public List<Phase> Phases { get; private set; }
public List<Phase> GetPhases()
{
return new List<Phase>()
{
new Phase() { Direction = Dir.Up, StartToTerminationBars = 3.0, StartToTerminationPriceChange = 4.0, StartToTerminationGa = 4.0},
new Phase() { Direction = Dir.Up, StartToTerminationBars = 6.0, StartToTerminationPriceChange = 8.0, StartToTerminationGa = 4.0},
new Phase() { Direction = Dir.Dn, StartToTerminationBars = 3.0, StartToTerminationPriceChange = -4.0, StartToTerminationGa = -4.0},
new Phase() { Direction = Dir.Dn, StartToTerminationBars = 6.0, StartToTerminationPriceChange = -8.0, StartToTerminationGa = -4.0},
};
}
}
void Main()
{
var phaseRepo = new PhaseRepo();
var phases = phaseRepo.GetPhases();
//phases.Dump();
var propertiesOfInterest = typeof (Phase).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.Name == "StartToTerminationBars"
|| prop.Name == "StartToTerminationPriceChange"
|| prop.Name == "StartToTerminationGa")
.ToList();
//propertiesOfInterest.Dump();
// Please Help...
var test = propertiesOfInterest
.SelectMany(propertyInfo => phases
.Select(phase => phase)
.Select(keyValuePair => new
{
phase.Direction,
keyValuePair.Key,
keyValuePair.Value
})
.Select(arg => new
{
Key = new BmkKey
{
Direction,
DetailType = propertyInfo.Name
},
Value = (double)propertyInfo.GetValue(arg.Value, null)
}))
.GroupBy(grp => grp.Key)
.ToDictionary(grp => grp.Key, grp => x => x.ToList());
test.Dump();
}
Expected output:
Solution
Replace the part following
//Please Help...
with the following:
var test = propertiesOfInterest
.SelectMany(propertyInfo => phases
.Select(phase => new
{
phase.Direction,
propertyInfo.Name,
Value = (double)propertyInfo.GetValue(phase)
}))
.GroupBy(o => new BmkKey { Direction = o.Direction, DetailType = o.Name })
.ToDictionary(grp => grp.Key, grp => grp.Select(x => x.Value));
This essentially gives the expected output shown in your question. (The order is different; adjust using OrderBy() as your needs require.)
Explanation
There were a few issues in your code, but the primary problems were:
The call to GetValue() invoked on the wrong object
The key selector in the .ToDictionary() call.
In addition, the following are not wrong, but unnecessary:
The .Select(phase => phase) call, which does nothing--like a line of code specifying x = x;
Building the key before grouping. Instead, you can simply define the key during the .GroupBy() operation.

Return an new object from join using EntityFramework

I am a little bit stuck here.
I got a Database with tables for projects and versions of these projects:
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public List<ProjectVersion> Versions { get; set; } = new List<ProjectVersion>();
public Project() { }
}
and
public class ProjectVersion
{
public int Id { get; set; }
public string Version { get; set; }
public string Checksum { get; set; }
public string Description { get; set; }
public ICollection<EntryPoint> EntryPoints { get; set; } = new List<EntryPoint>();
public ICollection<System> Systems { get; set; } = new List<System>();
public ProjectVersion() { }
}
now I want to get a specific version of a project and some detailed information
public static Project GetVersionByProjectId( int projectId, string version )
{
using ( var ctx = new DatabaseContext() )
{
var query =
ctx.Projects
.Where(p => p.Id.Equals(projectId))
.Join(
ctx.Versions.Where( v => v.Version.Equals( version )),
p => p.Id,
v => v.ProjectId,
(p, v) => new Project
{
Name = p.Name,
Type = p.Type,
Id = p.Id,
Versions = new List<ProjectVersion>
{
new ProjectVersion
{
Checksum = v.Checksum,
Description = v.Description,
Version = v.Version ,
EntryPoints = new List<EntryPoint>(v.EntryPoints),
Systems = new List<System>(v.Systems)
}
}
}
)
.Select(x => x);
var result = query.ToList();
return result[0];
}
}
if I remove the whole
Versions = new List<ProjectVersion>
it works and I get the Project but not the Version. When I tried the LINQ in LINQPad I got following error:
Cannot create a query result of type 'System.Collections.Generic.List`1[UserQuery+ProjectVersion]'.
How can I get the Project with the requested Version?
UPDATE
thanks to the ideas of #RomanoZumbé and #Maritim I could solve it. Problem was different classes of the models.
using ( var ctx = new DatabaseContext() )
{
var query =
ctx.Projects
.Include(p => p.Versions)
.Where(p => p.Id.Equals(projectId))
.Select( p =>
new Project()
{
Id = p.Id,
Name = p.Name,
Type = p.Type,
Versions =
p.Versions
.Where( v => v.Version.Equals(version))
.Select( v =>
new ProjectVersion()
{
Checksum = v.Checksum,
Description = v.Description,
EntryPoints =
v.EntryPoints
.Select( e => new EntryPoint()
{
Call = e.Call,
Step = e.Step
})
.ToList()
})
.ToList()
})
.Select(x => x);
var result = query.ToList();
return result[0];
}
I don't know if I understood it correctly, and this is from top of my mind, but I think it would be easier if you used the Include(...) statement. And since you already included the project 'Versions' (which is equivalent to a JOIN statement in the database), you can do something like:
var query =
ctx.Projects
.Include(p => p.Versions)
.Where(p => p.Id.Equals(projectId))
.Select(
new Project
{
Name = p.Name,
Type = p.Type,
Id = p.Id,
Versions = p.Versions.Where(v => v.Version.Equals(version))
});
return query.FirstOrDefault();
I'm not entirely sure wether this is what you want to achieve, but maybe it helps.
var res = (from p in ctx.Projects
let v = ctx.Versions.FirstOrDefault(x => p.Versions.Any(v => v.Id == x.Id))
where p.Id == projectId
select new Project
{
Name = p.Name,
Type = p.Type,
Id = p.Id,
Versions = new List<ProjectVersion>()
{
new ProjectVersion
{
Checksum = v.Checksum,
Description = v.Description,
Version = v.Version ,
EntryPoints = new List<EntryPoint>(v.EntryPoints),
Systems = new List<System>(v.Systems)
}
}
});
return res.FirstOrDefault()

Is there a way to avoid repetition in linq projections that depend on expressions?

So example program:
class Cat
{
public bool IsMale { get; set;}
public int TailLength { get; set; }
public string Name { get; set; }
public decimal ClawAttackFrequency { get; set; }
public List<DateTime> FeedingTimes { get; set; }
}
static void Main(string[] args)
{
var cats = new List<Cat>();
var someData = cats
.Select(x => new
{
x.Name,
x.IsMale,
LatestFeedingTime = x.FeedingTimes.Max(y => (DateTime?)y)
})
.Select(x => new
{
x.Name,
x.IsMale,
x.LatestFeedingTime,
WasFedRecently = x.LatestFeedingTime.HasValue && x.LatestFeedingTime.Value >= DateTime.Today.AddDays(-1)
});
}
This is just a toy program that does nothing but it illustrates my problem which is that in order to avoid having to duplicate the expression for LatestFeedingTime I have to instead make two projections and duplicate a bunch of properties.
The alternative would have been:
var someData = cats
.Select(x => new
{
x.Name,
x.IsMale,
LatestFeedingTime = x.FeedingTimes.Max(y => (DateTime?)y),
WasFedRecently = (x.FeedingTimes.Max(y => (DateTime?)y)).HasValue && (x.FeedingTimes.Max(y => (DateTime?)y)).Value >= DateTime.Today.AddDays(-1)
});
But this duplicates a tricky expression instead.
Is there any way to get the best of both worlds in c#. Like a way to declare an expression and then use in twice in the same projection?
I would look at doing it this way:
var someData =
from x in cats
let LatestFeedingTime = x.FeedingTimes.Max(y => (DateTime?)y)
select new
{
x.Name,
x.IsMale,
LatestFeedingTime,
WasFedRecently =
LatestFeedingTime.HasValue
&& LatestFeedingTime.Value >= DateTime.Today.AddDays(-1)
};
It certainly eliminates the duplication in your code, but under the hood it is still generating the same code.
You can even shorten it to this:
var someData =
from x in cats
let LatestFeedingTime = x.FeedingTimes.Max(y => (DateTime?)y)
select new
{
x.Name,
x.IsMale,
LatestFeedingTime,
WasFedRecently = LatestFeedingTime >= DateTime.Today.AddDays(-1)
}
You can simply define a Build method returning what you wish
var someData = cats
.Select (Cat.BuildData);
And inside your
public static Something BuildData (Cat cat) {
you can use any local variable...
Just use a statement lambda where you can use a local variable
var someCats = cats.Select(x =>
{
var lft = x.FeedingTimes.Max(y => (DateTime?)y);
return new
{
x.Name,
x.IsMale,
LatestFeedingTime = lft,
WasFedRecently = lft >= DateTime.Today.AddDays(-1)
};
});
But I feel sorry for your cat if you consider it being fed recently as potentially being fed in the last 48 hrs!

How to create result set from Linq with enumerable types?

The result set below:
ServiceName Ping Desc LogName BaseUrl EnvName
IntegrationServices.BillingInstructionsService /IntegrationServices/BillingInstructionsService.svc/Rest/Ping BillingInstructionsService IntegrationServices.BillingInstructionsServices https://icrDev.xxx.com Dev
IntegrationServices.BillingInstructionsService /IntegrationServices/BillingInstructionsService.svc/Rest/Ping BillingInstructionsService IntegrationServices.BillingInstructionsServices https://IUTD01.xxx.com DevUnitTest
IntegrationServices.BillingInstructionsService /IntegrationServices/BillingInstructionsService.svc/Rest/Ping BillingInstructionsService IntegrationServices.BillingInstructionsServices https://ickd01.xxx.com DevClock
IntegrationServices.BillingInstructionsService /IntegrationServices/BillingInstructionsService.svc/Rest/Ping BillingInstructionsService IntegrationServices.BillingInstructionsServices https://icd01.xxx.com DevConv
is returned from the linq query below my needs can be either filtered(ServiceId) or unfiltered ...:
var data = contextObj.ServiceMonitorMappings
.Where(r => r.ServiceId == 33)
.Select(x => new
{
ServiceName = x.Service.Name,
Ping = x.Service.PingUrl,
Desc = x.Service.Description,
LogName = x.ServiceLoggingName.LoggingName,
BaseUrl = x.ServiceBaseUrl.ServiceBaseUrl1,
EnvName = x.ServiceEnvironment.Name
});
ServiceMonitorMapping looks like this:
public partial class ServiceMonitorMapping
{
public int Id { get; set; }
public int ServiceEnvironmentId { get; set; }
public int ServiceId { get; set; }
public int ServiceLoggingNameId { get; set; }
public int ServiceBaseUrlId { get; set; }
public virtual Service Service { get; set; }
public virtual ServiceLoggingName ServiceLoggingName { get; set; }
public virtual ServiceBaseUrl ServiceBaseUrl { get; set; }
public virtual ServiceEnvironment ServiceEnvironment { get; set; }
}
I was trying to get BaseUrl and EnvName to return as an enumerable collection so that I wouldn't have 4 records but 1 with the last 2 columns containing a list of BaseUrl and EnvName, however I cannot find a way to do this. So I am stuck with 4 records as opposed to 1. Does not seem to be ideal to me.
So my question is this, is it possible to return just 1 row with the last 2 columns being a collection so that I have "item item item item List<> List<>"?
Any help would be appreciated.
var data = contextObj.ServiceMonitorMappings
.Where(r => r.ServiceId == 33)
.Select(x => new
{
Key = new {ServiceName = x.Service.Name,
Ping = x.Service.PingUrl,
Desc = x.Service.Description,
LogName = x.ServiceLoggingName.LoggingName};
BaseUrl = x.ServiceBaseUrl.ServiceBaseUrl1,
EnvName = x.ServiceEnvironment.Name
})
.GroupBy(x => x.Key)
.Select(g => new
{
ServiceName = g.Key.ServiceName,
Ping = g.Key.Ping,
Desc = g.Key.Desc,
LogName = g.Key.LogName,
BaseUrls = g.Select(x => x.BaseUrl).ToList(),
EnvNames = g.Select(x => x.EnvName ).ToList();
})
Although tolanj's answer is rather good, I believe it is not the easiest approach. You could use just Select after grouping to simplify your query:
var data = contextObj.ServiceMonitorMappings
.GroupBy(r => r.ServiceId)
.Where(r => r.Key == 33)
.Select(x => new
{
ServiceName = x.First().Service.Name,
Ping = x.First().Service.PingUrl,
Desc = x.First().Service.Description,
LogName = x.First().ServiceLoggingName.LoggingName,
BaseUrl = x.Select(y => y.ServiceBaseUrl.ServiceBaseUrl1).ToList(), //ToList is optional
EnvName = x.Select(y => y.ServiceEnvironment.Name).ToList() //ToList is optional
});
At first, I didn't notice that this is a DB query through LINQ to SQL. In order to use this approach and not loose the performance due to downloading whole table you can try it like this:
var data = contextObj.ServiceMonitorMappings
.Where(r => r.ServiceId == 33)
.AsEnumerable() //AsEnumerable after Where to apply filter on the DB query
.GroupBy(x => 1) //data already filtered, only one group as a result
.Select(x => new
{
ServiceName = x.First().Service.Name,
Ping = x.First().Service.PingUrl,
Desc = x.First().Service.Description,
LogName = x.First().ServiceLoggingName.LoggingName,
BaseUrl = x.Select(y => y.ServiceBaseUrl.ServiceBaseUrl1).ToList(), //ToList is optional
EnvName = x.Select(y => y.ServiceEnvironment.Name).ToList() //ToList is optional
});
I hope that you understand your needs; I solved with this way:
var query = contextObj.ServiceMonitorMappings
.Where(r => r.ServiceId == 33)
.Select(d => new {
BaseUrl = d.ServiceBaseUrl.ServiceBaseUrl1,
EnvName = d.ServiceEnvironment.Name})
.Aggregate((d1, d2) =>
new {
BaseUrl = d1.BaseUrl + ", " + d2.BaseUrl,
EnvName = d1.EnvName + ", " + d2.EnvName
});
This is the result:
BaseUrl
https://icrDev.xxx.com, https://IUTD01.xxx.com, https://ickd01.xxx.co, https://icd01.xxx.com
EnvName
Dev, DevUnitTest, DevClock, DevConv
EDIT:
I've changed my query to return your needed result:
var query = contextObj.ServiceMonitorMappings
.Where(r => r.ServiceId == 33)
.Select(d => new {
BaseUrl = d.ServiceBaseUrl.ServiceBaseUrl1,
EnvName = d.ServiceEnvironment.Name})
.Aggregate(
//initialize the accumulator
new { BaseUrl = new List<string>(), EnvName = new List<string>() },
(acc, next) => //accumulator and nextItem
{
acc.BaseUrl.Add(next.BaseUrl);
acc.EnvName.Add(next.EnvName);
return acc;
});

Categories