I have a collection in this format
IList<ReportRow> MyCollection
Where ReportRow is a
Dictionary<string, object>
So MyCollection might look like this:
Id1 Id2 Value Status Entered
123 2 56 New 50:01.7
123 2 76 Old 50:23.0
123 2 12 New 50:23.0
127 3 54 Old 50:23.0
127 3 77 New 59:23.0
...
Is there a way in Linq that I can do this:
What I need to do is for each Id1+Id2 combination I need to output the Value where Status is New and where the status is Old. There might be multiple New values for an Id1+Id2 combination so it should pick up the latest New record, using the Entered column to sort.
The new record will contain all the records plus 1 extra column like so:
Id1 Id2 NewValue OldValue Entered
123 2 12 76 50:23.0
127 3 77 54 59:23.0
Any help with this would be great
This should do it (or be close as this is off the top of my head)
collection.OrderByDescending(r => r.Value.Entered)
.GroupBy(r => {r.Id1, r.Id2)
.Select(g => new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = g.FirstOrDefault(v => v.Status == "New").Value ?? 0,
OldValue = g.FirstOrDefault(v => v.Status == "Old").Value ?? 0,
Entered = g.Last().Entered
})
If you don't care for the dictionary string key you could do
collection.SelectMany(d => d.Value)
.OrderByDescending(r => r.Value.Entered)
.GroupBy(rr => {rr.Value.Id1, rr.Value.Id2)
.Select(g => new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = g.FirstOrDefault(v => v.Status == "New").Value ?? 0,
OldValue = g.FirstOrDefault(v => v.Status == "Old").Value ?? 0,
Entered = g.Last().Entered
})
from row in MyCollection
group row by new { row.Id1, row.Id2 } into g
let lastNew = g.OrderByDescending(r => r.Entered)
.FirstOrDefault(r => r.Status == "New")
let lastOld = g.OrderByDescending(r => r.Entered)
.FirstOrDefault(r => r.Status == "Old")
select new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = (lastNew == null) ? null : (int?)lastNew.Value,
OldValue = (lastOld == null) ? null : (int?)lastOld.Value,
Entered = g.OrderByDescending(r => r.Entered).First().Entered
}
Related
I have table called info, and its data something like this:
FKUserID
CompletedTime
Type
1
2021-03-10 12:56:00.423
5
245
2021-03-10 12:46:46.977
5
1
2021-03-10 12:44:53.683
5
1
2021-03-10 12:40:54.733
5
1
2021-03-10 12:35:26.307
5
245
2021-03-10 11:11:33.887
5
245
2021-03-10 10:18:11.403
5
I need to get distinct userID data, and also with the maximum completed time of theirs CompletedTime column
expected output is:
FKUserID
CompletedTime
Type
1
2021-03-10 12:56:00.423
5
245
2021-03-10 12:46:46.977
5
I need to do this using Linq query
How can I do this, I did it using SQl, need it using Linq
SELECT FKUserID , MAX(CompletedTime)
from Info
where cast(CompletedTime as date) = '2021-03-10'
and Status = 5
GROUP BY FKUserID;
You can use the following query. The following query implements your SQL query.
var query = context.Info.GroupBy(a => a.FKUserID)
.Join(context.Info,
left => left.Key,
right => right.FKUserID,
(left, right) => new { left, right })
.Where(a => a.right.CompletedTime.ToShortDateString() == "2021-03-10" && a.right.Status == 5)
.Select(a => new
{
FKUserID = a.left.Key,
CompletedTime = a.left.Max(x => x.CompletedTime)
}).ToList();
Something like this
var d = new DateTime(2021, 3, 10);
var d2 = d.AddDays(1);
dbContext.Infos
.Where(i => i.Status == 5 && i.CompletedTime >= d && i.CompletedTime < d2)
.GroupBy(i => i.FkUserId)
.Select(g => new {
FkUserId = g.Key,
CompletedTime = g.Max(g2 => g2.CompletedTime)
}
);
I have this column:
PersonId CategoryId SubCategoryId
1 61 47
2 61 48
3 61 0 424
4 61 0 425
5 84 55
6 61 585
7 101 48
8 101 424
8 666 47
10 666 424
When i search only for categoryId its fine, and subCategoryId its fine, but when i search for both, like categoryId = 47 && subCategoryId = 424.
I cant make it work... in this case i only need person 61 and person 666.
i can do with a foreach, but not a good idea for performance.
u guys can help me?
if (ids.IdCategory != null && ids.IdSubCategory != null)
{
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdCategory == ids.IdCategory || m.IdSubCategory == ids.IdSubCategory);
}
else if (ids.IdCategory != null)
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdCategory == ids.IdCategory);
else
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdSubCategory == ids.IdSubCategory);
first case i get half what i want... but i want some way to filter, because in this way i get 101 too and i dont know how to see if people have both category and subcategory
and i cant see a way to do this without a foreach
You should group all person skills by person id, and then select only those groups, which contain both given category and sub category:
unitOfWork.PersonSkillsRepository.GetAll()
.GroupBy(p => p.PersonId)
.Where(g => g.Any(p => p.IdCategory == ids.IdCategory)
&& g.Any(p => p.IdSubCategory == ids.IdSubCategory))
.Select(g => g.Key)
For optimization, you can filter out skills which do not match any of given categories before grouping.
unitOfWork.PersonSkillsRepository.GetAll()
.Where(p => p.IdCategory == ids.IdCategory
|| p.IdSubCategory == ids.IdSubCategory)
.GroupBy(p => p.PersonId)
.Where(g => g.Any(p => p.IdCategory == ids.IdCategory)
&& g.Any(p => p.IdSubCategory == ids.IdSubCategory))
.Select(g => g.Key)
I just Showed the data of yours in Image
The issue is that you don't have any value against CategoryId=47 in column SubCategoryId thats why when you search both of them together it gives nothing.
Try it by adding any value in SubCategoryId against CategoryId=47...
entities.Where(z => z.categoryId == 47 || z.subCategoryId == 424)
.GroupBy(z => z.personId)
.Where(z => z.Count() == 2)
.SelectMany(z => z.ToList());
will likely get you the data that you need.
That query says 'filter the data and only return those where categoryId is 47 or subCategoryId = 424'. Then it groups them together and checks that there are two of them (i.e. one row for the categoryId and one for the subCategoryId).
EDIT:
After the question is more clarified the following code should work
if (ids.IdCategory != null && ids.IdSubCategory != null)
{
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => (m.IdCategory == ids.IdCategory || m.IdSubCategory == ids.IdSubCategory) && m.IdPerson != 101);
}
Firstly your data will return null for your search criteria. Following are some example of linq that might help this scenario.
You can do different kind of linq.
List<CollectionObject> ColObj = CollectionObject.GetListCollectionObj();
List<CollectionObject> LinqResult = ColObj.Where(x => x.CategoryID == 47 && x.SubcategoryID == null).ToList();
The following code works fine with the linq statements above.
class CollectionObject
{
public int PersonID { get; set; }
public int? CategoryID { get; set; }
public int? SubcategoryID { get; set; }
public static List<CollectionObject> GetListCollectionObj()
{
List<CollectionObject> LColObj = new List<CollectionObject>();
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 47, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 48, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 0, SubcategoryID = 424 });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 0, SubcategoryID = 425 });
LColObj.Add(new CollectionObject() { PersonID = 101, CategoryID = 48, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 101, CategoryID = null, SubcategoryID = 424 });
LColObj.Add(new CollectionObject() { PersonID = 666, CategoryID = 47, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 666, CategoryID = null, SubcategoryID = 424 });
return LColObj;
}
}
Well, while I was working on mine, it seems 3 other people came up with the same thing, so I'll throw in mine --- with the critical addition the OP needs....
Func<IGrouping<int, CollectionObject>, bool> query =
gp=>
gp.Any(g=>g.CategoryID == 47) &&
gp.Any(g=>g.SubcategoryID == 424);
var q = (from p in ColObj
group p by p.PersonID)
.Where(query);
You'll need to replace query with your one-field versions on occasion.
The following would do, but you should be aware your way of storing data is quite odd.
(I mean, aren't all your subcategories related to a category ?)
// gets all the Ids of people of category 47
var personIds = entities
.Where(x => x.categoryId == 47)
.Select(x => x.PersonId)
.Distinct()
.ToList();
// gets all rows of people of category 47 and subcategory 424
var result = entities
.Where(x => personIds.Contains(x.PersonId) && x.subCategoryId == 424)
.ToList();
Linked dotnetfiddle.
Say I have MyTable with MyField1 and MyField2, MyField3 populated as follows
MyField1 MyField2 MyField3
A1 null x
A1 123234 x
B1 47686876 x
C1 null x
C1 8856578 x
D1 null x
E1 23423 y
How can I write a lambda query to only bring back records where MyField3 = 'x' and there is either:
only one occurrence of MyField1, OR
Where there are 2 occurrences, only take the one where MyField2 is populated?
There will only ever be at most 2 occurrences of MyField2.
So far I have
MyList = db.MyTable.Where(p => p.MyField3 == "x")
I know it's bad table design (different field names obviously) but I just inherited it.
thanks
use below code
MyList = db.MyTable.Where(p => p.MyField3 == "x" && p.MyField2!=null).Distinct();
This clause will give you the records where MyField2 is null and MyField1 is unique:
var MySecondList = db.MyTable.Where(p => p.MyField2 == null)
.GroupBy(g => new
{
myField1 = g.MyField1
})
.Select(s => new
{
count = s.Count(),
myField1 = s.Key.myField1
}).Where(k => k.count == 1).ToList();
I have a survey where the answers are grouped by a Session GUID and a timestamp of the answer:
public class Answer
{
[Key]
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public string Session { get; set; }
public int Numeric { get; set; }
public string Text { get; set; }
}
If the answer is type Numeric (from 1 to 5 stars) the numeric is filled, if the answer is type Text (like a comment), the text is filled.
Example of records:
48 2016-07-15 11:20:14.823 12f68234-fee0-4a3a-88ef-f3977824ed51 5 NULL
49 2016-07-15 11:20:19.550 12f68234-fee0-4a3a-88ef-f3977824ed51 2 NULL
50 2016-07-15 11:20:19.553 12f68234-fee0-4a3a-88ef-f3977824ed51 4 NULL
51 2016-07-15 11:20:19.557 12f68234-fee0-4a3a-88ef-f3977824ed51 3 NULL
52 2016-07-15 11:20:19.560 12f68234-fee0-4a3a-88ef-f3977824ed51 0 gostei bastante!
53 2016-07-15 11:59:59.000 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
54 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
55 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 3 NULL
56 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
57 2016-07-15 12:00:26.297 a143125e-0463-13f9-fc83-48d660c96156 0 Acho que há algumas coisas para melhorar
58 2016-07-15 17:56:00.503 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
59 2016-07-15 17:56:16.617 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
60 2016-07-15 17:56:16.620 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
61 2016-07-15 17:56:16.617 610821d4-5c48-4222-8c49-c19f0dd9182c 4 NULL
62 2016-07-15 17:56:16.637 610821d4-5c48-4222-8c49-c19f0dd9182c 0 Gostei bastante de todo o serviço
The problem is that I can't group by session and ordered by datecreated because they are both distinct records.
The code I have that is not working is:
var sessions = _dbContext.Answers
.Where(p => p.Location.Company.Id == id)
.Where(p => p.Question.Type == QuestionType.Text)
.Where(p => p.Text != "")
.OrderByDescending(p => p.DateCreated)
.Select(p => p.Session)
.Distinct()
.Take(count);
var dto = new List<GetLastCommentsByCountByCompanyIdDTO>();
foreach (var session in sessions)
{
dto.Add(new GetLastCommentsByCountByCompanyIdDTO
{
LocationName = _dbContext.Answers.Where(s => s.Session == session).Select(s => s.Location.Name).FirstOrDefault(),
DateCreated = _dbContext.Answers.Where(s => s.Session == session).Select(s => s.DateCreated).FirstOrDefault(),
Comment = _dbContext.Answers.Where(s => s.Session == session && s.Question.Type == QuestionType.Text).Select(s => s.Text).FirstOrDefault()
});
}
return dto.OrderByDescending(p => p.DateCreated);
Try this one:
var baseQuery = _dbContext.Answers.AsNoTracking()
.Where(p => p.Location.Company.Id == id
&& p.Question.Type == QuestionType.Text
&& p.Text != null
&& p.Text != "")
.GroupBy(g => new { Session = g.Session, Location = g.Location.Name })
.Select(x =>
new
{
Session = x.Key.Session,
LocationName = x.Key.Location,
LastAnswer = x.OrderByDescending(f => f.DateCreated).FirstOrDefault()
})
.Select(x => new GetLastCommentsByCountByCompanyIdDTO
{
LocationName = x.LocationName,
DateCreated = x.LastAnswer.DateCreated,
Comment = x.LastAnswer.Text
})
.OrderByDescending(x => x.DateCreated)
.Take(count);
var res = baseQuery.ToList();
This should give you the answers grouped by Session.
var answersGroupedBySession = _dbContext.Answers
.Where(p => p.Location.Company.Id == id
&& p.Question.Type ==QuestionType.Text
&& p=>!String.IsNullOrEmpty(p.Text))
.GroupBy(g => g.Session, items => items,
(key, value) =>
new {
Session = key,
Answers = value.OrderByDescending(f => f.DateCreated)
}).ToList();
The variable answersGroupedBySession will be a list of annonymous objects with 2 properties Session and Answers. For each session, answers will be grouped (and sorted by date) under the Answers property. If you do not prefer the annonymous object, you may create a dto for that.
public class GroupedAnswers
{
public string Session {set;get;}
public IEnumerable<Answer> Answers {set;get;}
}
and use that in the projection part.
.GroupBy(g => g.Session, items => items, (key, value) => new GroupedAnswers
{
Session = key,
Answers = value.OrderByDescending(f => f.DateCreated)
}).ToList();
I have an ICollection of records (userID,itemID,rating) and an IEnumerable items
for a specific userID and each itemID from a set of itemIDs, i need to produce a list of the users rating for the items or 0 if no such record exists. the list should be ordered by the items.
example:
records = [(1,1,2),(1,2,3),(2,3,1)]
items = [3,1]
userID = 1
result = [0,2]
my attempt:
dataset.Where((x) => (x.userID == uID) & items.Contains(x.iID)).Select((x) => x.rating);
it does the job but it doesn't return 0 as default value and it isnt ordered...
i'm new to C# and LINQ, a pointer in the correct direction will be very appreciated.
Thank you.
This does the job:
var records = new int[][] { new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, new int[] { 2, 3, 1 } };
var items = new int[] { 3, 1 };
var userId = 1;
var result = items.Select(i =>
{
// When there's a match
if (records.Any(r => r[0] == userId && r[1] == i))
{
// Return all numbers
return records.Where(r => r[0] == userId && r[1] == i).Select(r => r[2]);
}
else
{
// Just return 0
return new int[] { 0 };
}
}).SelectMany(r => r); // flatten the int[][] to int[]
// output
result.ToList().ForEach(i => Console.Write("{0} ", i));
Console.ReadKey(true);
How about:
dataset.Where((x) => (x.userID == uID)).Select((x) => items.Contains(x.iID) ? x.rating : 0)
This does the job. But whether it's maintainable/readable solution is topic for another discussion:
// using your example as pseudo-code input
var records = [(1,1,2),(1,2,3),(2,3,1)];
var items = [3,1];
var userID = 1;
var output = items
.OrderByDescending(i => i)
.GroupJoin(records,
i => i,
r => r.ItemId,
(i, r) => new { ItemId = i, Records = r})
.Select(g => g.Records.FirstOrDefault(r => r.UserId == userId))
.Select(r => r == null ? 0 : r.Rating);
How this query works...
ordering is obvious
the ugly GroupJoin - it joins every element from items with all records that share same ItemId into annonymous type {ItemId, Records}
now we select first record for each entry that matches userId - if none is found, null will be returned (thanks to FirstOrDefault)
last thing we do is check whether we have value (we select Rating) or not - 0
How about this. your question sounds bit like an outer join from SQL, and you can do this with a GroupJoin, SelectMany:
var record1 = new Record() { userID = 1, itemID = 1, rating = 2 };
var record2 = new Record() { userID = 1, itemID = 2, rating = 3 };
var record3 = new Record() { userID = 2, itemID = 3, rating = 1 };
var records = new List<Record> { record1, record2, record3 };
int userID = 1;
var items = new List<int> { 3, 1 };
var results = items
.GroupJoin( records.Where(r => r.userID == userID), item => item, record => record.itemID, (item, record) => new { item, ratings = record.Select(r => r.rating) } )
.OrderBy( itemRating => itemRating.item)
.SelectMany( itemRating => itemRating.ratings.DefaultIfEmpty(), (itemRating, rating) => rating);
To explain what is going on
For each item GroupJoin gets the list of rating (or empty list if no rating) for the specified user
OrderBy is obvious
SelectMany flattens the ratings lists, providing a zero if the ratings list is empty (by DefaultIfEmpty)
Hope this makes sense.
Be aware, if there is more than one rating for an item by a user, they will all appear in the final list.