Linq query select property list with join - entity-framework

I'm trying to get a list in a linq with joins and get all events for specific user.
This is the query I have so far:
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = //get all events in Events table for the runnerId
};
Events should be all entries from Events table for that runner, based on their id which is joined in the RunnerEvents table. How can I get that?

Something like this?
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = r.Events.Select(e => new Event { }).ToList()
};

Related

LINQ query for joining multiple tables and get comma separated values in single row

I have below tables with the values.
Account:
Id
Name
Email
101
Nasir Uddin
nasir#email.com
Role:
Id
Title
101
Admin
102
Operator
AccountRole:
AccountId
RoleId
101
101
101
102
Now I want to write a linq to have the result like below:
UserAccount
AccountId
Name
Email
Roles
101
Nasir Uddin
nasir#email.com
Admin, Operator
To get the above result I have written the below query in LINQ. But it does not get the expected result.
var userAccount1 = (from account in _db.Accounts
join accountRole in _db.AccountRoles on account.Id equals accountRole.AccountId
join role in _db.Roles on accountRole.RoleId equals role.Id
select new UserAccountInfo
{
AccountId = account.Id,
Name = account.UserFullName,
Email = account.Email,
Roles = string.Join(",", role.Title)
});
At last I found my answer. The results can be achieved in different ways. Examples are given below:
var answer1 = (from account in userAccounts
join accountRole in accountRoles on account.Id equals accountRole.AccountId
join role in roles on accountRole.RoleId equals role.Id
select new UserAccount
{
AccountId = account.Id,
Name = account.Name,
Email = account.Email,
Roles = role.Title
}).ToList().GroupBy(x => new { x.AccountId, x.Name, x.Email }).Select(y => new UserAccount
{
AccountId = y.Key.AccountId,
Name = y.Key.Name,
Email = y.Key.Email,
Roles = string.Join(", ", y.Select(a => a.Roles))
}).ToList();
----------------------------------------------------------------------------
var answer2 = (from account in userAccounts
join accountRole in accountRoles on account.Id equals accountRole.AccountId
join role in roles on accountRole.RoleId equals role.Id
group new { account, role } by new { account.Id, account.Name, account.Email } into ag
select new UserAccount
{
AccountId = ag.Key.Id,
Name = ag.Key.Name,
Email = ag.Key.Email,
Roles = string.Join(", ", ag.Select(x=> x.role.Title))
}).ToList();
----------------------------------------------------------------------------
var answer3 = (from account in userAccounts
let roles1 = from accountRole in accountRoles
join role in roles on accountRole.RoleId equals role.Id
where accountRole.AccountId == account.Id
select role
select new UserAccount
{
AccountId = account.Id,
Name = account.Name,
Email = account.Email,
Roles = string.Join(", ", roles1.Select(x => x.Title))
}).ToList();
The below gives you the expected result using Lambda Expression (not Query Expression) based on the information provided in your post (and some assumptions since I could not find e.g. UserFullName in any of your tables)
Note: I'm also convinced there is a more efficient way to do this, but it is a starting point if nothing else.
(Here is working .NET Fiddle of the below: https://dotnetfiddle.net/aGra15):
// Join the AccountRoles and Roles together and group all Titles for
// a given AccountId together
var groupedAccountRoles = AccountRoles.GroupJoin(Roles, i => i.RoleId, o => o.Id, (o, i) => new {o, i})
.Select(x => new {AccountId = x.o.AccountId, Titles = string.Join(",", x.i.Select(y => y.Title))});
// Perform another GroupJoin to group by AccountId and Join to groupedAccountRoles table. Then `string.Join()`
var userAccount1 = Accounts.GroupJoin(AccountRoles, acc => acc.Id, accrol => accrol.AccountId,
(o, i) => new {o, UserAccountRoles = i})
.GroupJoin(groupedAccountRoles, ii => ii.o.Id, oo => oo.AccountId,
(ii, oo) => new UserAccountInfo
{
AccountId = ii.o.Id,
Email = ii.o.Email,
Name = ii.o.Name,
Roles = string.Join(",", oo.Select(x => x.Titles))
});
This will give the following output:

LINQ - Conditional Join

I have a condition where joining table have a condition. Let's say I have a table called Mapper Student Teacher where Mapper table have a column named AcNoId which contains a id from both table Student and Teacher. The table structure is
Mapper
Student
Teacher
TestOption is a enum and is defined as a
public enum TestOption
{
Teacher = 1,
Student = 2
}
Now I have a condition where if TestOption is a type of Student it should perform a join with Student table and if is a type of Teacher it should perform a join with Teacher table
This is how I have tried so far
(from m in _context.Mapper
where m.TestOption == TestOption.Student
join s in _context.Student
on m.AcNoId equals s.Id into tempStudent
from st in tempStudent.DefaultIfEmpty()
where m.TestOption == TestOption.Teacher
join t in _context.Teacher
on m.AcNoId equals t.Id into tempTeacher
from ta in tempTeacher.DefaultIfEmpty()
select new
{
Type = m.TestOption.ToString(),
Student = st.StudentName ?? string.Empty,
Teacher = ta.TeacherName ?? string.Empty
}).ToList();
Instead of conditional join this query perform a following query on SQL Profiler
exec sp_executesql N'SELECT [m].[TestOption], COALESCE([s].[StudentName], #__Empty_0) AS [Student], COALESCE([t].[TeacherName], #__Empty_1) AS [Teacher]
FROM [Mapper] AS [m]
LEFT JOIN [Student] AS [s] ON [m].[AcNoId] = [s].[Id]
LEFT JOIN [Teacher] AS [t] ON [m].[AcNoId] = [t].[Id]
WHERE ([m].[TestOption] = 2) AND ([m].[TestOption] = 1)',N'#__Empty_0 nvarchar(4000),#__Empty_1 nvarchar(4000)',#__Empty_0=N'',#__Empty_1=N''
How can I do this????
You can use the below code, with no need to use join or where statements on _context.Mapper:
(from m in _context.Mapper
select new
{
Type = m.TestOption.ToString(),
Student = _context.Student
.FirstOrDefault(s =>
m.TestOption == TestOption.Student &&
s.Id == m.AcNoId) ?? string.Empty,
Teacher = _context.Teacher
.FirstOrDefault(t =>
m.TestOption == TestOption.Teacher &&
t.Id == m.AcNoId) ?? string.Empty,
})
.ToList();

Entitty Framework outer join works until I add a where clause

This works:
var query = from q in _context.Questions
join ua in _context.UserAnswers on q.ID equals ua.QuestionId
into temp
from ua in temp.DefaultIfEmpty()
select new ProfileViewModel
{
Question = new Question { ID = q.ID, Text = q.Text },
Answer = (ua == null ? "" : ua.AnswerText)
};
I get a collection of FIVE blank userAnswer (there are FIVE questions) for the view. Great!
Now I need to add a condition to look for a specific user:
var query = from q in _context.Questions
join ua in _context.UserAnswers on q.ID equals ua.QuestionId
into temp
from ua in temp.DefaultIfEmpty()
where ua.User.Id == userId
select new ProfileViewModel
{
Question = new Question { ID = q.ID, Text = q.Text },
Answer = (ua == null ? "" : ua.AnswerText)
};
But now there are ZERO items in the collection instead of 5.
How do I get it to send my FIVE items like the first example ?

How can I fix linq query to select count of ids with group by?

I want to create this SQL query to linq:
SELECT
COUNT(m.FromUserId) AS Messages,
m.FromUserId AS UserId
FROM
dbo.ChatMessages m
INNER JOIN
dbo.ChatMessagesRead mr ON mr.ChatMessageId = m.ChatMessageId
WHERE
m.ToUserId = #toUserId
GROUP BY
m.FromUserId
I have tried create following linq query:
var messages = from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
var messagesList = messages.ToList();
But this doesn't work.
How can I fix this linq query?
I get this exception:
Expression of type 'System.Func2[Microsoft.Data.Entity.Query.EntityQueryModelVisitor+TransparentIdentifier2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' cannot be used for parameter of type 'System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' of method 'System.Collections.Generic.IEnumerable1[System.Linq.IGrouping2[System.Int32,Project.BL.ChatMessages.ChatMessages]] _GroupBy[<>f__AnonymousType12,Int32,ChatMessages](System.Collections.Generic.IEnumerable1[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead]], System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32], System.Func2[<>f__AnonymousType1`2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],Project.BL.ChatMessages.ChatMessages])'"
I'm facing the same issue and I've found that there is an opened issue on the Entity Framework Core bugtracker
The only workaround for now seems to split the request in two.
var filtered = (from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
select m).ToList();
var messages = from m in filtered
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
you can try this
var res = ctx.MyTable // Start with your table
.GroupBy(r => r.id) / Group by the key of your choice
.Select( g => new {Id = g.Key, Count = g.Count()}) // Create an anonymous type w/results
.ToList(); // Convert the results to List
Your code should work. However I created another version of your query using extension methods.
var messages =
_dbContext
.ChatMessages
.Where(message => message.ToUserId == userId)
.Join(
_dbContext.ChatMessageRead,
message => message.ChatMessageId,
readMessage => readMessage.ChatMessageId,
(m, mr) => m.FromUserId
)
.GroupBy(id => id)
.Select(group =>
new
{
UserId = group.Key,
Messages = group.Count()
}
);
Could you please try it if it also throws the same exception or not?

Left join after a into group in Linq using entity framework (core)

Problem: I would like to generate the exact sql below in the desired output using linq syntax (Entity framework 7)
The goal of the question is to generate the exact sql below!
Desired Output
select a.AppUserId, u.Email, a.FirstName, a.MiddleName, a.LastName, a.IsInternal, a.AspNetUserId, a.PictureLink, a.SignatureLink, a.PhoneNumber, a.Extension, a.FaxNumber, a.MobileNumber, a.Skype, r.Name as 'Role', a.SupervisorId, a.BackUpId, a.HasAutoAssignClaims, a.IsActive
from AppUser a
join AspNetUsers u on a.AspNetUserId = u.Id
left join AspNetUserRoles ur on u.Id = ur.UserId
left join AspNetRoles r on ur.RoleId = r.Id
I have only being able to get the exact same sql but with inner joins. I can't seem to get the two left joins. The code below here how I was able to generate the inner joins and also a fail attempt at generating the left joins.
SELECT [a].[AppUserId], [a].[FirstName], [a].[MiddleName], [a].[LastName], [a].[IsInternal], [a].[AspNetUserId], [a].[PictureLink], [a].[SignatureLink], [a].[PhoneNumber], [a].[Extension], [a].[FaxNumber], [a].[MobileNumber], [a].[Skype], [a].[SupervisorId], [a].[BackUpId], [a].[HasAutoAssignClaims], [a].[IsActive]
FROM [AppUser] AS [a]
INNER JOIN [AspNetUsers] AS [b] ON [a].[AspNetUserId] = [b].[Id]
INNER JOIN [AspNetUserRoles] AS [c] ON [b].[Id] = [c].[UserId]
INNER JOIN [AspNetRoles] AS [d] ON [a].[RoleId] = [d].[Id]
Code with inner join works but I want left joins....:
var query = (
//INNER JOIN
from a in _dbCtx.AppUser
join b in _dbCtx.Users
on a.AspNetUserId equals b.Id
////LEFT JOIN
join c in _dbCtx.UserRoles
on b.Id equals c.UserId
// // //LEFT JOIN (if you wanted right join the easiest way is to flip the order of the tables.
join d in _dbCtx.Roles
on a.RoleId equals d.Id
select new
{
AppUserId = a.AppUserId,
//Email = b.Email,
FirstName = a.FirstName,
MiddleName = a.MiddleName,
LastName = a.LastName,
IsInternal = a.IsInternal,
AspNetUserId = a.AspNetUserId,
PictureLink = a.PictureLink,
SignatureLink = a.SignatureLink,
PhoneNumber = a.PhoneNumber,
Extension = a.Extension,
FaxNumber = a.FaxNumber,
MobileNumber = a.MobileNumber,
Skype = a.Skype,
//Role = d.Name != null ? string.Empty :d.Name ,
SupervisorId = a.SupervisorId,
BackUpId = a.BackUpId,
HasAutoAssignClaims = a.HasAutoAssignClaims,
IsActive = a.IsActive
}).ToList();
Code with Left Join...which I am missing some concept doesnt work
what doesnt work is that on g2.RoleId equals d.Id into group3 line the g2 is not available. So how would I make c.RoleId available for my next left join? Basically after you group something you can no longer use it apparently.
var LeftJoin= (
//INNER JOIN
from a in _dbCtx.AppUser
join b in _dbCtx.Users
on a.AspNetUserId equals b.Id
////LEFT JOIN
join c in _dbCtx.UserRoles
on b.Id equals c.UserId into group2
from g2 in group2.DefaultIfEmpty() //makes it left join
join d in _dbCtx.Roles
on g2.RoleId equals d.Id into group3
from g3 in group3.DefaultIfEmpty()
select new
{
AppUserId = a.AppUserId,
Email = b.Email,
FirstName = a.FirstName,
MiddleName = a.MiddleName,
LastName = a.LastName,
IsInternal = a.IsInternal,
AspNetUserId = a.AspNetUserId,
PictureLink = a.PictureLink,
SignatureLink = a.SignatureLink,
PhoneNumber = a.PhoneNumber,
Extension = a.Extension,
FaxNumber = a.FaxNumber,
MobileNumber = a.MobileNumber,
Skype = a.Skype,
Role = g3.Name != null ? string.Empty :g3.Name ,
SupervisorId = a.SupervisorId,
BackUpId = a.BackUpId,
HasAutoAssignClaims = a.HasAutoAssignClaims,
IsActive = a.IsActive
}).ToList();
In case anyone comes to this question, thinking it hasn't been answered, the answer is currently buried in a comment chain after the question. I'm merely paraphrasing the key comments here.
The problem is that Entity Framework 7 is currently a release candidate and has some bugs. One of these bugs (Left Join doesn't work if the filter is composed on top) is causing the failure of the left join noted by the OP.
The solution, for now, is to revert to Entity Framework 6, or temporarily use a stored procedure or inline SQL until the bug is fixed.
If i understood you properly you problem is that EF is not generating LEft join. If yes then solution is pretty simple your Entities should have nullable property for instance
public class SomeClass
{
public int Id { get; set; }
public int? CategoryId { get; set; }
public Category Category {get;set:}
}
One option which is in my head
_dbCtx.SqlQuery<T>(SqlStringHEre).ToList()
Other option, and aspnet tables i would do it differently
var query = _dbCtx.AppUser
.Include(apu=>apu.AspNetUser)
.Include(apu=>apu.AspNetUser.Roles)
.Include(apu=>apu.AspNetUser.Roles.Select(r=>r.Role))
.ToList();
but here is problem as we talk in comments that IdentityUserRole does not have refference to role so lets fix that.
create class
public class UserToRole : IdentityUserRole<int>
{
public Role Role { get; set; }
}
Then extend your user class
public class YourUser : IdentityUser<int, IdentityUserLogin<int>, UserToRole, IdentityUserClaim<int>>
Now you can do what you want
_db.Users.Select(u=>u.Roles.Select(r=>r.Role.Name))
This is a work around that generates the data with a very bad query. It generate a series of calls to the database that does yield the same result set. However, it is definitely not the best query for the job. I am still waiting for RC2 and I will update the answer.
var query = (
//INNER JOIN
from a in _dbCtx.AppUser
join b in _dbCtx.Users
on a.AspNetUserId equals b.Id
from c in _dbCtx.UserRoles
.Where(x => b!=null && x.UserId == b.Id)
.DefaultIfEmpty()
from d in _dbCtx.Roles
.Where(x => a !=null && x.Id == a.RoleId)
.DefaultIfEmpty()
select new
{
AppUserId = a.AppUserId,
Email = b.Email,
FirstName = a.FirstName,
MiddleName = a.MiddleName,
LastName = a.LastName,
IsInternal = a.IsInternal,
AspNetUserId = a.AspNetUserId,
PictureLink = a.PictureLink,
SignatureLink = a.SignatureLink,
PhoneNumber = a.PhoneNumber,
Extension = a.Extension,
FaxNumber = a.FaxNumber,
MobileNumber = a.MobileNumber,
Skype = a.Skype,
Role = d.Name != null ? string.Empty :d.Name ,
SupervisorId = a.SupervisorId,
BackUpId = a.BackUpId,
HasAutoAssignClaims = a.HasAutoAssignClaims,
IsActive = a.IsActive
}).ToList();