5

So I've been searching Google and SO. Feels like this question has been asked many times, but no answer has helped me but I feel like I'm getting close. However, I'm new to LINQ and Lambda and don't have the knowledge to do what I want.

Desired Result

User                   Roles
-----------------------------------------
John                   Admin
Jane                   Staff, HR, Payroll
MyCoolUserName         User

I got pretty close from this post and this post. Here's what I got so far.

ViewModel:

public class UsersViewModel {
    [Display(Name = "User")]
    public ApplicationUser User { get; set; }

    [Display(Name = "Roles")]
    public string Roles { get; set; }
}

Controller:

Trial #1

This solution returns blanks for the roles, and I had to add this to my web.config file: <roleManager enabled="true" />

public class UsersController : Controller {
    public async Task Index() {
        var allUsers = await db.Users.ToListAsync();
        var users = new List();
        foreach (var user in allUsers) {
            String[] roles = Roles.GetRolesForUser(user.UserName);
            users.Add(new UsersViewModel {User = u, Roles = String.Join(",", roles.ToArray())});
        }
        return View(users);
    }
}

Trial #2

This solution returns one row per user per role, but only returns the RoleId

public class UsersController : Controller {
    public async Task Index() {
        var allUsers = await db.Users.ToListAsync();
        var users = allUsers.Select(u => new UsersViewModel {User = u, Roles = String.Join(",", u.Roles.Select(r => r.RoleId))}).ToList();
        return View(users);
    }
}

Here's what I get for Trial #2 when I change RoleId to RoleName: GIF showing objects in User.Roles

I can tell that in trial #2, u.Roles is linked to the UserRoles table. Logically, I know that what I want is to inner join the Roles table and get the name there.

I hope someone can help me out? Thanks in advance. Sample Project

Community
  • 1
  • 1
RoLYroLLs
  • 3,113
  • 4
  • 38
  • 57
  • var users = allUsers.Select(u => new UserViewModel {UserName = u.UserName, Roles = String.Join(",", u.Roles.Select(r => r.RoleName))}).ToList(); since it's already working, you can try to select role name instead of role id in the 2nd trial – Kien Chu Dec 03 '15 at 06:12
  • @kienct89, thanks for your answer, I was thinking that too, as it's an answer I see everywhere. I guess i should state that I'm using Identity 2.2.1, which may have changed things around. – RoLYroLLs Dec 03 '15 at 23:00
  • @kienct89, Also, I guess you missed the part where I said `I can tell that in trial #2, u.Roles is linked to the UserRoles table. Logically, I know that what I want is to inner join the Roles table and get the name there.` – RoLYroLLs Dec 03 '15 at 23:04
  • that's exactly what the query do. There won't be any "join" in the query because Roles is just a navigation property of User. If you enable "Lazy Loading", you just need perform the select query. If you disable the "Lazy Loading", you need to call Include(Roles) in the query. Hope it helps – Kien Chu Dec 04 '15 at 02:14
  • Ah cool. Not familiar with `Includes()` I'll look into it as I'm currently trying to learn lambda. Also, if you post it as an answer and it works, I'll accept it. Would be nice if you would provide solution for both, that way I can pick whatever fits my project, and so I can learn from example. Thanks! – RoLYroLLs Dec 04 '15 at 02:16

2 Answers2

1

Thanks to @Kienct89 and a discussion we had, I accidentally stumbled upon the answer myself. Here's what I got, and if anyone can or wants to improve on it, please do so.

e.g.: I don't know if it's better to get all the roles into a variable first and iterate over that, you see this in version 1, or not like in version 2.

Version 1

public class UsersController : Controller {
    public async Task Index() {
        var allUsers = await db.Users.ToListAsync();
        var users = allUsers.Select(u => new UsersViewModel {User = u, Roles = String.Join(",", db.Roles.Where(role => role.Users.Any(user => user.UserId == u.Id)).Select(r => r.Name))}).ToList();
        return View(users);
    }
}

Version 2 (preferred?)

public class UsersController : Controller {
    public async Task Index() {
        var allUsers = await db.Users.ToListAsync();

        // set all roles to a variable, so that we don't hit the database for every user iteration
        // is this true?
        var allRoles = await db.Roles.ToListAsync();

        var users = allUsers.Select(u => new UsersViewModel {User = u, Roles = String.Join(",", allRoles.Where(role => role.Users.Any(user => user.UserId == u.Id)).Select(r => r.Name))}).ToList();
        return View(users);
    }
}

I feel like version 2 is more efficient in that it will not hit the database to get the roles for every user. Instead it has the roles in a variable. I'm not sure if I am right, but I would like to be enlightened and learn from anyone who does know.

Community
  • 1
  • 1
RoLYroLLs
  • 3,113
  • 4
  • 38
  • 57
0

You don't need to perform join in your LINQ query because UserRole is just a navigation property of User (so they will be automatically map by EF). The only thing you need to remember is if you disable Lazy Loading, you need to call Include(entity) manually to load the entity into query.

Code sample:

In case you disable "Lazy Loading":

public class UsersController : Controller {
    public async Task Index() {
        var users = allUsers.Include(Roles).Select(u => new UserViewModel {UserName = u.UserName, Roles = String.Join(",", u.Roles.Select(r => r.RoleName))}).ToList();
        return View(users);
    }
}

In case you enable "Lazy Loading":

public class UsersController : Controller {
    public async Task Index() {
        var users = allUsers.Select(u => new UserViewModel {UserName = u.UserName, Roles = String.Join(",", u.Roles.Select(r => r.RoleName))}).ToList();
        return View(users);
    }
}

UPDATE: I haven't seen your model classes yet, but I think you have 3 model classes (Users, Roles, UserRoles) because User and Role have many-to-many relationships (as shown in your gif, Roles only have UserId & RoleId).

public class UsersController : Controller {
    public async Task Index() {
        var users = allUsers.Select(u => new UserViewModel {UserName = u.UserName, Roles = String.Join(",", u.UserRoles.Where(userRole => u.Roles.Select(r => r.RoleId).Contains(userRole.Id)).Select(userRole => userRole.Name)}).ToList();
        return View(users);
    }
}
Kien Chu
  • 4,735
  • 1
  • 17
  • 31
  • Thanks. How do I know if I have Lazy Loading enables or disabled? FYI: this is a brand new MVC 5 project created with VS2013 Update 5 with individual accounts for authentication. – RoLYroLLs Dec 04 '15 at 02:32
  • by default, it's enabled. You can try to look at your dbcontext code. If you cannot find any code like this in the constructor "Configuration.LazyLoadingEnabled = false;", then lazy loading is enabled. – Kien Chu Dec 04 '15 at 02:34
  • Thanks. Then I guess it's enabled. But your Lazy Loading example is exactly the code I have seen in other examples, but it doesn't work. I'll try to create a short gif to show you. And when I say it 'doesn't work' I mean it shows red and i get `cannot resolve symbol RoleName` – RoLYroLLs Dec 04 '15 at 02:36
  • Not sure what you mean, but I actually updated the names to exactly how i'm using in the app. The view model is really a ApplicationUser, but for simplicity of this question I just used the username. i changed it so my GIF example doesn't confuse anyone. – RoLYroLLs Dec 04 '15 at 02:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/96940/discussion-between-rolyrolls-and-kienct89). – RoLYroLLs Dec 04 '15 at 02:59