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

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.

Related

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

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.

iqueryable with sub query as outer join

I have a sample to look into Async calls and i need to get a count from the sub query. I know how to write this in a TSQL query but i am bit confused with the iqueryable use.
Here is what i currently have. I am getting the users and then get the count inside a loop. How can i do the loop part in the first query?
public static async Task GetUsers(this List<UserViewModel> users)
{
var db = ApplicationDbContext.Create();
users.AddRange(await (from u in db.Users
select new UserViewModel
{
Id = u.Id,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName
}).OrderBy(o => o.Email).ToListAsync());
if (users.Any())
{
foreach(var user in users)
{
user.SubscriptionsCount = await (from us in db.UserSubscriptions
join s in db.Subscriptions on us.SubscriptionId equals s.Id
where us.UserId.Equals(user.Id)
select us).CountAsync();
}
}
}
Could be handled in one of the following two ways. I have picked #2 for my sample.
1 : with sub query
var singleQuery = from u in db.Users
join sub in (from us in db.UserSubscriptions
join s in db.Subscriptions on us.SubscriptionId equals s.Id
group us by us.UserId into countGroup
select new { Count = countGroup.Count(), UserId = countGroup.Key })
on u.Id equals sub.UserId into sub1
from subR in sub1.DefaultIfEmpty()
select new UserViewModel
{
Id = u.Id,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName,
SubscriptionsCount = subR.Count == null ? 0 : subR.Count
};
var siteUsersSub = await (query).OrderBy(o => o.Email).ToListAsync();
2: Composing from sub queries
var subQuery = from us in db.UserSubscriptions
join s in db.Subscriptions on us.SubscriptionId equals s.Id
group us by us.UserId into countGroup
select new { Count = countGroup.Count(), UserId = countGroup.Key };
var query = from u in db.Users
join sq in subQuery on u.Id equals sq.UserId into sq1
from sqR in sq1.DefaultIfEmpty()
select new UserViewModel()
{
Id = u.Id,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName,
SubscriptionsCount = sqR.Count == null ? 0 : sqR.Count
};
var siteUsers = await(query).OrderBy(o => o.Email).ToListAsync();

Wrong LINQ request

Please look at my code below
var result = (from c in db.vCompanies
where id == c.id
from user in db.Users
select new ViewCompanyModel()
{
Id = c.id,
Descripton = c.Descripton,
Name = c.Name,
ImageLink = c.ImageLink,
AdminEmail = c.Email,
Users = db.eUsers
.Where(o => o.CompanyName.Equals(c.Name))
.Select(o => new UserManageViewModel.UserViewModel
{
Id = o.UserId,
Name = o.LastName,
Email = o.Email,
Company = o.CompanyName,
ListOfRoles = user.AspNetUsers.AspNetRoles.Select(x=>x.Name).ToList()
})
}).FirstOrDefault();
I receive not correct data in ListOfRoles - I receive data only of first user.
I tried to add something like this
Where(x=>x.UserId == o.UserId)
I also tried change for this
ListOfRoles = db.Users.Where(x=>x.UserId == o.UserId).Select(x=>x.AspNetUsers.AspNetRoles)
But in this case I can't select x.Name.
I am doing something wrong.
Please advise me.
if(result != null)
{
foreach (var user in result.Users)
{
var roles = db.Users.Where(x => x.UserId == user.Id).Select(x => x.AspNetUsers.AspNetRoles).FirstOrDefault();
user.ListOfRoles = roles.Select(x => x.Name).ToList();
}
}
This is the answer! It's work!

LINQ - Limit result based on count returned by custom function

I have the following code and want to return a limited subset of this query in LINQ. The limited subset will take u.ID as an argument to the function and count the number of records associated with u.ID from another table.
So far, this is what I have.
var res = from u in db.Users
where id == u.WorkGroupID && jobCount(u.ID) > 0
select
new
{
ArtistID = u.ID,
ArtistName = u.FirstName + " " + u.LastName
};
How can I modify this query to limit the number of returned records based on a count value associated with each u.ID?
EDIT:
New Query Below. Last line returns to caller a list from the last LINQ query.
var res = from u in db.Users
where id == u.WorkGroupID
select
new
{
// SELF
ArtistID = u.ID,
ArtistName = u.FirstName + " " + u.LastName
};
var res2 = res.ToList<dynamic>();
var res3 = from row in res2.AsEnumerable()
where jobCount(row.ArtistID) > 0
select new
{
row.ArtistName,
row.ArtistID
};
return res3.ToList<dynamic>();
Use a group join:
from u in db.Users
join o in db.Other on u.ID equals o.UserID into grp
where grp.Any()
select new
{
ArtistID = u.ID,
ArtistName = u.FirstName + " " + u.LastName
};

How to generate SQL COUNT(*) OVER (PARTITION BY {ColumnName}) in LINQ-to-SQL?

Is it possible to generate the following SQL query by using LINQ-to-SQL query expression or method chains which is defer-executable?
Data Structure
alt text http://www.freeimagehosting.net/uploads/e062a48837.jpg
Select Distinct ClassRoomTitle,
Count(*) Over(Partition By ClassRoomNo) As [No Sessions Per Room],
TeacherName,
Count(*) Over(Partition By ClassRoomNo, TeacherName) As [No Sessions Per Teacher] From ClassRoom
Expected Result
alt text http://www.freeimagehosting.net/uploads/47a79fea8b.jpg
Try this:
var vGroup = from p in ClassRoom
group p by new { p.ClassRoomNo, p.TeacherName }
into g
from i in g
select new
{
i.ClassRoomNo,
i.TeacherName,
i.ClassRoomTitle,
NoSessionsPerTeacher = g.Count()
};
var pGroup = from p in vGroup
group p by new { p.ClassRoomNo }
into g
from i in g
select new
{
i.ClassRoomTitle,
NoSessionsPerRoom = g.Count(),
i.TeacherName,
i.NoSessionsPerTeacher
};
var result = pGroup.OrderBy(p => p.ClassRoomNo).ThenBy(p => p.TeacherName);
I didn't test the above but you can check my original code in case I got something wrong in the rewrite:
var vGroup = from p in Products
group p by new { p.ProductId, p.VariantId }
into g
from i in g
select new
{
i.ProductId,
i.VariantId,
VariantCount = g.Count()
};
var pGroup = from p in vGroup
group p by new { p.ProductId }
into g
from i in g
select new
{
i.ProductId,
ProductCount = g.Count(),
i.VariantId,
i.VariantCount
};
var result = pGroup.OrderBy(p => p.ProductId).ThenBy(p => p.VariantId);
var classRooms = from c in context.ClassRooms
group c by new {c.ClassRoomNo} into room
select new {
Title = room.First().ClassRoomTitle,
NoSessions = room.Count(),
Teachers = from cr in room
group cr by new {cr.TeacherName} into t
select new {
Teacher = t.Key,
NoSessions = t.Count()
}
};
A bit more structured than the posted expected result, but I find that to be better.
You can always use SelectMany if you want to go back to unstructured:
var unstructured = classRooms
.SelectMany(c=> c.Teachers.Select( t=> new {
Title = c.Title,
SessionsPerRoom = c.NoSessions,
Teacher = t.Teacher,
SessionsPerTeacher = t.NoSessions
});

Categories

Resources