Querying EF Core identity roles - c#

I'm using .NET Core 3.1 and I want to query the users in my database.
So I modified my ApplicationUser class as follows:
public class ApplicationUser : IdentityUser
{
public ICollection<IdentityUserRole<string>> UserRoles { get; set; }
}
And now I'm trying to query the Users.
var users = from u in DbContext.Users
select new UserViewModel
{
Email = u.Email,
EmailConfirmed = u.EmailConfirmed,
Phone = u.PhoneNumber,
Roles = u.UserRoles.Select(r => r.Role.Name) // Whoops! Won't work
};
The problem is that UserRoles have a RoleId property, but they don't have a Role property.
So how would I get the name of those roles?
Update:
Based on Jawad's comments, I changed my code as follows:
public class ApplicationUser : IdentityUser
{
public ICollection<IdentityRole> Roles { get; set; }
}
And
Users = (from u in DbContext.Users
select new UserViewModel
{
Email = u.Email,
EmailConfirmed = u.EmailConfirmed,
Phone = u.PhoneNumber,
Roles = u.Roles.Select(r => r.Name)
})
.ToList();
I was thinking I couldn't do that because it's a many-to-many relationship. But in fact, it does compile. However, when I run it I get an error.
Invalid column name 'ApplicationUserId'.
I'm not sure where that column name is being referenced. It doesn't appear to be a column name in the database.
Update 2:
Turns out, the error above is because a database migration was pending. However, I didn't want to change the database. As I initially suspected, I can't do ApplicationUser.ICollection<IdentityRole> because there is an intermediate table.
So I got this to run, but it returned no results (because there are no foreign keys from IdentityRole to ApplicationUser.
So, the question stands: how can I query users along with their roles?

So, I don't know why IdentityUserRole doesn't include an IdentityRole property. This seems to be an unnecessary limitation.
In the end, I just changed my query.
var users = await (from u in DbContext.Users
join ur in DbContext.UserRoles on u.Id equals ur.UserId
join r in DbContext.Roles on ur.RoleId equals r.Id
select new
{
u.Email,
u.EmailConfirmed,
u.PhoneNumber,
Role = r.Name
})
.ToListAsync();
Users = (from u in users
group u.Role by u into g
select new UserViewModel
{
Email = g.Key.Email,
EmailConfirmed = g.Key.EmailConfirmed,
Phone = g.Key.PhoneNumber,
Roles = string.Join(", ", g)
})
.ToList();
I couldn't get the group by to work within the query so I'm group it after the data has been retrieved.
Anyway, it works.

You should create a new class called something like ApplicationUserRole that inherits from IdentityUserRole<string> then you just need to define the relationships between ApplicationUser -> ApplicationUserRole and ApplicationRole -> ApplicationUserRole.
modelBuilder.Entity<ApplicationUserRole>(entity =>
{
entity
.HasOne(x => x.Role)
.WithMany(x => x.UserRoles)
.HasForeignKey(x => x.RoleId);
entity
.HasOne(x => x.User)
.WithMany(x => x.UserRoles)
.HasForeignKey(x => x.UserId);
});
You should also replace any references to the Identity classes with your Application classes.
public class ApplicationUserRole : IdentityUserRole<string>
{
public ApplicationUser User { get; set; }
public ApplicationRole Role { get; set; }
}
public class ApplicationUser : IdentityUser<string>
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationRole : IdentityRole<string>
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}

Related

EF Core. Get data from many-many relationship

I'm new to EF Core. I have many-many relationship between 2 tables. In total, I have these 3 tables:
Tenant.
UserTenant.
User.
User has property "email". I want to get all tenants that are related to user by given user email. How to do that?
I would do something like this but I think is bad approach.
var user = await dbContext.Users.FirstAsync(u => u.Email == userEmail);
var userTenants = dbContext.UserTenants.Where(u => u.UserId == user.Id);
etc...
What you could do is to create a Class ex:
public class UserEmailViewModel
{
public int Id { get; set; }
public string Email{ get; set; }
}
and Then
var userEmail = await dbContext.SqlQuery<UserEmailViewModel>("select Id, Email from User U , Tenant T , UserTenant UT where u.id=ut.Userid and t.Id=UT.TenantId ")
Note That you should add Properties to UserEmailViewModel if you want to add column to the query

Get list of user and their roles into a view model [duplicate]

I'm trying to pull out all my Identity users and their associated roles for a user management admin page. I thought this would be reasonably easy but apparently not. I've tried following the following solution: https://stackoverflow.com/a/43562544/5392786 but it hasn't worked out so far.
Here is what I have so far:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public List<IdentityUserRole<string>> Roles { get; set; }
}
DBContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Startup Identity code
services.AddIdentity<ApplicationUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Razor Page where I want to display the list:
public class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> userManager;
public IndexModel(UserManager<ApplicationUser> userManager)
{
this.userManager = userManager;
}
public IEnumerable<ApplicationUser> Users { get; set; }
public void OnGetAsync()
{
this.Users = userManager.Users.Include(u => u.Roles).ToList();
}
}
I get the following error when calling userManager.Users.Include(u => u.Roles).ToList();:
MySql.Data.MySqlClient.MySqlException: 'Unknown column 'u.Roles.ApplicationUserId' in 'field list''
I have now implemented the following solution.
As CodeNotFound pointed out in the comments, IdentityUser used to have a Roles property. This is no longer the case in .NET Core. This comment/issue on GitHub seems to be the current solution for .Net Core. I have attempted to implemented it with the following code:
ApplicationUser
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
ApplicationUserRole
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
ApplicationRole
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
DBContext
public class ApplicationDbContext
: IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
Startup
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Finally, make sure when you're using it that you eagerly load the User's UserRoles, and then the UserRole's Role like so:
this.Users = userManager.Users.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).ToList();
I had an issue where the Role property of each UserRole was null and this was resolved by adding in the .ThenInclude(ur => ur.Role) part.
Microsoft doc on multi-level eager loading: https://learn.microsoft.com/en-us/ef/core/querying/related-data#including-multiple-levels
ASP Core 2.2 update
Inherent from IdentityUserRole<Guid> not string
You may also need to remove the code in the ModelBuilder to get migrations working.
For dotnet core 3.1, I've been using the following general approach.
// _appContext is an instance of IdentityDbContext<ApplicationUser>
_appContext.Users
.SelectMany(
// -- below emulates a left outer join, as it returns DefaultIfEmpty in the collectionSelector
user => _appContext.UserRoles.Where(userRoleMapEntry => user.Id == userRoleMapEntry.UserId).DefaultIfEmpty(),
(user, roleMapEntry) => new { User = user, RoleMapEntry = roleMapEntry })
.SelectMany(
// perform the same operation to convert role IDs from the role map entry to roles
x => _appContext.Roles.Where(role => role.Id == x.RoleMapEntry.RoleId).DefaultIfEmpty(),
(x, role) => new {User = x.User, Role = role})
.ToList() // runs the queries and sends us back into EF Core LINQ world
.Aggregate(
new Dictionary<ApplicationUser, List<IdentityRole>>(), // seed
(dict, data) => {
// safely ensure the user entry is configured
dict.TryAdd(data.User, new List<IdentityRole>());
if (null != data.Role)
{
dict[data.User].Add(data.Role);
}
return dict;
},
x => x);
The SQL this generates is straightforward and reasonable:
SELECT "a"."Id",
"a"."AccessFailedCount",
"a"."ConcurrencyStamp",
"a"."Email",
"a"."EmailConfirmed",
"a"."LockoutEnabled",
"a"."LockoutEnd",
"a"."NormalizedEmail",
"a"."NormalizedUserName",
"a"."PasswordHash",
"a"."PhoneNumber",
"a"."PhoneNumberConfirmed",
"a"."SecurityStamp",
"a"."TwoFactorEnabled",
"a"."UserName",
"a1"."Id",
"a1"."ConcurrencyStamp",
"a1"."Name",
"a1"."NormalizedName"
FROM "AspNetUsers" AS "a"
LEFT JOIN "AspNetUserRoles" AS "a0" ON "a"."Id" = "a0"."UserId"
LEFT JOIN "AspNetRoles" AS "a1" ON "a0"."RoleId" = "a1"."Id"
Reference comment
First is the code to get data
public async Task<IEnumerable<AccountViewModel>> GetUserList()
{
var userList = await (from user in _context.Users
select new
{
UserId = user.Id,
Username = user.UserName,
user.Email,
user.EmailConfirmed,
RoleNames = (from userRole in user.Roles //[AspNetUserRoles]
join role in _context.Roles //[AspNetRoles]//
on userRole.RoleId
equals role.Id
select role.Name).ToList()
}).ToListAsync();
var userListVm = userList.Select(p => new AccountViewModel
{
UserId = p.UserId,
UserName = p.Username,
Email = p.Email,
Roles = string.Join(",", p.RoleNames),
EmailConfirmed = p.EmailConfirmed.ToString()
});
return userListVm;
}
In ASP.Net core 2.1 we to setup ApplicationRole like this in order to get Roles of users. You need to defined data you want explicit expose for user to use
public class ApplicationRole : IdentityRole
{
public virtual ICollection<IdentityUserRole<string>> Users { get; set; }
public virtual ICollection<IdentityRoleClaim<string>> Claims { get; set; }
}
Finally
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
modelBuilder.Entity<User>().HasMany(u => u.Claims).WithOne().HasForeignKey(c => c.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<User>().HasMany(u => u.Roles).WithOne().HasForeignKey(r => r.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Claims).WithOne().HasForeignKey(c => c.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ApplicationRole>().HasMany(r => r.Users).WithOne().HasForeignKey(r => r.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
modelBuilder.EnableAutoHistory(null);
}
The result will be the user name and user roles. If user have more than 1 roles the data will display like this
Admin, Editor, etc...
Full code can be found here here here and here
Hope this help.
loops through user list and get user roles by calling _userManager.GetRolesAsync(user) function and loops through roles of user and split roles with "," in one string variable
[HttpPost]
public async Task<IActionResult> OnPostGetPagination()
{
var users = await _userManager.Users.ToListAsync();
InputModel inputModel = new InputModel();
foreach (var v in users)
{
inputModel = new InputModel();
var roles = await _userManager.GetRolesAsync(v);
inputModel.Email = v.UserName;
inputModel.role = "";
foreach (var r in roles)
{
if (!inputModel.role.Contains(","))
{
inputModel.role = r;
}
else
{
inputModel.role = "," + r;
}
}
Input2.Add(inputModel);
}
}
good luck
UPDATE: this solution worked with EF Core 5, but seems like it was never supposed to, and it's not possible in EF Core 6 anymore.
You can use EF Core 5.0 Many-To-Many feature, and avoid subclassing IdentityUserRole/IdentityRole.
ApplicationUser
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
public class ApplicationUser : IdentityUser
{
public ICollection<IdentityRole> Roles { get; set; }
}
DbContext:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
...
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>()
.HasMany(u => u.Roles)
.WithMany("Users")
.UsingEntity<IdentityUserRole<string>>(
userRole => userRole.HasOne<IdentityRole>()
.WithMany()
.HasForeignKey(ur => ur.RoleId)
.IsRequired(),
userRole => userRole.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(ur => ur.UserId)
.IsRequired());
}
}
Since this is the top google search result; Nowadays you can just join off the UserRoles dbset (if your db context inherits from IdentityDbContext).
E.g outer joining the roles table to any user roles and then creating our manageUserModel (reduced info of applicationUser class for our api):
var employees = (from bb in _appContext.Users
join roleIds in _appContext.UserRoles on bb.Id equals roleIds.UserId
join role in _appContext.Roles on roleIds.RoleId equals role.Id into roles
orderby bb.LastName, bb.FirstName
where roles !=null && roles.Any(e => e.Name == Permissions.RoleNames.Administrator || e.Name == Permissions.RoleNames.Employee)
select ManageUserModel.FromInfo(bb, roles)).ToList();
public static ManageUserModel FromInfo(ApplicationUser info, IEnumerable<UserRole> roles)
{
var ret= FromInfo(info);
ret.Roles = roles.Select(e => new SimpleEntityString() {Id=e.Id, Text=e.Name}).ToList();
return ret;
}
This also demos a where clause using any of the role info (the above selects only users in our Admin and Employee roles).
Note: this inner joins the IdentityUserRole, so only users with a role will be returned, if you want all users just add a "into identRoles" to the end of the join roleIds... line and modify the rest of the conditions accordingly.
Update:
When upgrading NuGet Duende.IdentityServer.EntityFramework.Storage to 6.1.0 I got the following error:
CS0535 'ApplicationApiAuthorizationDbContext<TUser, TRole>' does not
implement interface member
'IPersistedGrantDbContext.ServerSideSessions'
ApplicationApiAuthorizationDbContext.cs now needs another DbSet like this:
public DbSet<ServerSideSession> ServerSideSessions
{
get;
set;
}
This caused the error below though for endpoints.MapRazorPages();.
System.Reflection.ReflectionTypeLoadException: 'Unable to load one or
more of the requested types. Method 'get_ServerSideSessions' in type
'Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiAuthorizationDbContext`1'
from assembly 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer,
Version=6.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
does not have an implementation.'
Recommend staying on Duende.IdentityServer.EntityFramework.Storage 5.2.0 until this is fixed.
Original:
As #Dreamescaper and #graycrow says you could use shadow many-to-many navigation in EF Core 5.0 even though it should not work.
https://github.com/dotnet/efcore/issues/25383#issuecomment-894785144
https://github.com/dotnet/efcore/issues/23362
Support might be added in EF Core 7.0 with unidirectional many-to-many relationships through shadow navigations again but not completed yet:
https://github.com/dotnet/efcore/issues/3864
I got it working like this using EF Core 6.0:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationRole> Roles { get; set; }
}
ApplicationRole:
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUser> Users { get; set; }
}
Program.cs or Startup.cs:
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
ApplicationApiAuthorizationDbContext:
//Based on Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiAuthorizationDbContext, Version=6.0.2.0
//https://github.com/dotnet/aspnetcore/issues/14161#issuecomment-533468760
public class ApplicationApiAuthorizationDbContext<TUser, TRole> : IdentityDbContext<TUser, TRole, string>, IPersistedGrantDbContext, IDisposable where TUser : IdentityUser where TRole : IdentityRole
{
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
public DbSet<PersistedGrant> PersistedGrants
{
get;
set;
}
public DbSet<DeviceFlowCodes> DeviceFlowCodes
{
get;
set;
}
public DbSet<Key> Keys
{
get;
set;
}
public ApplicationApiAuthorizationDbContext(DbContextOptions options, IOptions<OperationalStoreOptions> operationalStoreOptions)
: base(options)
{
_operationalStoreOptions = operationalStoreOptions;
}
Task<int> IPersistedGrantDbContext.SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
}
}
ApplicationDbContext inherits from ApplicationApiAuthorizationDbContext<ApplicationUser, ApplicationRole> instead of ApiAuthorizationDbContext<ApplicationUser>
public class ApplicationDbContext : ApplicationApiAuthorizationDbContext<ApplicationUser, ApplicationRole>
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.Roles)
.WithMany(r => r.Users)
.UsingEntity<IdentityUserRole<string>>(
userRole => userRole.HasOne<ApplicationRole>()
.WithMany()
.HasForeignKey(ur => ur.RoleId)
.IsRequired(),
userRole => userRole.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(ur => ur.UserId)
.IsRequired());
You can then get all users with roles like this:
var usersWithRoles = dbContext.Users.Include(x => x.Roles).ToList();
I implemented a solution to this problem providing a balance between performance and complexity I was happy with. We perform a handful of database roundtrips, one for every role, rather than one for every user. No DbMigrations or class overrides required.
//Fetch all the Users
var users = await userManager.Users
.Select(u => new { User = u, Roles = new List<string>() })
.ToListAsync();
//Fetch all the Roles
var roleNames = await roleManager.Roles.Select(r => r.Name).ToListAsync();
foreach (var roleName in roleNames)
{
//For each role, fetch the users
var usersInRole = await userManager.GetUsersInRoleAsync(roleName);
//Populate the roles for each user in memory
var toUpdate = users.Where(u => usersInRole.Any(ur => ur.Id == u.User.Id));
foreach (var user in toUpdate)
{
user.Roles.Add(roleName);
}
}
The accepted answer required customization of identity by extension, which without this will disable the use of roleManager and userManager. When you are customizing ASP.NET Core Identity, you should not use AddEntityFrameworkStores anymore. Because it will override all of your previous settings and customization to default Identity services.
First you need to create new services with the following signatures:
Why this violates the constraint of type parameter 'TUser'?
Without extending, using userManager and roleManager:
namespace identityDemo.Controllers
{
public class UserManagementController : Controller
{
private readonly ApplicationDbContext _context;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<IdentityUser> _userManager;
public UserManagementController(ApplicationDbContext context,
UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
_context = context;
_roleManager = roleManager;
_userManager = userManager;
}
// GET: ApplicationUserRoles
public async Task<IActionResult> GetApplicationUsersAndRoles()
{
return View(new UserMv(
(from user in await _userManager.Users.ToListAsync()
select new UserMv(user, GetUserRoles(user).Result)).ToList()));
}
private async Task<List<string>> GetUserRoles(IdentityUser user)
{
return new List<string>(await _userManager.GetRolesAsync(user));
}
}
With simple constructor for mapping to DTO:
namespace IdentityDemo.Models.ModelView
{
public class UserMv
{
public UserMv(IdentityUser aus, List<string> userRoles)
{
UserId = aus.Id;
UserName = aus.UserName;
RolesHeld = userRoles;
Email = aus.Email;
EmailConfirmed = aus.EmailConfirmed;
LockoutEnabled = aus.LockoutEnabled;
AccessFailedCount = aus.AccessFailedCount;
}
}
and the startup.cs
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
ASP.NET CORE 3.1 UPDATE
I use the following code and it works perfectly
namespace MyProject.Pages.Roles
{
public class DetailsModel : PageModel
{
public UserManager<ApplicationUser> _userManager;
public RoleManager<IdentityRole> _roleManager;
private readonly ApplicationDbContext _context;
public DetailsModel(UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
ApplicationDbContext context)
{
_userManager = userManager;
_roleManager = roleManager;
_context = context;
}
public IList<IdentityRole> Roles { get; set; }
[BindProperty]
public IList<ApplicationUser> applicationUserList { get; set; }
[BindProperty]
public IList<IdentityRole> allRolesList { get; set; }
public IList<IdentityUserRole<string>> usersRoles { get; set; }
public IList<IdentityUserRole<string>> usersRole { get; set; }
public IList<IdentityUserRole<string>> userWithRole { get; set; }
public Dictionary<ApplicationUser, string> itemDictionary;
public async Task<IActionResult> OnGetAsync(string id)
{
if (id == null)
{
return NotFound();
}
Roles = await _context.Roles.Where(r => r.Id == id).ToListAsync();
allRolesList = await _context.Roles.ToListAsync();
usersRoles = await _context.UserRoles.ToListAsync();
usersRole = await _context.UserRoles.Where(r => r.RoleId == id).ToListAsync();
userWithRole = usersRoles.Where(u => u.RoleId == id).ToList();
applicationUserList = await _context.Users.ToListAsync();
itemDictionary = new Dictionary<ApplicationUser, string> { };
foreach (var item in usersRole)
{
itemDictionary.Add(await _context.Users.FindAsync(id = item.UserId), item.UserId);
}
return Page();
}
}
}
It's very useful to bind all that stuff to get an idea what's going on!
On the Details Razor Page I simply have
#page "{id}"
#model MyProject.Pages.Roles.DetailsModel
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var dict = Model.itemDictionary;
int cou = dict.Count();
var x = Model.applicationUserList;
}
<h5 class="bg-primary text-white text-center p-2">List of Members having the role #Model.Roles[0].Name</h5>
<table class="table">
<thead>
<tr>
<th>#Html.DisplayNameFor(model => model.userWithRole[0].UserId)</th>
<th>#Html.DisplayNameFor(model => model.userWithRole[0].RoleId)</th>
<th>LastName, FirstName</th>
</tr>
</thead>
<tbody>
#foreach (var kvp in dict.ToArray())
{
<tr>
<td>#kvp.Key</td>
<td>#kvp.Value</td>
<td>#kvp.Key.LastName, #kvp.Key.FirstName</td>
</tr>
}
</tbody>
</table>
And here the result:
There is a useful article on microsoft docs https://learn.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-5.0
For me, exposing navigation properties (roles, users) looked like this(NET 5):
public class ApplicationUser : IdentityUser
{
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
public class ApplicationDbContext
: IdentityDbContext<
ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne()
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
});
}
}
Note that in ApplicationDbContext you can change the primary key type (string in my case)
Worked perfectly. I'm using integer keys, so I replaced the "string" with "int"
ApplicationRole : IdentityRole<int>
ApplicationUserRole : IdentityUserRole<int>
ApplicationUser : IdentityUser<int>
ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int,
IdentityUserClaim<int>,
ApplicationUserRole, IdentityUserLogin<int>, IdentityRoleClaim<int>,
IdentityUserToken<int>>
Linq:
RoleId = (from a in m.UserRoles select a.Role.Id).FirstOrDefault(),
you can use sql command like bottom code for get all admin users(with out import any class to your model):
var adminUsers = await _db.Users
.FromSqlRaw("select AspNetUsers.* from AspNetUsers with(nolock)"+
" join AspNetUserRoles with(nolock) on UserId = AspNetUsers.id"+
" join AspNetRoles with(nolock) on AspNetRoles.id = AspNetUserRoles.RoleId"+
" where AspNetRoles.name = 'Admin'").ToListAsync();
in above code _db is your database context.
I needed to display all the roles a user has a in a view, instead of the solutions provided here already, i went with this quick and dirty thing:
#foreach(var user in Model.Users)
{
<tr>
<td>#user.Email</td>
<td>#String.Join(", ", #Model._userManager.GetRolesAsync(user).GetAwaiter().GetResult().ToArray())</td>
</tr>
}
_userManager has to be public for this to work. and user is simply an instance of IdentityUser.

Accessing additional properties of an Identity user when retrieving data from the database using EF and Linq

Lets say I added a couple of additional properties to the default User when using asp.net identity:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am aware that in my asp.net MVC controller I can simply do the following to get the current logged in user's name:
User.Identity.Name
So when saving to the database I can simply pass User.Identity.Name to my repository along with the object I am saving so the CreatedBy field can be populated.
Now lets say I am retrieving items from the database which have a field of CreatedBy that contains a string of the username, but I want to display Created by : FirstName + LastName in the View.
How do I obtain this extra information? If I was using pure SQL I would do an INNER JOIN on the AspNetUsers table where CreatedBy=Username and simply retrieve the FirstName and LastName in a custom column called CreatedByFullName.
Since I am using Entity Framework now along with the latest version of ASP.NET Identity I am a bit confused at how we are expected to retrieve user information to display in the View of our pages. Is it a matter of doing a join with linq in my repository or simply adding an object to each of my properties called ApplicationUser or is there better ways?
Assumptions:
You have a single tabled called ApplicationUser that contains all your users.
This table has an Id column(int) that you are reusing to store lookups in other tables.
Other classes (what I call uni-directional navigation properties):
public class BookContext : DbContext
{
public DbSet<Book> Books { get; set; }
public Dbset<ApplicationUser> Users { get; set; }
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany()
.HasForeignKey(b => b.CreatedBy);
}
}
public class Book
{
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstBookWithRelatedUser bookContext.Books
.Include(b => b.CreatedByUser)
.First();
}
Something like that. I recommend reading the Entity Framework Documentation. Granted the above code I pretty much just wrote off the top of my head so I may not be exactly right.
If you wanted, what I call, Bi-Directional navigation properties:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Book> Books { get; set; }
}
then
public overridee OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasRequired(b => b.CreatedByUser)
.WithMany(u => u.Books)
.HasForeignKey(b => b.CreatedBy);
}
Then you'd simply
using (var bookContext = new BookContext())
{
var firstUserWithAllRelatedBooks = bookContext.Users
.Include(u => u.Books)
.First();
}
It really just depends on your needs. But becareful, you can end up with a Giant God DbContext that is aware of all relationships...
Sample EF query will look like below -
var result = (from tab in db.YourTable
join user in db.AspNetUsers on user.username equals tab.CreatedBy
select new {YourTableObj = tab, CreatedByFullName = user.FirstName + " " + user.LastName).ToList();

Entity Framework and LINQ left join and include on same table

I have the following class structure for my Users and the permissions they're in for the different companies they may be associated to:
public class User
{
public Guid Id { get; set; }
public List<Permission> Permissions { get; set; }
public Company DefaultCompany { get; set; }
}
public class Permission
{
public User User { get; set; }
public Company Company { get; set; }
public int PermissionLevel { get; set; }
}
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
}
This results in three SQL tables. There is a FK between Permission.User_Id > User.Id and Permission.Company_Id > Company.Id. There is no explicit relationship (ie. FK) between User.DefaultCompany and the Company table. This is on purpose due to a legacy choice in our database schema.
I also have a database repository method that grabs a user by it's Id, and includes the full Company record:
public User GetById(Guid Id)
{
return (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
select r)
.SingleOrDefault();
}
This works fine, but it doesn't set the DefaultCompany property. So I tried setting that by changing this method to the following. It's worth pointing out that the Company record that represents the DefaultCompany shares the same ID value as the User.
public User GetById(Guid Id)
{
return (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
join c in this.Context.Companies on r.Id equals c.Id into companies
from company in companies.DefaultIfEmpty()
select new { User = r, Company = company })
.ToList()
.Select(p => { p.User.DefaultCompany = p.Company; return p.User; })
.SingleOrDefault();
}
And this does, in fact, set the DefaultCompany but it has the side effect of not selecting the Permissions list. I can do this all as two separate operations, as in the following code, but I'd rather not hit the database twice if I don't have to.
public User GetById(Guid Id)
{
var u = (from r in this.Context.Users.Include("Permissions.Company")
where r.Id == Id
select r)
.SingleOrDefault();
u.DefaultCompany = (from r in this.Context.Companies where r.Id == u.Id select r).SingleOrDefault();
return u;
}
Is there another way to accomplish this?
Edit: explaining resulting SQL data model and additional example.
There are two possible solutions for this problem.
The cleanest is to use the Fluent API to indicate the model that there is a 1 to 1 relation between User and Company.
Override the OnModelCreating(DbModelBuilder modelBuilder) method of your context.
Inside it, configure the 1 ro 1 relation like this:
modelBuilder.Entity<User>()
.HasOptional(u => u.DefaultCompany)
.WithRequired();
NOTE: with this configuration there is a relationship of 1 user to 0 or 1 default companies (note the Optional in HasOptional). And the default company must have a User on the other side. When a 1 to 1 (or 1 to 0..1) relation is configured, EF will automatically use the PK of the related tables to create the relation between them. You can fine tune the relation using other Fluent API functions
After doing so, you can include the DefaultCompany using Include():
User user = ctx.Users
.Include(u => u.DefaultCompany)
.SingleOrDefault(u => u.Id == userId);
The other, more ugly solution, is to use your second query and include the missing permissions in the projection, to force EF to recover them from the DB.
// ...
select new { User = r, Company = company, Companies = r.Permissions }
// ...

How to query junction table in EF without it being defined (performance issue)

The classic User-UserRole-Role example
The UserProfile class:
public class UserProfile
{
public int Id { get; set; }
public String UserName { get; set; }
public virtual List<Role> Roles { get; set; }
}
The Role class:
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<UserProfile> Users { get; set; }
}
With additional FluentAPI mapping:
modelBuilder.Entity<Role>()
.HasMany(p => p.Users).WithMany(u => u.Roles)
.Map(m =>
m.MapLeftKey("RoleId").MapRightKey("UserId").ToTable("UserInRole"));
Entity Framework Code First generates the database tables UserProfile, Role and UserInRole. UserInRole table contains only RoleId and UserId keys.
Problem/Need: I need a list of users paired with their roles, but only for a specific list of users and roles.
I solved this problem by first getting the users and then get their Roles, but it seems to be much less performant way (as it seems it does many round-trips to the DBMS).
In SQL, I would do this:
select u.UserName, r.Name from UserInRole as ur
left outer join UserProfile as u on ur.UserId = u.Id
left outer join Role as r on ur.RoleId = r.Id
where u.Id in (1, 2, 3) and r.Id in (1, 2, 3, 4, 5)
If possible, how can I do that in EntityFramework? (Note that I do not have the junction table UserInRole mapped in Code First?) None of the LINQ queries I tried generated the two left outer joins from the junction table, and as far as I could see, this would be the fastest way. Correct me if I'm wrong.

Categories