Linq to select objects with unique Id and greatest date? - c#

I have a list of objects that look like this:
public class A
{
public int Id {get; set;}
public DateTime Date {get;set;}
public int TypeId {get; set;}
public int Version {get; set;}
}
My data looks like this:
Id Date TypeId Version
1 10/3/18 1 1
2 10/3/18 1 2
3 10/4/18 1 1
4 10/4/18 2 1
How can I make a linq query to return these 2 in a list where it gets the item with the greatest date where the version # is 1 and also uses the TypeId to return more items?
Id Date TypeId Version
3 10/4/18 1 1
4 10/4/18 2 1
This is what I have tried but my code only returns one item because of my FirstOrDefault function.
var q = from n in A.All.Where(x => x.Version == 1)
group n by n.TypeId into g
select g.OrderByDescending(t => t.Date).FirstOrDefault();

You need to group by typeId and then in each group order elements by date. Then you can pick first element in each group. Try this code:
var input = new[]
{
new A {Id = 1, Date = new DateTime(2018, 10, 3), TypeId = 1, Version = 1},
new A {Id = 2, Date = new DateTime(2018, 10, 3), TypeId = 1, Version = 2},
new A {Id = 3, Date = new DateTime(2018, 10, 4), TypeId = 1, Version = 1},
new A {Id = 4, Date = new DateTime(2018, 10, 4), TypeId = 2, Version = 1},
};
var result = input.Where(a => a.Version == 1)
.GroupBy(a => a.TypeId)
.Select(g => g.OrderByDescending(x => x.Date).First())
.ToArray();

Related

Select TOP 1 for each FK in list using Entity Framework

I have a large table where I'm trying to select the top 1 row for each FK in a list.
My table is laid out as:
ChangeId | AssetId | Timestamp
1 1 123
2 2 999
3 1 3478
4 3 344
5 2 1092
Where ChangeId is my PK, AssetId is my FK and Timestamp is the value I'm trying to select.
If I try the following:
var results =
from Asset in _context.Asset
join change in _context.Change on Asset.AssetId equals change.AssetId into potentialChange
from actualChange in potentialChange.OrderByDescending(y => y.ChangeId).Take(1)
select
{
AssetId,
Timestamp
}
Where my expected result would be:
[
{
AssetId: 1,
Timestamp: 3478
},
{
AssetId: 2,
Timestamp: 1092
},
{
AssetId: 3,
Timestamp: 344
}
]
This query flags up the The LINQ expression could not be translated and will be evaluated locally. which is not suitable for a production rollout.
Running a foreach loop and selecting each item out 1 by 1 works, not it's not a performant solution.
Is there a suitable way to achieve the above?
Try to group it by AssetId and take max from each group
var results =
from Asset in _context.Asset
join change in _context.Change on Asset.AssetId equals change.AssetId into potentialChange
group potentialChange by potentialCharge.AssetId into g
select
{
g.Key,
g.Max().Timestamp
}
Use Group By as follows:
List<MyTable> data = new List<MyTable>()
{
new MyTable(){ChangeId = 1, AssetId = 1, Timestamp = 123},
new MyTable(){ChangeId = 2, AssetId = 2, Timestamp = 999},
new MyTable(){ChangeId = 3, AssetId = 1, Timestamp = 123},
new MyTable(){ChangeId = 5, AssetId = 3, Timestamp = 123},
new MyTable(){ChangeId = 5, AssetId = 2, Timestamp = 123},
};
var expectedData = data.OrderByDescending(d => d.Timestamp).GroupBy(d => d.AssetId).Select(g => new
{
AssetId = g.Key,
TimeStamp = g.First().Timestamp
}).ToList();
This will give your expected result.
Try using .First() instead of .Take(1)
LINQ How to take one record and skip rest c#

Select values with max date for each ID [duplicate]

This question already has answers here:
How do I get the MAX row with a GROUP BY in LINQ query?
(7 answers)
Closed 5 years ago.
This is the table "history"
id value date
1 1 01/01/2017 20:20:20
1 2 02/01/2017 20:20:20
1 3 03/01/2017 20:20:20
2 5 01/01/2017 20:20:20
2 6 02/01/2017 20:20:20
How with linq select max values for each id
context.History
.GroupBy(x => x.id) ??
.SelectOnlyWithMax(z => z.date) ??
In result only two objects
id value date
1 3 03/01/2017 20:20:20
2 6 02/01/2017 20:20:20
If you want the entire row with the highest date for each Id, you can use the following code (written with LinqPad). If you just want the Id, you can use #BurnsBA's answer, as it will be slightly more efficient.
void Main()
{
var data = new List<Record>
{
new Record(){Id=1, Value=1, Date=new DateTime(2017,1,1)},
new Record(){Id=1, Value=2, Date=new DateTime(2017,2,1)},
new Record(){Id=1, Value=3, Date=new DateTime(2017,3,1)},
new Record(){Id=2, Value=5, Date=new DateTime(2017,1,1)},
new Record(){Id=2, Value=6, Date=new DateTime(2017,2,1)},
};
var query = data.GroupBy(d => d.Id)
.SelectMany(g => g.OrderByDescending(d => d.Date)
.Take(1));
query.Dump();
}
public class Record
{
public int Id { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
Results:
First it groups by Id, then sorts the items within the group by Date in descending order, and returns the first one, SelectMany then flattens the list.
public class History
{
public int id { get; set; }
public int value { get; set; }
public DateTime date { get; set; }
}
// setup:
var values = new List<History>();
values.Add(new History() { id = 1, value = 1, date = DateTime.Parse("01/01/2017 20:20:20") });
values.Add(new History() { id = 1, value = 2, date = DateTime.Parse("02/01/2017 20:20:20") });
values.Add(new History() { id = 1, value = 3, date = DateTime.Parse("03/01/2017 20:20:20") });
values.Add(new History() { id = 2, value = 5, date = DateTime.Parse("01/01/2017 20:20:20") });
values.Add(new History() { id = 2, value = 6, date = DateTime.Parse("02/01/2017 20:20:20") });
// result :
values.GroupBy(
x => x.id,
y => y.date,
// Below, dates will be enumerable
(id, dates) => new { id = id, date = dates.Max() }
)
// returns enumerable collection of anonymous type:
{
{ id = 1, date = [3/1/2017 8:20:20 PM] },
{ id = 2, date = [2/1/2017 8:20:20 PM] }
}
I suggest MoreLINQ's MaxBy function, that is:
context.History.GroupBy( x => x.id ).Select( x => x.MaxBy( y => y.date) )

Looking for a more elegant LINQ solution

I am trying to figure out an efficient way to retrieve the data I am after. I need to get a list of all of the most recent children by ParentId coupled with all parent entries that do NOT have children. I have created a visual guide to illustrate what the response should be.
The query needs to remain as IQueryable until ALL sorting and paging is completed.
Last and LastOrDefault are not supported by LINQ to Entities (as stated by the error messages I have received while using them).
Using First or FirstOrDefault will return the error "This method or operation is not implemented"
Original Data:
-------------------------------
- Id - ParentId - CreatedDate -
-------------------------------
- 1 - - 07/01/2013 -
- 2 - - 07/01/2013 -
- 3 - - 07/01/2013 -
- 4 - 1 - 07/02/2013 -
- 5 - 2 - 07/03/2013 -
- 6 - 2 - 07/04/2013 -
- 7 - 1 - 07/05/2013 -
-------------------------------
Data returned by query
-------------------------------
- Id - ParentId - CreatedDate -
-------------------------------
- 3 - - 07/01/2013 -
- 6 - 2 - 07/04/2013 -
- 7 - 1 - 07/05/2013 -
-------------------------------
Currently, my LINQ query looks like this:
// Retrieves parent records with NO children.
var q1 = myTable
.Where(x => x.ParentId == null)
.Except(myTable
.Where(x => myTable
.Any(c => (c.ParentId == x.Id))));
// Retrieves most recent child records for each parentId
var q2 =
(from a in myTable
join b in
(myTable.Where(a => a.ParentId != null)
.GroupBy(a => a.ParentId)
.Select(b => new { ParentId = b.Key, CreatedDate = b.Max(t => t.CreatedDate) }))
on a.ParentId equals b.ParentId
where a.CreatedDate == b.CreatedDate
select a);
q1 = q1.Union(q2);
The back-end is using Npgsql2 with PostgreSQL. I am looking for a more elegant solution for this query. I am very new to LINQ and would like to optimize this.
Sorting code (sloppy, but jTable returns these strings):
if (string.IsNullOrEmpty(sorting) || sorting.Equals("Name ASC")) {
q1 = q1.OrderBy(p => p.Customer.Name);
} else if (sorting.Equals("Name DESC")) {
q1 = q1.OrderByDescending(p => p.Customer.Name);
} else if (sorting.Equals("Date ASC")) {
q1 = q1.OrderBy(p => p.CreatedDate);
} else if (sorting.Equals("Date DESC")) {
q1 = q1.OrderByDescending(p => p.CreatedDate);
}
Paging code:
var result = pageSize > 0
? q1.Skip(startIndex).Take(pageSize).ToList()
: q1.ToList();
Use grouping:
Mock data:
public class Entry {
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime Date { get; set; }
};
var list = new List<Entry> {
new Entry{ Id = 1, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 2, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 3, ParentId = null, Date = new DateTime(2013, 7, 1) },
new Entry{ Id = 4, ParentId = 1, Date = new DateTime(2013, 7, 2) },
new Entry{ Id = 5, ParentId = 2, Date = new DateTime(2013, 7, 3) },
new Entry{ Id = 6, ParentId = 2, Date = new DateTime(2013, 7, 4) },
new Entry{ Id = 7, ParentId = 1, Date = new DateTime(2013, 7, 5) }
};
Query:
var query = from l in list
group l by l.ParentId into g
select new {
Items = g.OrderBy(x => x.Date).Last()
};
var res = query.OrderBy(x => x.Items.Id).Select(x => x.Items).ToList();
LinqPad result:
Id ParentId Date
3 null 01.07.2013 0:00:00
6 2 04.07.2013 0:00:00
7 1 05.07.2013 0:00:00
I can propose a different query, still in two phases
var firstQuery = myTable.Select(p => new { p.ID, ParentID = p.ParentID ?? p.ID, p.CreatedDate })
.GroupBy( p => p.ParentID).Select( q => new
{
el = q.OrderByDescending( k => k.CreatedDate).Take(1)
}).SelectMany(t => t.el);
var result = dc.TabellaId_ParentId.Where(p => test.Select(q => q.ID).Contains(p.ID));

How to reconcile first and last record in LINQ

I have a table in my SQL database that tracks an employee's in time and out time. A typical record would look like this
Id Device DateTime EmployeeId
-------------------------------------------------------------------------
1 InReader 2013/05/05 08:00:00 1
2 InReader 2013/05/05 08:00:05 1
3 InReader 2013/05/05 08:01:00 2
4 InReader 2013/05/05 08:02:00 3
5 InReader 2013/05/05 08:03:00 4
6 OutReader 2013/05/05 17:00:00 1
7 OutReader 2013/05/05 17:05:05 2
8 OutReader 2013/05/05 17:05:10 2
9 OutReader 2013/05/05 17:10:00 3
10 OutReader 2013/05/05 17:30:00 4
Id is just an auto increment column
Device is the device that they tapped their staff card on, to clock in/or out
DateTime is the time that they tapped their staff card
I would like to know, at the end of the day, when I'm generating a report, how can I reconcile their in-time with out-time, such that the output may look like this:
Employee Id In time Out time
-----------------------------------------------------------------------
1 2013/05/05 08:00:00 2013/05/05 17:00:00
2 2013/05/05 08:01:00 2013/05/05 17:05:10
3 2013/05/05 08:02:00 2013/05/05 17:10:00
4 2013/05/05 08:03:00 2013/05/05 17:30:00
Caveats:
- Note that Employee 1 has 2 records for "InReader", I would want to take the earlier record
- Employee 2 has 2 records for "OutReader", I would want to take only his latest record
How do I reconcile the IN and OUT records using LINQ? (or TSQL if it's not possible in LINQ)
I made you this query, and tested in LinqPad. I'll give you the full code so you can try it yourself.
The query itself:
tracks.GroupBy(x => x.EmployeeId)
.Select(x => new
{
EmployeeId = x.Key,
InTime = x.FirstOrDefault(y => y.Device.Equals("InReader")).DateTime,
OutTime = x.LastOrDefault(y => y.Device.Equals("OutReader")).DateTime
})
The full code sample:
void Main()
{
var tracks = new[]
{
new Track{Id = 1, Device = "InReader", DateTime = new DateTime(2013,5,5,8,0,0), EmployeeId = 1},
new Track{Id = 2, Device = "InReader", DateTime = new DateTime(2013,5,5,8,0,5), EmployeeId = 1},
new Track{Id = 3, Device = "InReader", DateTime = new DateTime(2013,5,5,8,1,0), EmployeeId = 2},
new Track{Id = 4, Device = "InReader", DateTime = new DateTime(2013,5,5,8,2,0), EmployeeId = 3},
new Track{Id = 5, Device = "InReader", DateTime = new DateTime(2013,5,5,8,3,0), EmployeeId = 4},
new Track{Id = 6, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,0,0), EmployeeId = 1},
new Track{Id = 7, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,5,5), EmployeeId = 2},
new Track{Id = 8, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,5,10), EmployeeId = 2},
new Track{Id = 9, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,10,0), EmployeeId = 3},
new Track{Id = 10, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,30,0), EmployeeId = 4},
};
// the Query
tracks
.GroupBy(x => x.EmployeeId)
.Select(x => new
{
EmployeeId = x.Key,
InTime = x.FirstOrDefault(y => y.Device.Equals("InReader")).DateTime,
OutTime = x.LastOrDefault(y => y.Device.Equals("OutReader")).DateTime
})
}
public class Track
{
public int Id { get; set; }
public string Device { get; set; }
public DateTime DateTime { get; set; }
public int EmployeeId { get; set; }
}
Use Min, Max aggregate to returns the smallest or largest element in the sequence and use GroupBy for ordering.
var result=YourTableRowCollection.GroupBy(x=>x.EmployeeId)
.Select(x=>new { EmployeeId=x.Key,
InTime=x.Min(t=>DateTime.Parse(t.InTime)).ToString(),
OutTime=x.Max(t=>DateTime.Parse(t.OutTime)).ToString()});
var x = from current in context.Employess
let firstRecord = current.Where(c=> c.Track.Device == "InReader").OrderBy(c => c.DateTime).First()
let lastRecord = current.Where(c=> c.Track.Device == "OutReader").OrderBy(c => c.DateTime).Last()
select{
// do something
}
Something like that above should do the trick.
var result = table.Where(e => e.Device == "InReader")
.GroupBy(e => e.EmployeeId)
.Select(g => g.OrderBy(d => d.DateTime).First())
.Join(
table.Where(e => e.Device == "OutReader")
.GroupBy(e => e.EmployeeId)
.Select(g => g.OrderByDescending(d => d.DateTime).First()),
t => t.EmployeeId, t => t.EmployeeId,(t, i) => new { Id = t.EmployeeId, In = t.DateTime, Out = i.DateTime });
I suppose that you know your employee's ID (or may be you are going through a list) and the date at which you're generating your report, so first thing you need to do is to get your employee's in and out times of the day with sth. like this:
//first second of the day
DateTime firstSecondOfTheDay = dateToCheck.Subtract(dateToCheck.TimeOfDay);
//last second of the day
TimeSpan endOfDay = new TimeSpan(23, 59, 59);
DateTime lastSecondOfTheDay = firstSecondOfTheDay.Add(endOfDay);
var employeeDayInOut = from emp in context.Employess
where (emp.DateTime >= firstSecondOfTheDay) &
(emp.DateTime <= lastSecondOfTheDay) &
(emp.EmployeeId == idToCheck)
select emp;
You could also easily rewrite this query in order to obtain all employees times of the day, and filter by EmployeeId lately (it depends on what is better for your case).
And after that you can obtain easily from your employee's in and out times of the day the date you need for your report like this:
employeeDayInOut.Max(emp => emp.DateTime);
employeeDayInOut.Min(emp => emp.DateTime);

Select multiple fields group by and sum

I want to do a query with linq (list of objects) and I really don't know how to do it, I can do the group and the sum but can't select rest of the fields.
Example:
ID Value Name Category
1 5 Name1 Category1
1 7 Name1 Category1
2 1 Name2 Category2
3 6 Name3 Category3
3 2 Name3 Category3
I want to group by ID, SUM by Value and return all fields like this.
ID Value Name Category
1 12 Name1 Category1
2 1 Name2 Category2
3 8 Name3 Category3
Updated :
If you're trying to avoid grouping for all the fields, you can group just by Id:
data.GroupBy(d => d.Id)
.Select(
g => new
{
Key = g.Key,
Value = g.Sum(s => s.Value),
Name = g.First().Name,
Category = g.First().Category
});
But this code assumes that for each Id, the same Name and Category apply. If so, you should consider normalizing as #Aron suggests. It would imply keeping Id and Value in one class and moving Name, Category (and whichever other fields would be the same for the same Id) to another class, while also having the Id for reference. The normalization process reduces data redundancy and dependency.
void Main()
{
//Me being lazy in init
var foos = new []
{
new Foo { Id = 1, Value = 5},
new Foo { Id = 1, Value = 7},
new Foo { Id = 2, Value = 1},
new Foo { Id = 3, Value = 6},
new Foo { Id = 3, Value = 2},
};
foreach(var x in foos)
{
x.Name = "Name" + x.Id;
x.Category = "Category" + x.Id;
}
//end init.
var result = from x in foos
group x.Value by new { x.Id, x.Name, x.Category}
into g
select new { g.Key.Id, g.Key.Name, g.Key.Category, Value = g.Sum()};
Console.WriteLine(result);
}
// Define other methods and classes here
public class Foo
{
public int Id {get;set;}
public int Value {get;set;}
public string Name {get;set;}
public string Category {get;set;}
}
If your class is really long and you don't want to copy all the stuff, you can try something like this:
l.GroupBy(x => x.id).
Select(x => {
var ret = x.First();
ret.value = x.Sum(xt => xt.value);
return ret;
}).ToList();
With great power great responsibility comes. You need to be careful. Line ret.value = x.Sum(xt => xt.value) will change your original collection, as you are passing reference, not new object. If you want to avoid it, you need to add some Clone method into your class like MemberwiseClone (but again, this will create shallow copy, so be careful). Afer that just replace the line with: var ret = x.First().Clone();
try this:
var objList = new List<SampleObject>();
objList.Add(new SampleObject() { ID = 1, Value = 5, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 1, Value = 7, Name = "Name1", Category = "Catergory1"});
objList.Add(new SampleObject() { ID = 2, Value = 1, Name = "Name2", Category = "Catergory2"});
objList.Add(new SampleObject() { ID = 3, Value = 6, Name = "Name3", Category = "Catergory3"});
objList.Add(new SampleObject() { ID = 3, Value = 2, Name = "Name3", Category = "Catergory3"});
var newList = from val in objList
group val by new { val.ID, val.Name, val.Category } into grouped
select new SampleObject() { ID = grouped.ID, Value = grouped.Sum(), Name = grouped.Name, Category = grouped.Category };
to check with LINQPad:
newList.Dump();

Categories