web-dev-qa-db-fra.com

Comment charger les propriétés de navigation sur un IdentityUser avec UserManager

J'ai étendu IdentityUser pour inclure une propriété de navigation pour l'adresse de l'utilisateur, mais lorsque vous obtenez l'utilisateur avec UserManager.FindByEmailAsync, La propriété de navigation n'est pas remplie. ASP.NET Identity Core a-t-il un moyen de remplir les propriétés de navigation comme Include() d'Entity Framework, ou dois-je le faire manuellement?

J'ai configuré la propriété de navigation comme ceci:

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; }
}
24
Mourndark

Malheureusement, vous devez le faire manuellement ou créer votre propre IUserStore<IdentityUser> où vous chargez les données associées dans la méthode 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);
    }
}

Bien sûr, l'implémentation de l'ensemble du magasin juste pour cela n'est pas la meilleure option.

Vous pouvez également interroger le magasin directement, cependant:

UserManager<IdentityUser> userManager; // DI injected

var user = await userManager.Users
    .Include(x => x.Address)
    .SingleAsync(x => x.NormalizedEmail == email);
22
SO used to be good

La réponse courte: vous ne pouvez pas. Cependant, il existe des options:

  1. Chargez explicitement la relation plus tard:

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

    Cela nécessitera bien sûr une requête supplémentaire, mais vous pouvez continuer à extraire l'utilisateur via UserManager.

  2. Utilisez simplement le contexte. Vous n'avez pas avez pour utiliser UserManager. Cela rend simplement certaines choses un peu plus simples. Vous pouvez toujours recourir à l'interrogation directement via le contexte:

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

FWIW, vous n'avez pas besoin de virtual sur votre propriété de navigation. C'est pour le chargement différé, qu'EF Core ne prend pas en charge actuellement. (Cependant, EF Core 2.1, actuellement en préversion, prend en charge le chargement paresseux.) Quoi qu'il en soit, le chargement paresseux est une mauvaise idée le plus souvent, vous devez donc vous en tenir à charger vos relations avec impatience ou explicitement.

10
Chris Pratt

J'ai trouvé utile d'écrire une extension sur la classe 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());
}
0
Daniel Kahle