C# Linq - EF, select grouped data having the max value - c#

I have a many to many table where I store UserId, SectionId, Attempt, Qualification and timestamps. So, the user can have N Attempts by Section but when I evaluate every section only need to take where the Attempt is the max value.
I tried make a join with the keys UserId and SectionId ordering desc by Attempt
var result = await (from exam in db.exams
join section in db.sections on exam.SectionId equals section.Id
join groupedTable in (from exam2 in db.exams
group exam2 by new { UserId = exam2.UserId, SectionId = exam2.SectionId, Attempt = exam2.Attempt } into grouped
select new { UserId = grouped.Key.UserId, SectionId = grouped.Key.SectionId, LastAttempt = grouped.Max(x => x.Attempt) })
.OrderByDescending(x => x.LastAttempt)
.Select(x => new
{
UserId = x.UserId,
SectionId = x.SectionId,
LastAttempt = x.LastAttempt
})
on new { UserId = exam.UserId, SectionId = section.Id }
equals new { UserId = groupedTable.UserId, SectionId = groupedTable.SectionId }
select exam)
.Distinct()
.ToListAsync();
also tried this
var result = await (from exam in db.exams
join section in db.sections on exam.SectionId equals section.Id
select new
{
UserId = exam.UserId,
SectionId = exam.SectionId,
Attempt = exam.Attempt
})
.GroupBy(x => new
{
x.UserId,
x.SectionId,
x.Attempt
})
.Select(x => new
{
UserId = x.Key.UserId,
SectionId = x.Key.SectionId,
Attempt = x.Max(x => x.Attempt)
})
.ToListAsync();
but the result is the same:
{ UserId = {e56e13b6-28e5-46b2-bd78-f975fd96e1a7}, SectionId = 8, Attempt = 1 }
{ UserId = {e56e13b6-28e5-46b2-bd78-f975fd96e1a7}, SectionId = 10, Attempt = 1 }
{ UserId = {e56e13b6-28e5-46b2-bd78-f975fd96e1a7}, SectionId = 9, Attempt = 1 }
{ UserId = {e56e13b6-28e5-46b2-bd78-f975fd96e1a7}, SectionId = 10, Attempt = 2 }
I this example I need to exclude where SectionId = 10 and Attempt = 1

You keep grouping by Attempt, which means every user+section+attempt becomes its own group (probably of size 1)
Remove Attempt from the grouping part of the operation; only group by user and section
It might be clearer to explain using SQL. You are doing this:
SELECT user, section, attempt, MAX(attempt) --max is useless here
FROM ...
GROUP BY user, section, attempt --always a group size of 1
You need to do this:
SELECT user, section, MAX(attempt)
FROM ...
GROUP BY user, section

var result = await (from exam in db.exams
join section in db.sections on exam.SectionId equals
section.Id
select new
{
UserId = exam.UserId,
SectionId = exam.SectionId,
Attempt = exam.Attempt
})
.GroupBy(x => new
{
x.UserId,
x.SectionId
})
.Select(x => new
{
UserId = x.Key.UserId,
SectionId = x.Key.SectionId,
Attempt = x.Max(c => c.Attempt)
})
.ToListAsync();
try this it will work.

Related

Remove whole group if value of grouped item is zero

I have an object
public class ActiveUser
{
public int UserID { get; set; }
public string UserName { get; set; }
public string Location { get; set; }
public bool IsActive{ get; set; }
}
with data
List<ActiveUser> userList = new List<ActiveUser>();
userList.Add( new User { UserID = 1, UserName = "UserOne", Location = "LOne", IsActive= false } );
userList.Add( new User { UserID = 1, UserName = "UserOne", Location = "LTwo", IsActive= false } );
userList.Add( new User { UserID = 2, UserName = "UserTwo", Location = "LOne", IsActive= true } );
userList.Add( new User { UserID = 2, UserName = "UserTwo", Location = "LTwo", IsActive= false } );
userList.Add( new User { UserID = 2, UserName = "UserTwo", Location = "LThree", IsActive= true } );
userList.Add( new User { UserID = , UserName = "UserThree", Location = "LOne", IsActive= true } );
What I Want?
I want to select data with the following condition using LinQ or lambda expressions using C#.
If we group by UserID, in that group, if IsActive is false for all the row, then that group need not be selected.
If the group contains at least one IsActive as True, then we have to select all the rows in the group including IsActive = false
So the output should be like all rows with UserID = 1 get removed and all other rows get included.
I tried something like this and i'm stuck. I dont have any idea how to use this.
userList.GroupBy(c => c.UserID).Select(g => g.UserName )
Any help would be much appreciated.
Thanks
If you want to alter the original list, then a for loop is more appropriate. If you want to create a new list with just the items that have at least one active item, then you can add a Where after the GroupBy:
var query = userList.GroupBy(c => c.UserID)
.Where(g => g.Any(c => c.IsActive))
note that you can't do Select(g => g.UserName) because the output of a GroupBy is a collection of groups, and the groups do not expose the properties of the items within them. If you want to project the original items instead of groups, use SelectMany to "flatten" the groups:
var query = userList.GroupBy(c => c.UserID)
.Where(g => g.Any(c => c.IsActive))
.SelectMany(g => g)
You can use group.Any(u => u.IsActive)
var allWithAtLeastOneActiveUser = userList
.GroupBy(c => new { c.UserID, c.UserName })
.Where(g => g.Any(u => u.IsActive));
If you want to flatten these groups:
List<ActiveUser> allWithAtLeastOneActiveUser = userList
.GroupBy(c => new { c.UserID, c.UserName })
.Where(g => g.Any(u => u.IsActive))
.SelectMany(g => g)
.ToList();

LINQ not sorting List<> properly

My EF query is supposed to be sorting by the date of the first Product in the list, but for some reason, it only sorts most of the products and some of the dates are in the wrong order.
Here's the code...
using (var context = new SalesEntities())
{
var groupedData = context.s84_Schedule.AsExpandable()
.Where(predicate)
.GroupBy(c => new { c.CustomerID, c.s84_Customer.CustomerName, c.SubdivisionID, c.s84_Subdivision.SubdivisionName, c.LotNumber })
.Select(grouped => new s84_Report_Project_POCO
{
CustomerID = grouped.Key.CustomerID,
CustomerName = grouped.Key.CustomerName,
SubdivisionID = grouped.Key.SubdivisionID,
SubdivisionName = grouped.Key.SubdivisionName,
LotNumber = grouped.Key.LotNumber,
Products = grouped.Select(x => new s84_Report_Project_Product
{
ProductID = x.ProductID,
ProductName = x.s84_Product.ProductName,
ProductDate = x.CustomerExpectedDate,
FieldRepID = x.FieldRepID,
FieldRepName = x.s84_FieldRep.FieldRepName,
InstallerID = x.InstallerID,
InstallerName = x.s84_Installer.InstallerName,
StatusID = x.StatusID,
StatusColor = x.s84_Status.StatusColor,
StatusName = x.s84_Status.StatusName,
Completed = x.Completed
}).ToList()
});
var finalList = groupedData.ToList().Where(x => x.Products.Last().Completed == false).ToList();
List<s84_Report_Project_POCO> lst = finalList.OrderBy(x => x.Products.First().ProductDate).ToList();
return lst;
}
Code seems good to me, but look at how one of the dates is out of order...
weird sorting http://www.84sales.com/weird_sort.png
Try doing the order by on the inital select
var groupedData = context.s84_Schedule.AsExpandable()
.Where(predicate)
.GroupBy(c => new { c.CustomerID,
c.s84_Customer.CustomerName,
c.SubdivisionID,
c.s84_Subdivision.SubdivisionName,
c.LotNumber })
.Select(grouped => new s84_Report_Project_POCO
{
CustomerID = grouped.Key.CustomerID,
CustomerName = grouped.Key.CustomerName,
SubdivisionID = grouped.Key.SubdivisionID,
SubdivisionName = grouped.Key.SubdivisionName,
LotNumber = grouped.Key.LotNumber,
Products = grouped
.Select(x => new s84_Report_Project_Product
{
ProductID = x.ProductID,
ProductName = x.s84_Product.ProductName,
ProductDate = x.CustomerExpectedDate,
FieldRepID = x.FieldRepID,
FieldRepName = x.s84_FieldRep.FieldRepName,
InstallerID = x.InstallerID,
InstallerName = x.s84_Installer.InstallerName,
StatusID = x.StatusID,
StatusColor = x.s84_Status.StatusColor,
StatusName = x.s84_Status.StatusName,
Completed = x.Completed
}).OrderBy(x => x.CustomerExpectedDate).ToList()
});
The problem is the .First() function, witch returns the first record, but not necessarly in date order. if you wich to order your grouped datas by date so that the First() function returns the most recent date, you'll need to order your datas before grouping them, and then REorder your results with the First()function :
using (var context = PrimaryConnection.returnNewConnection())
{
var groupedData = context.s84_Schedule.AsExpandable()
.Where(predicate)
.GroupBy(c => new { c.CustomerID, c.s84_Customer.CustomerName, c.SubdivisionID, c.s84_Subdivision.SubdivisionName, c.LotNumber })
.Select(grouped => new s84_Report_Project_POCO
{
CustomerID = grouped.Key.CustomerID,
CustomerName = grouped.Key.CustomerName,
SubdivisionID = grouped.Key.SubdivisionID,
SubdivisionName = grouped.Key.SubdivisionName,
LotNumber = grouped.Key.LotNumber,
Products = grouped
.Select(x => new s84_Report_Project_Product
{
ProductID = x.ProductID,
ProductName = x.s84_Product.ProductName,
ProductDate = x.CustomerExpectedDate,
FieldRepID = x.FieldRepID,
FieldRepName = x.s84_FieldRep.FieldRepName,
InstallerID = x.InstallerID,
InstallerName = x.s84_Installer.InstallerName,
StatusID = x.StatusID,
StatusColor = x.s84_Status.StatusColor,
StatusName = x.s84_Status.StatusName,
Completed = x.Completed
}).Orderby(t => t.CustomerExpectedDate).ToList()
});
var finalList = groupedData.ToList().Where(x => x.Products.Last().Completed == false).ToList();
List<s84_Report_Project_POCO> lst = finalList.OrderBy(x => x.Products.First().ProductDate).ToList();
All SQL queries (and hence Linq queries, when attached to a SQL database) have a random order, unless you sort them.
Products is not sorted - hence it has a random order.
You sort by Products.First(), but Products has a random order, so your sort will also be random.
Make sure Products is sorted within the query, and you should be ok.
Products = grouped.Select(....)
.OrderBy(x => x.ProductDate)
.ToList()

How can I use this Linq Query using it with a multiple parameter by Join tables

var list = dc.Orders.
Join(dc.Order_Details,
o => o.OrderID, od => od.OrderID, <-- what if i have 2 more parameters let say an ID and a REC on both table. ex.: o=> o.OrderID && o.ItemName, od => od.OrderID && od.Itemname then (o, od) but its result is error? is there another way?
(o, od) => new
{
OrderID = o.OrderID,
OrderDate = o.OrderDate,
ShipName = o.ShipName,
Quantity = od.Quantity,
UnitPrice = od.UnitPrice,
ProductID = od.ProductID
}).Join(dc.Products,
a => a.ProductID, p => p.ProductID, <-- at this point too?
(a, p) => new
{
OrderID = a.OrderID,
OrderDate = a.OrderDate,
ShipName = a.ShipName,
Quantity = a.Quantity,
UnitPrice = a.UnitPrice,
ProductName = p.ProductName
});
is it possible to use this lambda expression linq query with multiple parameters by joining 3 tables?
--- UPDATE STILL ERROR -- :(
var header = DB.Delivery_HeaderRECs.Join(DB.Delivery_DetailsRECs, <-- Red Line on DB.Delivery_HeaderRECs.Join
q => new { q.drNO, q.RecNO },
qw => new { qw.DrNO, qw.RecNO },
(q, qw) => new
{
DR = q.drNO,
DATE = q.DocDate,
RECNO = q.RecNO,
CUSTID = q.CustomerID,
CUSTADDR = q.CustomerADDR,
RELEASE = q.ReleasedBy,
RECEIVE = q.ReceivedBy,
REMARKS = q.Remarks,
ITEM = qw.ItemCode,
DESC = qw.ItemDesc,
QTY = qw.Qty,
COST = qw.Unit,
PLATENO = qw.PlateNo,
TICKETNO = qw.TicketNo
}).Join(DB.Delivery_TruckScaleRECs,
w => new { w.DR, w.TICKETNO },
we => new { we.DrNo, we.TicketNO },
(w, we) => new
{
DR = w.DR,
DATE = w.DATE,
RECNO = w.RECNO,
CUSTID = w.CUSTID,
CUSTADDR = w.CUSTADDR,
RELEASE = w.RELEASE,
RECEIVE = w.RECEIVE,
REMARKS = w.REMARKS,
ITEM = w.ITEM,
DESC = w.DESC,
QTY = w.QTY,
COST = w.COST,
PLATENO = w.PLATENO,
TICKETNO = w.TICKETNO,
TRANSAC = we.TransactionType,
FWEIGHT = we.FirstWeight,
SWEIGHT = we.SecondWeight,
NWEIGHT = we.NetWeight
}).FirstOrDefault();
I made up changes base on the answer but an error said above the statement:
"The type arguments for method cannot be inferred from the usage. Try specifying the type arguments explicitly". i think it's talking about the parameters i've made..
You can use anonymous type for the Join like this:
var list = dc.Orders.Join(dc.Order_Details,
o => new { o.OrderID, o.ItemName},
od => new { od.OrderID, od.ItemName},
...);
The anonymous type will be compiled to use the autoimplemented Equals and GetHashCode so that the equality will be derived by the equality of all the corresponding properties. Just add more properties as you want in the the new {....}. Note that the order of properties provided in the 2 new {...} should be the same order of correspondence. The names should also be matched, you can explicitly specify the names to ensure this (this is needed in some cases) such as:
new {OrderID = o.OrderID, Name = o.ItemName}
However in your case the property names will be used as the same properties of the item.
UPDATE
This update is just a fix for your specific parameters, I said that the property names should be the same, if they are not you have to explicitly name them like this:
var list = dc.Orders.Join(dc.Order_Details,
q => new {DrNO = q.drNO, q.RecNO},
qw => new {DrNO = qw.DrNO, qw.RecNO},
...);

Joining two tables in linq method syntax, MVC EntityFramework

I'm working with two tables: CI_CLIENTRISK (SCD type 2)... and QB_INVOICES_HEADER (edmx screenshot).
They can be joined via ClientID. I want to essentially replicate this query:
SELECT a.ClientID,
MAX(b.InvoiceDt) AS MaxInvoiceDt
(omitted for brevity)
FROM CI_CLIENTRISKADJS a
INNER JOIN QB_INVOICES_HEADER b
ON a.ClientID = b.ClientID
WHERE a.IsActive = 1
GROUP BY a.ClientID
ORDER BY MaxInvoiceDt DESC
Here's what I have so far. It's not returning any records.
using (var db = new PLOGITENS01Entities())
{
var rvClientRiskAdjs = db.CI_CLIENTRISKADJS
.Take(50)
.Join(db.QB_INVOICES_HEADER,
a => a.ClientID,
b => b.ClientID,
(a, b) => new { Risk = a, Invoices = b })
.Where(a => a.Risk.IsActive == 1)
.OrderByDescending(o => o.Invoices.InvoiceDt)
.Select(c => new ClientRiskModel()
{
ClientRiskId = c.Risk.ClientRiskID,
ClientName = c.Risk.CI_CLIENTLIST.ClientName,
ClientId = c.Risk.ClientID,
ClientRiskAdjs = c.Risk.ClientRiskAdjs,
RecordValidStartDt = c.Risk.RecordValidStartDt,
RecordValidEnddt = c.Risk.RecordValidEnddt,
IsActive = c.Risk.IsActive
})
.ToList();
return View(new GridModel(rvClientRiskAdjs));
}
Try putting your .Take(50) method after your final .Select and before .ToList(). As it is, you are only taking the first 50 records of the first table and then joining from there. I'm assuming that there are no joins to the second table in the first 50 records of the first table; therefore, your result will have 0 records.
I stumbled across this solution from reading this post: https://stackoverflow.com/a/157919/1689144
var rvClientRiskAdjs = (from ri in db.CI_CLIENTRISKADJS
join qb in
(from qb in db.QB_INVOICES_HEADER
orderby qb.InvoiceDt ascending
group qb by qb.ClientID into grp
select new
{
InvoiceDt = grp.Max(s => s.InvoiceDt),
ClientID = grp.Key
})
on ri.ClientID equals qb.ClientID
orderby qb.InvoiceDt descending
where ri.IsActive == 1
select new ClientRiskModel()
{
ClientRiskId = ri.ClientRiskID,
ClientName = ri.CI_CLIENTLIST.ClientName,
ClientId = ri.ClientID,
ClientRiskAdjs = ri.ClientRiskAdjs,
RecordValidEnddt = ri.RecordValidEnddt,
RecordValidStartDt = ri.RecordValidStartDt
})
.ToList();

C# linq group by on different keys in the same entity

I've been toying with this for a while and just can't get it. I'm new to Linq, C# and these Lambda things.
What I want to do is group entities according to two properties on each entity. It's a Message entity:
Message
{
int UserId; //The user generating the message
int UserIdTo; //The receiver of the message
|...| // Other stuff
}
So, I want it so that these UserId=5, UserIdTo=6 and UserId=6, UserIdTo=5 would be in the same group.
Here's my start:
var groupList = (from m in db.Messages
where m.UserId == userId || m.UserIdTo == userId
join u in db.Users on m.UserId equals u.UserId
join w in db.Users on m.UserIdTo equals w.UserId
orderby m.MessageTimestamp descending
select new DomMessage
{
MessageId = m.MessageId,
MessageContent = m.MessageContent,
MessageTimestamp = m.MessageTimestamp,
UserId = m.UserId,
UserIdTo = m.UserIdTo,
ScreenName = u.ScreenName,
ScreenName2 = w.ScreenName
}).GroupBy(m=>m.UserId == userId)
.ToList();
This does the first bit of grouping by UserId, but I'm stuck on trying to extend this so that where any UserId value in the resulting group equals the UserIdTo somewhere else add that to this group?
EDIT: I need the result to go to a List because there is other stuff I need to do with it...
Thanks!
Try this:
var payload = new[]
{
new{ To = 1, From = 2, Message = "msj1" },
new{ To = 1, From = 2, Message = "msj2" },
new{ To = 2, From = 1, Message = "msj3" },
new{ To = 4, From = 1, Message = "msj4" },
new{ To = 1, From = 3, Message = "msj5" }
};
var groupped = payload.Select(x => new { Key = Math.Min(x.To, x.From) + "_" + Math.Max(x.To, x.From), Envelope = x }).GroupBy(y => y.Key).ToList();
foreach (var item in groupped)
{
Console.WriteLine(String.Format(#"Group: {0}, messages:", item.Key));
foreach (var element in item)
{
Console.WriteLine(String.Format(#"From: {0} To: {1} Message: {2}", element.Envelope.From, element.Envelope.To, element.Envelope.Message));
}
}
Try the following GroupBy expression:
.GroupBy(m => Math.Min(m.UserId, m.UserIdTo) + ',' + Math.Max(m.UserId, m.UserIdTo))
I think this is the easiest way:
var groupList = from a in (
from m in db.Messages
where m.UserId == userId || m.UserIdTo == userId
join u in db.Users on m.UserId equals u.UserId
join w in db.Users on m.UserIdTo equals w.UserId
select new
{
MessageId = m.MessageId,
MessageContent = m.MessageContent,
MessageTimestamp = m.MessageTimestamp,
UserId = m.UserId,
UserIdTo = m.UserIdTo,
ScreenName = u.ScreenName,
ScreenName2 = w.ScreenName
})
group a by new {
UserId = a.UserId,
UserIdTo = a.UserIdTo
} into grp
orderby grp.Max(a => a.MessageTimestamp) descending
select new
{
UserId = grp.Key.UserId
UserIdTo = grp.Key.UserIdTo,
MessageId = grp.Max(a => a.MessageId),
MessageContent = grp.Max(a => a.MessageContent),
MessageTimestamp = grp.Max(a => a.MessageTimestamp),
ScreenName = grp.Max(a => a.ScreenName),
ScreenName2 = grp.Max(a => a.ScreenName2)
}
You have to tell it what to do with the fields you are not grouping by. In this case I got the MAX value for each.

Categories

Resources