Как загрузить свойства навигации в IdentityUser с помощью 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; }
}

Ответ 1

К сожалению, вам нужно либо сделать это вручную, либо создать свой собственный 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);

Ответ 2

Короткий ответ: вы не можете. Однако есть варианты:

  1. Явно загрузите отношение позже:

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

    Разумеется, для этого потребуется выдача дополнительного запроса, но вы можете продолжать вытаскивать пользователя через UserManager.

  2. Просто используйте контекст. Вам не нужно использовать UserManager. Это немного упрощает некоторые вещи. Вы всегда можете вернуться к запросу напрямую через контекст:

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

FWIW, вам не нужно virtual свойство навигации. Это для ленивой загрузки, которую EF Core в настоящее время не поддерживает. (Хотя EF Core 2.1, в настоящее время в предварительном просмотре, фактически будет поддерживать ленивую загрузку.) Несмотря на это, ленивая загрузка - это плохая идея чаще, чем нет, поэтому вы все равно должны придерживаться либо с нетерпением, либо с явной загрузкой своих отношений.

Ответ 3

Я нашел полезным написать расширение для класса UserManager.

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());
}