grouping and counting in linq-to-sql - c#

I have the following query that receives a list of IDs and I want to do a count. There's also an object model CountModel that holds the counts with each property defined as an int.
public class GetCountByStatus(List<int> TheIDs)
{
...using MyDC...
var CountData = (from d in MyDC.Data
where TheIDs.Contains(d.ID)
group d by d.Status into statusgroup
select new CountModel()
{
CountStatus1 = (from g in statusgroup
where g.Status == 1
select g).Count(),
CountStatus2 = (from g in statusgroup
where g.Status == 2
select g).Count(),
CountStatusN = ....
}).Single();
If for instance there are no elements with status N, will this code crash or will the count be 0 for CountStatusN ? Is this the best way to do what I want?
Thanks.

I would go for a dictionary instead, try something like this:
var countData = MyDC.Data.Where(y => TheIDs.Contains(y.ID))
.GroupBy(y => y.Status).ToDictionary(y => y.Key, y => y.Count());
I haven't tried it my self and not written the code in VS, but I think that is almost how you do can do it. That will give you a dictionary where the key is the status and the value is the count of that status.
Defining a model with properties named SomethingX is not very flexible. That means you have to go in an change the model when there is a new status. Keeping the data in the dictionary instead will save you from that.

Count() will always return an integer, which is 0 if there are no elements with the given status. Therefore CountStatusN will always be an integer as well.

Related

Partition By Logic in Code to calculate value of a DataTable Column

I'm using the following SQL for calculating the value of a column named weight within a view.
I need to move this calculation logic to code.
CASE
WHEN SUM(BaseVal) OVER (PARTITION BY TEMPS.MandateCode) = 0 THEN 0
ELSE (BaseVal / (SUM(BaseVal) OVER (PARTITION BY TEMPS.MandateCode))) END AS [Weight]
Is iterating over each and grouping by MandateCode a good idea
var datatableenum = datatable.AsEnumerable();
foreach(var item in datatableenum)
{
List<DataTable> result = datatable.AsEnumerable()
.GroupBy(row => row.Field<int>("MandateCode"))
.Select(g => g.CopyToDataTable())
.ToList();
}
I'm going to say "no" because as you have it, it will perform the group operation for every mandate code, for each row then copy then to list, which adds up to a huge amount of burnt resources.. I would make a dictionary of mandatecode=>sum first and then use it when iterating the table
var d = datatable.AsEnumerable()
.GroupBy(
row => row.Field<int>("MandateCode"),
row => row.Field<double>("BaseVal")
).ToDictionary(g => g.Key, g => g.Sum());
Note I've no idea what type BaseVal is; you need to adjust this. If it's an integer remember that you'll be doing a calc of small_int/big_int eg 12/6152, which is always 0 so cast one of the operandi to eg double so the result will be like 0.1234
Then use the dictionary on each row
foreach(var item in datatableenum)
{
int sumbv = d[item.Field<int>("MandateCode"));
item["Weight"] = sumbv == 0 ? 0 : item.Field<double>("BaseVal") / sumbv;
}

Group in Linq with Max: How do I get the full data set of the max value?

what I have got so far is this:
var a = from e in tcdb.timeclockevent
group e by e.workerId into r
select new { workerId = r.Key, Date = r.Max(d => d.timestamp) };
This Query is giving me latest "timestamp" of every workerId (Note: workerId is not the primary key of tcdb.timeclockevent). So it is only giving me pairs of two values but I need the whole data sets
Does anybody know how I can get the whole datasets of tcdb.timeclock with the maximal timestamp for every workerId?
OR
Does anybody know how I can get the Id of the data sets of the maximal date for each worker?
Thank you in advance :)
You can order your r grouping by timestamp and select the first one
var a = from e in tcdb.timeclockevent
group e by e.workerId into r
select r.OrderByDescending(d => d.timestamp).FirstOrDefault();
Does anybody know how I can get the whole datasets of tcdb.timeclock with the maximal timestamp for every workerId?
Well, the straightforward query would be like this:
var queryA =
from e in tcdb.timeclockevent
group e by e.workerId into g
let maxDate = g.Max(e => e.timestamp)
select new { workerId = g.Key, events = g.Where(e => e.timestamp == maxDate) };
If you don't need IQueryable<T> result and since there is no SQL construct that returns directly the grouped result set, you could try the following query, which uses a different way of filtering the records with maximal timestamp for every workerId inside the database, and then does the grouping in memory:
var queryB = tcdb.timeclockevent
.Where(e => !tcdb.timeclockevent.Any(e2 =>
e2.workerId == e.workerId && e2.timestamp > e.timestamp))
.AsEnumerable()
.GroupBy(e => e.workerId);
You can try and see which one performs better with your data.

Any value in the list - in LINQ

I need to pull all values where the request type is any of the ones I have on the list.
from v in ctx.vEmailSents
where v.RequestType_ID == reqTypeID
group v by v.SentToLab_ID into g
select g.OrderByDescending(x => x.DateSent).FirstOrDefault() into lastV
select new
{
ClaimID = lastV.Claim_ID,
};
reqTypeID is of type List<int>.
How can I use it in Linq to get all records that are in that list?
You can do something like this:
where requestTypes.Contains(v.RequestType_ID)
requestTypes would be the list you talked about.

Order by clauses coming from two other tables

I have a table RecentChanges which holds reference to all actions done on my website. It contains the Type of the action done (e.g. edit, upload, etc.), a Revision ID foreign key (which references to the ID in the Revisions table - can be null) and a Log ID foreign key (which references to the ID in the Logs table - can be null). Both the last two tables mentioned contain a timestamp of when the action occurred.
My problem is how can I order the results from the RecentChanges table to be displayed in descending order since the timestamp values are in two separate tables?
To get an idea here is a snippet of Linq query:
var list = (from r in Entity.RecentChanges
//where clause??
select r);
Something like this? I'm projecting into an anoymous type to help with the ordering (you'll need to add other fields that you need into the projection):
var query = (from c in Entity.RecentChanges
from l in Entity.Logs
.FirstOrDefault(log => log.ID == c.LogID).DefaultIfEmpty()
from r in Entity.Revisions
.FirstOrDefault(rev => rev.ID == c.RevisionID).DefaultIfEmpty()
select new
{
ActivityDate = l != null
? l.Timestamp
: r != null
? r.Timestamp
: DateTime.Now. //what if both are null? catching all eventualities here
//select your other fields here
})
.OrderByDescending(c => c.ActivityDate)
.ToList();

Help with LINQ query

I currently a list of a Supplier class, within that supplier class is a list of orders.
Each order has a userID and an empty string variable for username.
I then have a list of users which contains userID and username.
The way I am doing this now is:
foreach(supplier s in SupplierList)
{
foreach (order o in s.childorders)
{
user u = _users.First(p => p.userid == o.userid);
o.username = u.username;
}
}
I feel this might be a little inefficient and I was wondering if it is possible to compact it down into one linq query?
The logic should be
set supplierslist.childorders.username to the value in _users where supplierslist.childorders.userid == _users.userid.
Im fairly new to Linq so any advice for this would be apreciated, or also if its a bad idea and to leave it as it is / reasons why would be good too.
Thanks
What you want to do here is iterate over a collection (many collections, really, but it doesn't make a difference) and mutate its members. LINQ is not really targeted at performing mutating operations but rather at querying. You can do it with LINQ, but it's against the spirit of the tool.
If you are constructing the SupplierList yourself, it might be possible to fetch the data appropriately with LINQ so that it comes pre-populated as you want it to be.
Otherwise, I 'd leave the foreach as it is. You can make a dictionary that maps ids to users to make the inner loop faster, but that's your call and it depends on your data size.
var orderUserPairs = SupplierList
.SelectMany(s => s.ChildOrders)
.Join(_users, o => o.UserId, u => u.userId, (Order, User) => new {Order, User});
foreach (var orderUserPair in orderUserPairs)
orderUserPair.Order.username = orderUserPair.User.username;
Though having both username and userId as part of order looks suspicious.
First a question...
It looks like you are operating on every order. Why do you need to cycle through the supplierlist first since you don't seem to be using it inside the loop? Unless there are orders that don't belong to any supplierlist, you might be able to skip that step.
If that isn't the case, then I think you can use a join. If you aren't familiar with the syntax for joins in linq, this is one (simplified) way to approach it:
var x = from S in SupplierList
join C in childorders on C.supplierlistID equals S.ID
where [whatever you need here if anything]
select new { field1, field2};
foreach var y in x
{
}
Note I assumed a foreign key in childorders to supplierlist. If that isn't the case you will have to modify accordingly.
Hope that helps.
You need to use SelectMany or join depending on weather you are using linq-to-sql or linq with local collections. If you are using local collections the better way is to use join, else use SelectMany.
Like this...join:
var selection = (from s in SupplierList
join o in s.childholders on s.userid equals o.userid
select new { username = o.username);
or, in case of linq-to-sql:
var selection = (from s in SupplierList
from o in s.childholders
select { username = o.username);
You can then use the anonymous type you projected the way you want.
I agree with Jon, but you could say:
var orders = (from s in supplier
from o in s.childorders
select new
{
Order = o,
User = _users.First(p => p.userid == o.userid)
}).ToList();
foreach(var order in orders) {
order.Order.username = order.User.username;
}
Untested of course :)
If users list contains many elements, it can be really slow so I'd use a temporary dictionary:
var userById = users.GroupBy(x => x.userid)
.ToDictionary(x => x.Key, x => x.First());
foreach(var order in supplier.SelectMany(x => x.childorders))
{
order.username = userById[order.userid].username;
}

Categories

Resources