如何使用 UserManager 在 IdentityUser 上加载导航属性

How to load navigation properties on an IdentityUser with UserManager

我已经扩展 IdentityUser 以包含用户地址的导航 属性,但是当使用 UserManager.FindByEmailAsync 获取用户时,导航 属性 不是人口稠密。 ASP.NET Identity Core 是否有一些方法来填充导航属性,例如 Entity Framework 的 Include(),还是我必须手动完成?

我这样设置导航属性:

public class MyUser : IdentityUser
{
    public int? AddressId { get; set; }

    [ForeignKey(nameof(AddressId))]
    public virtual Address Address { get; set; }
}

public class Address
{
    [Key]
    public int Id { get; set; }
    public string Street { get; set; }
    public string Town { get; set; }
    public string Country { get; set; }
}

不幸的是,您必须手动执行或创建自己的 IUserStore<IdentityUser>,在 FindByEmailAsync 方法中加载相关数据:

public class MyStore : IUserStore<IdentityUser>, // the rest of the interfaces
{
    // ... implement the dozens of methods
    public async Task<IdentityUser> FindByEmailAsync(string normalizedEmail, CancellationToken token)
    {
        return await context.Users
            .Include(x => x.Address)
            .SingleAsync(x => x.Email == normalizedEmail);
    }
}

当然,仅仅为此实施整个商店并不是最佳选择。

您也可以直接查询店铺,不过:

UserManager<IdentityUser> userManager; // DI injected

var user = await userManager.Users
    .Include(x => x.Address)
    .SingleAsync(x => x.NormalizedEmail == email);

简短的回答:你不能。但是,有选项:

  1. 稍后显式加载关系:

    await context.Entry(user).Reference(x => x.Address).LoadAsync();
    

    这当然需要发出额外的查询,但您可以通过 UserManager.

  2. 继续拉取用户
  3. 就用上下文吧。您 没有 使用 UserManager。它只是让一些事情变得简单一些。您始终可以回退到直接通过上下文查询:

    var user = context.Users.Include(x => x.Address).SingleOrDefaultAsync(x=> x.Id == User.Identity.GetUserId());
    

FWIW,您的导航 属性 不需要 virtual。这是用于延迟加载,EF Core 目前不支持。 (不过,目前处于预览状态的 EF Core 2.1 实际上将支持延迟加载。)无论如何,延迟加载通常不是一个好主意,因此您仍然应该坚持急切或显式加载您的关系。

我发现在 UserManager 上写一个扩展很有用 class。

public static async Task<MyUser> FindByUserAsync(
    this UserManager<MyUser> input,
    ClaimsPrincipal user )
{
    return await input.Users
        .Include(x => x.InverseNavigationTable)
        .SingleOrDefaultAsync(x => x.NormalizedUserName == user.Identity.Name.ToUpper());
}

带有 EF Core 6.0 的 .NET 6.0 更新:

您现在可以将 属性 配置为自动包含在每个查询中。

modelBuilder.Entity<MyUser>().Navigation(e => e.Address).AutoInclude();

有关更多信息,请查看: https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager#model-configuration-for-auto-including-navigations

在我的情况下,最好的选择是添加对 Microsoft.EntityFrameworkCore.Proxies 的包引用,然后在您的服务中使用 UseLazyLoadingProxies

.AddDbContext<YourDbContext>(
    b => b.UseLazyLoadingProxies()
          .UseSqlServer(myConnectionString));

更多信息 https://docs.microsoft.com/de-de/ef/core/querying/related-data/lazy