And*_*iss 26 c# mysql entity-framework-core asp.net-core asp.net-core-identity
我正在尝试为用户管理管理页面提取所有Identity用户及其相关角色.我认为这相当容易,但显然不是.我尝试过以下解决方案:https://stackoverflow.com/a/43562544/5392786但到目前为止还没有解决.
这是我到目前为止:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public List<IdentityUserRole<string>> Roles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
的DbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Run Code Online (Sandbox Code Playgroud)
启动标识代码
services.AddIdentity<ApplicationUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Run Code Online (Sandbox Code Playgroud)
我想要显示列表的Razor Page:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
调用时出现以下错误userManager.Users.Include(u => u.Roles).ToList();:
MySql.Data.MySqlClient.MySqlException:'字段列表'中的'未知列'u.Roles.ApplicationUserId'
And*_*iss 47
我现在已经实现了以下解决方案.
正如CodeNotFound在评论中指出的那样,IdentityUser曾经拥有一个Roles属性.在.NET Core中不再是这种情况.这个关于GitHub的评论/问题似乎是.Net Core的当前解决方案.我试图用以下代码实现它:
ApplicationUser
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
ApplicationUserRole
public class ApplicationUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
ApplicationRole
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUserRole> UserRoles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
的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();
});
}
}
Run Code Online (Sandbox Code Playgroud)
启动
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Run Code Online (Sandbox Code Playgroud)
最后,确保当你使用它时,你急切地加载用户的UserRoles,然后像UserRole的角色一样:
this.Users = userManager.Users.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).ToList();
Run Code Online (Sandbox Code Playgroud)
我有一个问题,其中Role每个的属性UserRole为null,这通过添加.ThenInclude(ur => ur.Role)部分解决.
Microsoft doc on multi-level eager loading:https://docs.microsoft.com/en-us/ef/core/querying/related-data#including-multiple-levels
对于 dotnet core 3.1,我一直在使用以下通用方法。
// _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);
Run Code Online (Sandbox Code Playgroud)
生成的 SQL 简单而合理:
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"
Run Code Online (Sandbox Code Playgroud)
更新:此解决方案适用于 EF Core 5,但似乎从来不应该这样做,并且在 EF Core 6 中不再可能。
您可以使用 EF Core 5.0 多对多功能,并避免子类化 IdentityUserRole/IdentityRole。
应用程序用户
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
public class ApplicationUser : IdentityUser
{
public ICollection<IdentityRole> Roles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
数据库上下文:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
参考评论
首先是获取数据的代码
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;
}
Run Code Online (Sandbox Code Playgroud)
在 ASP.Net core 2.1 中,我们像这样设置 ApplicationRole 以获取用户的角色。您需要定义要显式公开供用户使用的数据
public class ApplicationRole : IdentityRole
{
public virtual ICollection<IdentityUserRole<string>> Users { get; set; }
public virtual ICollection<IdentityRoleClaim<string>> Claims { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
最后
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);
}
Run Code Online (Sandbox Code Playgroud)
结果将是用户名和用户角色。如果用户有 1 个以上的角色,则数据将显示为管理员、编辑器等...
通过调用_userManager.GetRolesAsync(user)函数遍历用户列表并获取用户角色,并在一个字符串变量中遍历用户角色和带“,”的拆分角色
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
祝好运
更新:
将 NuGet 升级Duende.IdentityServer.EntityFramework.Storage到 6.1.0 时出现以下错误:
CS0535“ApplicationApiAuthorizationDbContext <TUser,TRole>”未实现接口成员“IPersistedGrantDbContext.ServerSideSessions”
ApplicationApiAuthorizationDbContext.cs现在需要另一个DbSet这样的:
public DbSet<ServerSideSession> ServerSideSessions
{
get;
set;
}
Run Code Online (Sandbox Code Playgroud)
但这导致了以下错误endpoints.MapRazorPages();。
System.Reflection.ReflectionTypeLoadException:“无法加载一种或多种请求的类型。程序集“Microsoft.AspNetCore.ApiAuthorization.IdentityServer,Version=6.0.5.0,Culture=neutral,PublicKeyToken=adb9793829ddae60”中类型“Microsoft.AspNetCore.ApiAuthorization.IdentityServer.ApiAuthorizationDbContext`1”中的方法“get_ServerSideSessions”没有实现。 '
建议继续坚持下去,Duende.IdentityServer.EntityFramework.Storage 5.2.0直到这个问题得到解决。
原来的:
正如 @Dreamescaper 和 @graycrow 所说,您可以在 EF Core 5.0 中使用影子多对多导航,即使它不应该工作。
https://github.com/dotnet/efcore/issues/25383#issuecomment-894785144
https://github.com/dotnet/efcore/issues/23362
EF Core 7.0 中可能会再次通过影子导航添加单向多对多关系的支持,但尚未完成:
https://github.com/dotnet/efcore/issues/3864
我使用 EF Core 6.0 让它像这样工作:
应用用户:
public class ApplicationUser : IdentityUser
{
public ICollection<ApplicationRole> Roles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
应用角色:
public class ApplicationRole : IdentityRole
{
public ICollection<ApplicationUser> Users { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
Program.cs或者Startup.cs:
services.AddDefaultIdentity<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
ApplicationDbContext继承自ApplicationApiAuthorizationDbContext<ApplicationUser, ApplicationRole>而不是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());
Run Code Online (Sandbox Code Playgroud)
然后,您可以获得具有如下角色的所有用户:
var usersWithRoles = dbContext.Users.Include(x => x.Roles).ToList();
Run Code Online (Sandbox Code Playgroud)