How to tackle complex LINQ queries with outer join? - c#

In my database I have roles and users, I also have user roles to tie the 2 together.
The problem is trying to get all users with their roles (if they have any, which they may not).
I use this query:
return (from ur in db.UserRoles
join r in db.Roles on ur.RoleID equals r.ID
group r by ur.UserProfileID into ugr
join u in db.UserProfiles on ugr.Key equals u.ID
select new UserModel() {
ID = u.ID,
Username = u.UserName,
IsLockedOut = u.IsLockedOut,
LastLoginDate = u.LastLoginDate,
UserRoles = (from r in ugr
select new RoleModel() {
Name = r.Name,
ID = r.ID
}).ToList()
}).ToList();
This works for users who have at least one role, but I also want users who do not have roles.
I'm currently trying to use http://msdn.microsoft.com/en-us/library/bb397895.aspx DefaultIfEmtpy(), but I don't know how and where to place it, meaning however I try my code does not compile.
How do I get all my Users, even if they do not have any UserRoles linked to them?

Get the users first and include their roles from then
return db.UserProfiles
.Include(up => up.UserRoles)
.Select(u => new UserModel() {
ID = u.ID,
Username = u.UserName,
IsLockedOut = u.IsLockedOut,
LastLoginDate = u.LastLoginDate,
UserRoles = u.Roles
.Select(r => new RoleModel() {
Name = r.Name,
ID = r.ID
})
})
.ToList();
Update based on comments
return db.UserProfiles
.Include(up => up.UserRoles)
.Include("UserRoles.Roles") // <-- Added further include
.Select(u => new UserModel() {
ID = u.ID,
Username = u.UserName,
IsLockedOut = u.IsLockedOut,
LastLoginDate = u.LastLoginDate,
// Modified this to use joining table
UserRoles = u.UserRoles
.Select(ur => new RoleModel() {
Name = ur.Role.Name,
ID = ur.RoleID
})
})
.ToList();

Related

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();

How to flatten tables using LINQ

I've this query:
var usersByBranch = (from u in _db.VRT_User
join urb in _db.VRT_UserRoleBranch on u.UserId equals urb.UserId
join r in _db.VRT_Role on urb.RoleId equals r.RoleId
where branches.Contains(urb.BranchId)
select new UserRoleBranchModel
{
UserId = u.UserId,
BranchId = urb.BranchId,
RoleId = urb.RoleId,
RoleName = r.RoleName
});
In this query, for the same userId, the roleId (1-4) and RoleName with the same BranchId are returned separately.
I'd like to flatten the rows, so that a row with the same userId contains all the RoleId and RoleName within the same BranchId.
Your help is greatly appreciated.
Not sure what you mean by contains, but you can't use the same UserRoleBranchModel to hold multiple roles, so an anonymous object will do the job:
var usersByBranch = (from u in _db.VRT_User
join urb in _db.VRT_UserRoleBranch on u.UserId equals urb.UserId
join r in _db.VRT_Role on urb.RoleId equals r.RoleId
where branches.Contains(urb.BranchId)
group r by new { urb.UserId, urb.BranchId } into rg
select new {
UserId = rg.Key.UserId,
BranchId = rg.Key.BranchId,
Roles = rg.Select(r => r)
});
var usersByBranch = (from u in _db.VRT_User
join urb in _db.VRT_UserRoleBranch on u.UserId equals urb.UserId
join r in _db.VRT_Role on urb.RoleId equals r.RoleId
where branches.Contains(urb.BranchId)
group u by u.UserId into g
select new UserRoleBranchModel
{
UserId = g.Key,
BranchId = g.First().BranchId,
RoleId = g.First().RoleId,
RoleName = g.First()RoleName
});

Using Linq how do get a list of users and their last login from two tables

I'm trying to get a list of users and and their last login date.
I need to display the following columns
User Name
First Name
Last Name
Last Login Date
Role
Is Active
I'm having difficulty because the data is split between two tables:
User table:
Users
UserId
UserName
FirstName
LastName
Role
IsActive
And LogonHistory table
LogonHistory
Id
Username
LoginDate
I have tried using Join,group, and maxbut it only lets me use properties from the logonhistory table.
Here's an example of my joinquery:
var users = db.Users
.Join(db.LogonHistory, user => user.UserName, logon => logon.Username, (user, logon) => new UserSearchResults
{
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
IsActive = user.Active,
LoginHistory = logon.LoginDate
});
Here's an example of my grouping query:
var loginHistory = from l in db.LogonHistory
join u in db.Users on l.Username equals u.UserName
group l by l.Username into grp
let LastLoginDate = grp.Max(d => d.LoginDate)
from l in grp
where l.LoginDate == LastLoginDate
select l;
Can anyone spot what I'm doing wrong, or recommend a better method?
EDIT:
Essentially what I need to do is Join the User table and LogonHistory table on the Username and return the user's details and the latest login date.
var lastlogins = from h in db.LogonHistory
group h by h.UserName into hgroup
select new
{
UserName = hgroup.Key,
LastLoginDate = hgroup.Max(x => x.LoginDate)
};
var query = from u in db.Users
join h in lastlogins on u.UserName equals h.Username
select new
{
u.UserName,
u.FirstName,
u.LastName,
u.Role,
u.IsActive,
h.LastLoginDate
};
You should use navigation properties instead. Your query would be like:
var loginHistory= db.Users.Select(user => new UserSearchResults
{
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
IsActive = user.Active,
LastLoginDate=user.LogonHistory
.OrderByDescending(e=>e.LoginDate)
.FirstOrDefault().LoginDate
});
if you are using EntityFramework :
var loginHistory = db.Users.select(e => new {
e.UserName,
e.FirstName,
e.LastName,
e.Role,
e.IsActive,
e.LogonHistory.OrderByDescending(ee=>ee.LoginDate).FirstOrDefault().LoginDate
});

Select properties for particular entities LINQ

You can probably see the result I want to get. It's easy using loop, but I can't understand how to achieve such result using LINQ extension methods
I have two contexts that target one DB. ApplicationUser is authentication class, and profileDTO profile info that I get from same DB.
ProfileDTO properties: string Id, string FirstName, string LastName
Both tables share same ID but are not connected neither through navigation properties nor any references in the DB.
IEnumerable<ViewModels.User.IndexViewModel> model;
IEnumerable<Models.ApplicationUser> users;
var profilesDtos = _profileService.GetAll();
using (var context = new Models.ApplicationDbContext())
{
users = context.Users.ToList();
}
model = users.Select(user =>
new ViewModels.User.IndexViewModel
{
Id = user.Id,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
LockedOutTill = user.LockoutEndDateUtc ?? default(DateTime),
Roles = UserManager.GetRoles(user.Id)
});
foreach (var user in model)
{
var userProfile = profilesDtos.FirstOrDefault(o => o.Id == user.Id);
if (userProfile != null)
{
user.FirstName = userProfile.FirstName;
user.LastName = userProfile.LastName;
}
};
I want to get all users but with Names set only in those who have profiles.
You can use left join in Linq, like below -
IEnumerable<ViewModels.User.IndexViewModel> model;
IEnumerable<Models.ApplicationUser> users;
var profilesDtos = _profileService.GetAll();
using (var context = new Models.ApplicationDbContext())
{
users = context.Users.ToList();
}
model = (from u in users
join p in profilesDtos on u.Id equals p.Id into tempTbl
from up in tempTbl.DefaultIfEmpty()
select new ViewModels.User.IndexViewModel
{
Id = u.Id,
Email = u.Email,
PhoneNumber = u.PhoneNumber,
LockedOutTill = u.LockoutEndDateUtc ?? default(DateTime),
Roles = UserManager.GetRoles(u.Id),
FirstName = up!= null? up.FirstName : string.Empty;
LastName = up!= null? up.LastName : string.Empty;
}).ToList();
First of all I would suggest to update your context to setup such property. If you can't do this use JOIN:
var result =
from user in context.Users
join profile in userProfiles on user.ID equals profile.ID
select new ViewModels.User.IndexViewModel {
Id = user.Id,
FirstName = profile.FirstName,
...
}
As a solution, you can just join them.
MSDN
Plus DefaultIfEmpty statement.

lambda expression join multiple tables with select and where clause

I have three table many to many relationship I have joined the three table and select the value I want but now I need to select one row from the query result by where by specifying the id this is my three table
And this is the query using LINQ lambda expression :
DataBaseContext db = new DataBaseContext();
public ActionResult Index()
{
var UserInRole = db.UserProfiles.
Join(db.UsersInRoles, u => u.UserId, uir => uir.UserId,
(u, uir) => new { u, uir }).
Join(db.Roles, r => r.uir.RoleId, ro => ro.RoleId, (r, ro) => new { r, ro })
.Select(m => new AddUserToRole
{
UserName = m.r.u.UserName,
RoleName = m.ro.RoleName
});
return View(UserInRole.ToList());
}
the result will be like that using sql query
sql query
select *
from UserProfile u join webpages_UsersInRoles uir on u.UserId = uir.UserId
join webpages_Roles r on uir.RoleId = r.RoleId
result of the sql query
now i use anther sql query to filter the result of previews sql query by where and set the condition to where u.UserId = 1 to only give me back the user with the id 1 like that
select *
from UserProfile u join webpages_UsersInRoles uir on u.UserId = uir.UserId
join webpages_Roles r on uir.RoleId = r.RoleId
where u.UserId = 1
and the result of this sql query
so how can i add the where clause to my lambda expression to give me the same result as the result of the sql query and thanks for any help
If I understand your questions correctly, all you need to do is add the .Where(m => m.r.u.UserId == 1):
var userInRole = db.UserProfiles.
Join(db.UsersInRoles, u => u.UserId, uir => uir.UserId,
(u, uir) => new { u, uir }).
Join(db.Roles, r => r.uir.RoleId, ro => ro.RoleId, (r, ro) => new { r, ro })
.Where(m => m.r.u.UserId == 1)
.Select (m => new AddUserToRole
{
UserName = m.r.u.UserName,
RoleName = m.ro.RoleName
});
Hope that helps.
I was looking for something and I found this post. I post this code that managed many-to-many relationships in case someone needs it.
var userInRole = db.UsersInRoles.Include(u => u.UserProfile).Include(u => u.Roles)
.Select (m => new
{
UserName = u.UserProfile.UserName,
RoleName = u.Roles.RoleName
});

Categories

Resources