Reputation: 2687
I've extended IdentityUser
to include a navigation property for the user's address, however when getting the user with UserManager.FindByEmailAsync
, the navigation property isn't populated. Does ASP.NET Identity Core have some way to populate navigation properties like Entity Framework's Include()
, or do I have to do it manually?
I've set up the navigation property like this:
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; }
}
Upvotes: 47
Views: 14653
Reputation: 1
I know this is an old post but I solved this issue by using the first option given by Camilo Terevinto but tweaked it a little and I inherited UserManager and overrode the GetUserAsync method to add in some logic I needed to load the property I needed:
public class UserService : UserManager<MyUser> {
private readonly ApplicationDbContext context;
public UserService(ApplicationDbContext context, IUserStore<MyUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<MyUser> passwordHasher, IEnumerable<IUserValidator<MyUser>> userValidators, IEnumerable<IPasswordValidator<MyUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<MyUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) {
this.context = context;
}
public override async Task<MyUser?> GetUserAsync(ClaimsPrincipal principal) {
var user = await base.GetUserAsync(principal);
if (user is null)
return user;
await context.Entry(user).Reference(x => x.Address).LoadAsync();
return user;
}
}
Upvotes: 0
Reputation: 71
Best Option in my case is to add a package reference to Microsoft.EntityFrameworkCore.Proxies and then in your services use the UseLazyLoadingProxies
.AddDbContext<YourDbContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
More infos https://learn.microsoft.com/de-de/ef/core/querying/related-data/lazy
Upvotes: 1
Reputation: 393
Update for .NET 6.0 with EF Core 6.0:
You can now configure the property to be automatically included on every query.
modelBuilder.Entity<MyUser>().Navigation(e => e.Address).AutoInclude();
For more info check out: https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager#model-configuration-for-auto-including-navigations
Upvotes: 13
Reputation: 69
I found it useful to write an extension on the 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());
}
Upvotes: 5
Reputation: 239440
The short answer: you can't. However, there's options:
Explicitly load the relation later:
await context.Entry(user).Reference(x => x.Address).LoadAsync();
This will require issuing an additional query of course, but you can continue to pull the user via UserManager
.
Just use the context. You don't have to use UserManager
. It just makes some things a little simpler. You can always fallback to querying directly via the context:
var user = context.Users.Include(x => x.Address).SingleOrDefaultAsync(x=> x.Id == User.Identity.GetUserId());
FWIW, you don't need virtual
on your navigation property. That's for lazy-loading, which EF Core currently does not support. (Though, EF Core 2.1, currently in preview, will actually support lazy-loading.) Regardless, lazy-loading is a bad idea more often than not, so you should still stick to either eagerly or explicitly loading your relationships.
Upvotes: 24
Reputation: 32072
Unfortunately, you have to either do it manually or create your own IUserStore<IdentityUser>
where you load related data in the FindByEmailAsync
method:
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);
}
}
Of course, implementing the entire store just for this isn't the best option.
You can also query the store directly, though:
UserManager<IdentityUser> userManager; // DI injected
var user = await userManager.Users
.Include(x => x.Address)
.SingleAsync(x => x.NormalizedEmail == email);
Upvotes: 48