如何在 ASP.Net Core 中高效查询身份数据?
How do I query identity data efficiently in ASP.Net Core?
背景
我有一个用 ASP.NET Core v2.1.1 编写的网站。
我有一个自定义身份用户class:
public class FooIdentityUser : IdentityUser<string>, IIdentityModel
{
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public bool FooBool { get; set; }
}
和自定义身份角色 class:
public class FooIdentityRole : IdentityRole<string>
{
}
然后我在 dbcontext 中引用:
public class FooIdentityDbContext : IdentityDbContext<FooIdentityUser,FooIdentityRole,string>
{
public FooIdentityDbContext(DbContextOptions<FooIdentityDbContext> options)
: base(options)
{
}
}
要求
我的总体要求是让系统管理员用户能够在网站的管理区域内查看并最终管理用户数据。
具体来说:
- 我想提供一个 foo 角色的用户列表
- 和/或我想列出所有将 FooBool 设置为 true 的用户
- 和/或我想查询电子邮件地址、名字和姓氏
- 和/或进行排序
问题
有没有人有任何指向以前已经完成此操作的网页的链接,或者您能否回答我如何实现此功能?我尝试了以下几种方法。
方法/研究
据我所知,有两种方法可以做到这一点:
方法一
因为我想在一个视图中专门为一个用户角色列出用户,我可以看到用户管理器为此提供了一个方法:
_userManager.GetUsersInRoleAsync(fooRoleName)
我遇到的问题是它 return 是一个 IList,因此如果我想查询 FooBool 和/或 FirstName、LastName 或 return 所有具有此角色的用户Email Address,它需要循环遍历列表来过滤掉这些,如果有几十万或几百万用户,效率会很低吗?
理想情况下,这会 return 一个 IQueryable,因此在应用我的位置和顺序之前它不会访问数据库,但我找不到这样做的方法?
方法二
另一种方法可能是直接通过我的通用存储库查询上下文。
public class GenericIdentityRepository<TModel> : IIdentityRepository<TModel> where TModel : class, IIdentityModel
{
private readonly ILogger _logger;
public FooIdentityDbContext Context { get; set; }
private readonly DbSet<TModel> _dbSet;
public GenericIdentityRepository(FooIdentityDbContext dbContext, ILogger<GenericIdentityRepository<TModel>> logger)
{
Context = dbContext;
_logger = logger;
_dbSet = Context.Set<TModel>();
}
public IQueryable<TModel> GetAll()
{
_logger.LogDebug("GetAll " + typeof(TModel));
IQueryable<TModel> query = _dbSet;
return query;
}
public IQueryable<TModel> GetAllNoTracking()
{
_logger.LogDebug("GetAllNotTracking " + typeof(TModel));
IQueryable<TModel> query = GetAll().AsNoTracking();
return query;
}
}
我想看看我是否可以通过为用户角色创建自定义 classes 然后使用 linq 给我一个 IQueryable 来做些什么?
public class FooIdentityUserRole : IdentityUserRole<string>
{
public virtual FooIdentityUser User { get; set; }
public virtual FooIdentityRole Role { get; set; }
}
然后以某种方式将数据查询到 return IQueryable,但我正在努力生成正确的 linq,我需要这样做。
我的建议是直接在你的控制器中使用 FooIdentityDbContext
并按照你想要的方式查询数据。我不知道有什么方法可以使用 UserManager
class 实现您想要的结果。也许有,但老实说,我不会把东西混在一起。 UserManager
当您与单个用户打交道并想用它做一些事情时更有用,例如 AddToRoleAsync
或 ChangePasswordAsync
.
您可以更灵活地直接使用 DbContext
class。您不需要一些花哨的通用存储库。保持简单和简洁,除非你确实需要抽象(你几乎总是不需要)
实际答案:您已经正确配置了实体,所以现在只需注入 FooIdentityDbContext
并开始查询。像这样:
public class HomeController : Controller
{
private readonly FooIdentityDbContext_dbContext;
public HomeController(FooIdentityDbContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public async Task<IActionResult> UserList(string roleName, bool fooBool, string firstName)
{
// You are interested in Users on Roles, so it's easier to start on the UserRoles table
var usersInRole = _dbContext.UserRoles.Select(userRole => userRole);
// filter only users on role first
if (!string.IsNullOrWhiteSpace(roleName))
{
usersInRole = usersInRole.Where(ur => ur.Role.Name == roleName);
}
// then filter by fooBool
usersInRole = usersInRole.Where(ur => ur.User.FooBool == fooBool);
// then filter by user firstname or whatever field you need
if (!string.IsNullOrWhiteSpace(firstName))
{
usersInRole = usersInRole.Where(ur => ur.User.FirstName.StartsWith(firstName));
}
// finally materialize the query, sorting by FirstName ascending
// It's a common good practice to not return your entities and select only what's necessary for the view.
var filteredUsers = await usersInRole.Select(ur => new UserListViewModel
{
Id = ur.UserId,
Email = ur.User.Email,
FooBool = ur.User.FooBool,
FirstName = ur.User.FirstName
}).OrderBy(u => u.FirstName).ToListAsync();
return View("UserListNew", filteredUsers);
}
}
奖金:我一直在阅读 Jon Smith 的 EF Core in Action 书,这本书很棒。如果您想在项目中继续使用 EF Core,我强烈建议您阅读它。它充满了不错的技巧和真实世界的例子。
使用 .Users.
await _userManager.Users.Where(w => w.LastChangeUserId == null).ToListAsync();
背景
我有一个用 ASP.NET Core v2.1.1 编写的网站。
我有一个自定义身份用户class:
public class FooIdentityUser : IdentityUser<string>, IIdentityModel
{
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public bool FooBool { get; set; }
}
和自定义身份角色 class:
public class FooIdentityRole : IdentityRole<string>
{
}
然后我在 dbcontext 中引用:
public class FooIdentityDbContext : IdentityDbContext<FooIdentityUser,FooIdentityRole,string>
{
public FooIdentityDbContext(DbContextOptions<FooIdentityDbContext> options)
: base(options)
{
}
}
要求
我的总体要求是让系统管理员用户能够在网站的管理区域内查看并最终管理用户数据。
具体来说:
- 我想提供一个 foo 角色的用户列表
- 和/或我想列出所有将 FooBool 设置为 true 的用户
- 和/或我想查询电子邮件地址、名字和姓氏
- 和/或进行排序
问题
有没有人有任何指向以前已经完成此操作的网页的链接,或者您能否回答我如何实现此功能?我尝试了以下几种方法。
方法/研究
据我所知,有两种方法可以做到这一点:
方法一
因为我想在一个视图中专门为一个用户角色列出用户,我可以看到用户管理器为此提供了一个方法:
_userManager.GetUsersInRoleAsync(fooRoleName)
我遇到的问题是它 return 是一个 IList,因此如果我想查询 FooBool 和/或 FirstName、LastName 或 return 所有具有此角色的用户Email Address,它需要循环遍历列表来过滤掉这些,如果有几十万或几百万用户,效率会很低吗?
理想情况下,这会 return 一个 IQueryable,因此在应用我的位置和顺序之前它不会访问数据库,但我找不到这样做的方法?
方法二
另一种方法可能是直接通过我的通用存储库查询上下文。
public class GenericIdentityRepository<TModel> : IIdentityRepository<TModel> where TModel : class, IIdentityModel
{
private readonly ILogger _logger;
public FooIdentityDbContext Context { get; set; }
private readonly DbSet<TModel> _dbSet;
public GenericIdentityRepository(FooIdentityDbContext dbContext, ILogger<GenericIdentityRepository<TModel>> logger)
{
Context = dbContext;
_logger = logger;
_dbSet = Context.Set<TModel>();
}
public IQueryable<TModel> GetAll()
{
_logger.LogDebug("GetAll " + typeof(TModel));
IQueryable<TModel> query = _dbSet;
return query;
}
public IQueryable<TModel> GetAllNoTracking()
{
_logger.LogDebug("GetAllNotTracking " + typeof(TModel));
IQueryable<TModel> query = GetAll().AsNoTracking();
return query;
}
}
我想看看我是否可以通过为用户角色创建自定义 classes 然后使用 linq 给我一个 IQueryable 来做些什么?
public class FooIdentityUserRole : IdentityUserRole<string>
{
public virtual FooIdentityUser User { get; set; }
public virtual FooIdentityRole Role { get; set; }
}
然后以某种方式将数据查询到 return IQueryable,但我正在努力生成正确的 linq,我需要这样做。
我的建议是直接在你的控制器中使用 FooIdentityDbContext
并按照你想要的方式查询数据。我不知道有什么方法可以使用 UserManager
class 实现您想要的结果。也许有,但老实说,我不会把东西混在一起。 UserManager
当您与单个用户打交道并想用它做一些事情时更有用,例如 AddToRoleAsync
或 ChangePasswordAsync
.
您可以更灵活地直接使用 DbContext
class。您不需要一些花哨的通用存储库。保持简单和简洁,除非你确实需要抽象(你几乎总是不需要)
实际答案:您已经正确配置了实体,所以现在只需注入 FooIdentityDbContext
并开始查询。像这样:
public class HomeController : Controller
{
private readonly FooIdentityDbContext_dbContext;
public HomeController(FooIdentityDbContext dbContext)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public async Task<IActionResult> UserList(string roleName, bool fooBool, string firstName)
{
// You are interested in Users on Roles, so it's easier to start on the UserRoles table
var usersInRole = _dbContext.UserRoles.Select(userRole => userRole);
// filter only users on role first
if (!string.IsNullOrWhiteSpace(roleName))
{
usersInRole = usersInRole.Where(ur => ur.Role.Name == roleName);
}
// then filter by fooBool
usersInRole = usersInRole.Where(ur => ur.User.FooBool == fooBool);
// then filter by user firstname or whatever field you need
if (!string.IsNullOrWhiteSpace(firstName))
{
usersInRole = usersInRole.Where(ur => ur.User.FirstName.StartsWith(firstName));
}
// finally materialize the query, sorting by FirstName ascending
// It's a common good practice to not return your entities and select only what's necessary for the view.
var filteredUsers = await usersInRole.Select(ur => new UserListViewModel
{
Id = ur.UserId,
Email = ur.User.Email,
FooBool = ur.User.FooBool,
FirstName = ur.User.FirstName
}).OrderBy(u => u.FirstName).ToListAsync();
return View("UserListNew", filteredUsers);
}
}
奖金:我一直在阅读 Jon Smith 的 EF Core in Action 书,这本书很棒。如果您想在项目中继续使用 EF Core,我强烈建议您阅读它。它充满了不错的技巧和真实世界的例子。
使用 .Users.
await _userManager.Users.Where(w => w.LastChangeUserId == null).ToListAsync();