ASP.NET Core中如何让GetUserAsync自动加载关联UserProfile
嗨,这个场景我之前做项目的时候也碰到过,ASP.NET Core里确实没有了ASP.NET 5那套Owin的加载方式,不过我们有几种靠谱的办法来解决这个问题,让你获取用户的时候能自动带上关联的Profile甚至Image数据:
方法1:给UserManager写扩展方法(灵活可控)
默认的GetUserAsync只会查询AspNetUsers表的数据,不会自动Include导航属性,所以我们可以自己封装一个扩展方法,主动把关联数据加进来:
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using System.Threading.Tasks; public static class UserManagerExtensions { // 扩展方法,获取用户并包含UserProfile和关联的UserImage public static async Task<ApplicationUser> GetUserWithProfileAsync(this UserManager<ApplicationUser> userManager, ClaimsPrincipal principal) { var userId = userManager.GetUserId(principal); return await userManager.Users .Include(u => u.UserProfile) // 加载UserProfile .ThenInclude(p => p.UserImage) // 再加载UserProfile关联的UserImage .FirstOrDefaultAsync(u => u.Id == userId); } }
之后你在代码里就可以替换原来的调用方式,改成:
var currentUser = await _userManager.GetUserWithProfileAsync(User);
这种方式的好处是你可以根据不同场景选择是否加载关联数据,避免不必要的性能开销。
方法2:启用延迟加载(简单但注意性能)
如果你希望所有地方访问UserProfile时都自动加载,可以给导航属性加上virtual关键字,然后启用EF Core的延迟加载代理:
首先修改你的模型类:
public class ApplicationUser : IdentityUser { // 给导航属性加virtual public virtual UserProfile UserProfile { get; set; } } public class UserProfile { public int Id { get; set; } // 其他属性... public virtual ApplicationUser ApplicationUser { get; set; } public virtual UserImage UserImage { get; set; } }
然后在你的ApplicationDbContext里启用延迟加载:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // 启用延迟加载代理 optionsBuilder.UseLazyLoadingProxies(); // 其他配置...比如数据库连接字符串 }
不过这种方式要注意N+1查询问题,如果在循环里批量访问用户的Profile,可能会触发大量数据库查询,性能会受影响,所以适合关联数据不频繁使用的场景。
方法3:自定义UserStore(一劳永逸)
如果希望所有通过UserManager获取用户的操作都自动加载关联数据,可以自定义UserStore,重写查询方法来Include关联数据:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using System.Threading; using System.Threading.Tasks; public class CustomUserStore : UserStore<ApplicationUser, IdentityRole, ApplicationDbContext> { public CustomUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { } // 重写根据ID查找用户的方法,加入关联数据 public override async Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default) { return await Context.Users .Include(u => u.UserProfile) .ThenInclude(p => p.UserImage) .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); } // 重写根据用户名查找用户的方法,加入关联数据 public override async Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default) { return await Context.Users .Include(u => u.UserProfile) .ThenInclude(p => p.UserImage) .FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName, cancellationToken); } }
然后在Startup.cs(或者Program.cs,看你的.NET版本)里注册这个自定义的UserStore:
services.AddIdentity<ApplicationUser, IdentityRole>() .AddUserStore<CustomUserStore>() // 替换默认的UserStore .AddDefaultTokenProviders();
这样之后,不管你调用_userManager.GetUserAsync(User)还是其他获取用户的方法,都会自动加载UserProfile和关联的UserImage了,适合全局都需要关联数据的场景。
内容的提问来源于stack exchange,提问作者Orlando Yero

