I am using ASP.NET Core targeting NET6.0 using the Microsoft.AspNetCore.Identity user management system with EntityFrameworkCore.
I have a custom dependent entity (Players) which I want to link to my custom ApplicationUser class in a one-to-one relationship. Every AspNetUsers record has an associated single record in the previously existing my_db_schema.tblPlayers database table. The AspNetCore identity tables are in the same database. Both are populated with real data (This project is to migrate from AspNetIdentity to AspNetCore.Identity)
[Table("tblPlayers", Schema = "my_db_schema")]
public class Players
{
//[Key]
public int Id { get; set; }
public string PlayerName { get; set; }
//[ForeignKey("User")]
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
The UserId is a foreign key to the Id column on the AspNetUsers table. The Id column is a primary key.
My custom ApplicationUser class is defined as this:
public class ApplicationUser : IdentityUser<string>
{
public Players Players { get; set; }
}
In my custom ApplicationDbContext OnModelCreating function, I have the following:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Players>().HasKey(t => t.Id);
modelBuilder.Entity<ApplicationUser>()
.HasOne<Players>(s => s.Players)
.WithOne(u => u.User)
.HasForeignKey<Players>(ad => ad.UserId).HasPrincipalKey<ApplicationUser>(x => x.Id).IsRequired();
}
I am not interested in any automatic removal/management of data in the tblPlayers database table. I am not interested in running migrations or scaffolding. All that I need is that for any place in my code where I get an instance of an ApplicationUser, that the Players property is populated with data from the tblPlayers database table. No matter what combination of fluid API, or annotations I use, the ApplicationUser.Players property is always null, and the rest of the ApplicationUser properties are populated as expected.
I have seen these articles:
Why custom properties for IdentityUser custom class are always null
Custom property is null IdentityUser
But both of these require a specific instantiation of the custom ApplicationUserManager class. I would like to avoid having to do that in all areas of my code where ApplicationUserManager is referenced if possible.
Please help :)
EDIT: I have implemented my own custom ApplicationUserManager already.
Implementing a custom UserManager is trivial -
public class CustomUserManager : UserManager<User>
Then register it at startup
services.AddIdentity<User, Role>()
.AddUserManager<QuestUserManager>()
From there you'll just inject the custom user manager class where you need it.
public class SomeController : Controller {
private readonly CustomUserManager _userManager;
public SomeController(CustomUserManager userManager)
{
_userManager = userManager;
}
}
The only way you'll get the Players property populated is to override the method(s) that return a User and add .Include(u => u.Players) to the EF Core queries - something like
public override async Task<User> FindByIdAsync(string userId)
return await Users.Include(u => u.Players).SingleOrDefaultAsync(u => u.Id == userId);
}
You don't have to create your own custom UserManager in order to do that. But #Mr.T is right, it is necessary to include the dependant table in order to retrieve records filled.
So you have 3 options, except creating a custom UserManager:
In case you are following the Repository pattern you would do something like the following:
//Yor 'UserRepository' or whatever
public async Task<List<ApplicationUser>> GetUsers()
{
var set = _context.Set<ApplicationUser>()
.Include(user => user.Players);
return await set.ToListAsync();
}
In case you are using the DbContext directly - you would basically do the same as within the method above.
And in case you are using the default UserManager:
var filledUsers = await _userManager.Users.Include(user => user.Players).ToListAsync();
Related
I'm currently trying to write to a table which inherits from an abstract base class. When I try to do this I get the following error (The ContactMethod property is the discriminator):
System.Data.SqlClient.SqlException: Invalid column name 'ContactMethod'.
EmailContactDetails.cs:
public class EmailContactDetail : ContactDetail
{
[ApiMember(Description = "The Contact Method")]
public override ContactMethod ContactMethod => ContactMethod.Email;
[ApiMember(Description = "Email Address")]
public string EmailAddress { get; set; }
}
EmailContactDetailConfiguration.cs:
public class EmailContactDetailsConfiguration : IEntityTypeConfiguration<EmailContactDetail>
{
public void Configure(EntityTypeBuilder<EmailContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<EmailContactDetail> builder, string schema)
{
builder.Property(x => x.EmailAddress).HasColumnName("EmailAddress").HasColumnType("nvarchar(255)");
}
}
ContactDetail.cs:
public abstract class ContactDetail
{
[ApiMember(Description = "The Identifier")]
public Guid Id { get; set; }
[ApiMember(Description = "The Contact Method")]
public virtual ContactMethod ContactMethod { get; set; }
}
ContactDetailConfiguration.cs
public class ContactDetailsConfiguration : IEntityTypeConfiguration<ContactDetail>
{
public void Configure(EntityTypeBuilder<ContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<ContactDetail> builder, string schema)
{
builder.ToTable("ContactDetails", schema);
// Table per hierarchy. all subclasses share the same db table for performance.
builder.HasDiscriminator(x => x.ContactMethod)
.HasValue<EmailContactDetail>(ContactMethod.Email);
builder.Property(x => x.Id).HasColumnName("Id").IsRequired().HasColumnType("uniqueidentifier").ValueGeneratedOnAdd();
}
}
I've tried hiding the discriminator "ContactMethod" by adding the following to the ContactDetailConfiguration.cs file:
builder.Ignore(x => x.ContactMethod);
Once I've done that I end up with the following error
The entity type 'EmailContactDetail' is part of a hierarchy, but does not have a discriminator property configured.
You shouldn't hide the property configured as TPH discriminator from EF because it is essential for EF Core implementation of the TPH strategy.
The initial error simply indicates that your model and database are out of sync. It's true that by convention EF Core uses string shadow property and column called Discriminator. But the whole purpose of HasDiscriminator fluent API is to allow changing the discriminator property/column type, as well as mapping it to an existing property of your entity model.
Which is the case here. You've told EF Core to use your existing property ContactMethod as discriminator, hence EF Core is looking for column named ContactMethod in the database table. So to resolve the issue, simply update your database from the model (using the usual procedure when model is changed - add new migration, update database etc).
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; }
}
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);
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.
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
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());
}
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
I am using ASP.NET Identity 2.0. Currently I would like to extend ApplicationUser (renamed AspNetUsers to Users) by table UserDetails like FirstName etc.
What's my issue?
I have separated class library project which stores EF Model. I don't like to generate another EF Model (if its not necessary).
I have in SQL Server relationship between Users table and UserDetails table, that's all alright. Unfortunately in ASP project Users table is somewhere deep hard-coded in IdentityUser. That means I have ApplicationUser which inherits IdentityUser.
What am I trying is in AppDbContext:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Ignore columns
builder.Entity<ApplicationUser>().Ignore(c => c.PhoneNumberConfirmed);
// Rename tables
builder.Entity<ApplicationUser>().ToTable("Users");
builder.Entity<IdentityRole>().ToTable("Roles");
builder.Entity<IdentityRoleClaim<string>>().ToTable("IdentityRoleClaims");
builder.Entity<IdentityUserClaim<string>>().ToTable("IdentityUserClaims");
builder.Entity<IdentityUserLogin<string>>().ToTable("IdentityUserLogins");
builder.Entity<IdentityUserToken<string>>().ToTable("IdentityUserTokens");
// Relationship
builder.Entity<ApplicationUser>()
.HasOne(p => p.UserDetails)
.WithOne(i => i.Users)
.HasForeignKey<Shared.Database.UserDetails>(u => u.UserID);
}
Problem is on last line in WithOne(i => i.Users), i.Users is not ApplicationUser. Funny thing is that ApplicationUser has same properties like i.Users :D
So I only need to know how to correctly convert it to make relationship.
Any ideas how to solve this? Or any advice how to do it another way?
Shared project is used by 1 additional project (WCF) so I can't put there inheriting from IdentityUser :/
Thanks guys.
The way I understand it, you want to map two different objects to one and the same database table and also map another entity FK to that table. Adn you have no problem with the first part, but having troubles with the second:
Problem is on last line in WithOne(i => i.Users), i.Users is not ApplicationUser. Funny thing is that ApplicationUser has same properties like i.Users :D
I see two solutions.
Remove the navigation property from the UserDetails class and simply use in both places WithOne().
In case you need that navigation property, you could use approach similar to the Identity class structure as follows:
Let say the UserDetails class that you have currently in the shared project looks like this:
public class UserDetails
{
public string Id { get; set; }
public string FirstName { get; set; }
// other properties ...
public string UserID { get; set; }
public User User { get; set; } // the problematic navigation property
}
You can convert it to a generic class:
public class UserDetails<TUser> where TUser: class
{
public string Id { get; set; }
public string FirstName { get; set; }
// other properties ...
public string UserId { get; set; }
public TUser User { get; set; }
}
and let the old non generic class simply inherit from it:
public class UserDetails : UserDetails<User> { }
Now everything in the shared project EF model should be as before, so the other (WCF) project using it should not be affected.
I guess you already are getting the point. In ASP project, you would just create another class:
public class ApplicationUserDetails : UserDetails<ApplicationUser> { }
and map it to the UserDetails table similar to how you mapped ApplicationUser to Users:
builder.Entity<ApplicationUserDetails>().ToTable("UserDetails");
Then change the UserDetails navigation property in the ApplicationUser class to:
public ApplicationUserDetails UserDetails { get; set; }
and finally set up the relationship w/o any problem:
builder.Entity<ApplicationUser>()
.HasOne(u => u.UserDetails)
.WithOne(d => d.User)
.HasForeignKey<ApplicationUserDetails>(d => d.UserID);
I'm trying to do the following... When a user submits a new article, i want to display the authors (users) username in my web application. That's why i need to "connect" these two and i want to have only 1 DbContext.
Now i'm doing it this way:
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
private DbSet<Article> Articles { get; set; } // This line is all i have added/changed. Everything else was auto-generated when i created a new ASP.net MVC project with individual authentication in visual studio.
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
But i'm not sure if this is the right way to do it. How should this be done properly?
UPDATE (explanation of my comment to DavidG's answer)
First i was retrieving a list of users like this:
class ApplicationUserService
{
public List<ApplicationUser> GetUsers()
{
using (var dbContext = new ApplicationDbContext())
{
return dbContext.ApplicationUsers.ToList();
}
}
}
instead of:
class ApplicationUserService
{
public List<IdentityUser> GetUsers()
{
using (var dbContext = new ApplicationDbContext())
{
return dbContext.Users.ToList();
}
}
}
The problem was that i couldn't get the UserName or any other property because of this mistake. I watched some tutorials online, no one ever mentioned that the DbSet is Users. Anyways... now i know why i don't need this property:
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
This message from VS helped me (intended for other people which should stumble upon the same problem i had):
'ApplicationDbContext.Users' hides inherited member 'IdentityDbContext<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>.Users'.
This is fine if you want to use one context, and the Identity tables are in the same database as your "article" table. I normally like to override OnModelCreating so I can add mapping/configurations for tables I create.
Depending on the size of the application, I leave the Identity context alone, and I create one for my application (I may include the Users table to make it easier to retrieve users). A matter of choice.
Your application just needs to inherit from the correct version of the IdentityDbContext class. You need to use the generic one so you can customise the user class with your own IdentityUser. So you context should be like this. Note the new inheritance and removal of the ApplicationUsers property. This is removed as the Identity class already has this done for you.
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
private DbSet<Article> Articles { get; set; }
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I have a nice clean domain layer in my app that was developed in a DDD fashion. The database was not considered at all when developing the domain. Property names make sense, aren't in ALL CAPS, and are relevant to my application.
Today, I am implementing a repository to pull from an existing EF DbContext. The DbContext was developed to (basically) match a poorly-designed Oracle database.
Ideally, I would like to implement a repository like this:
public interface IRepository {
IQueryable<T> Find<T>(Expression<Func<T, bool>> query) where T : IMyDomainEntity;
}
T is my domain entity. But, inside my Find method in my repository, I have to...
Somehow convert the expression to work with the DbContext
I am not sure how to do this yet.
Query the DbContext
Once the expression is 'mapped', this is simple
Somehow map to my domain object
I'm sure I can use AutoMapper or implement my own mapper.
Return an IQueryable having not made a trip to the database yet.
Not sure this is possible after all the meddling done in #'s 1 - 3
So, how has this problem been solved in the past? Are there any reusable patterns here?
Well, you're on the right track already, just implement what your say you want :)
1.You're passing an expression into your find method so, just use that expression in your Where clause
2.You just need to get the correct DbSet from your DbContext to query against, DbContext has a method to get the DbContext of a given type, use that and you can query like
public IQueryable<T> Find<T>(Expression<Func<T, bool>> query) where T : IMyDomainEntity
{
var dbSet = context.Set<T>();
return dbSet.Where(query);
}
3.If your domain objects are not the ones mapped by EF to the database, you'll need to customize your mapping against what's in your DB in your DbContext class (no need for automapper for that), so you would have something like this in your DbContext class
public class MyContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Map(a => a.ToTable("DB_USERS"))
.Property(a => a.Email).HasColumnName("MAIL");
base.OnModelCreating(modelBuilder);
}
}
To map from the table DB_USERS in the DB to the class User, having different names for the fields, etc. here's an article on that
http://www.codeproject.com/Articles/165720/Using-the-Code-First-Model-Configuration-Classes
You could also map the properties to the correct table columns using attributes if you don't want/can't change your DbContext class
http://msdn.microsoft.com/en-us/data/gg193958
Or you can have a different set of entities that are mapped to your DB and use automapper to translate them into your domain objects, but you lose no. 4 bellos since you'll need to materialize the query to automap it to your domain model.
4.No need to do anything special, EF takes care of the that
UPDATE: Solution without having access to the DbContext (not fully generic version but works)
The idea is to create the mapping part of the repository for each domain class, so all gets binded correctly. Continueing with the User domain model and DBUser table model:
public class User : IDomainModelEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class DBUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int USER_ID { get; set; }
[Required]
[MaxLength(150)]
public string USER_NAME { get; set; }
[Required]
[MaxLength(260)]
public string USER_MAIL { get; set; }
}
Then you would have an abstract Repository and an a concrete repository per domain class that implements the basic GetAll query mapped:
public abstract class Repository<T> where T : IDomainModelEntity
{
protected readonly DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
public abstract IQueryable<T> GetAll();
public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return GetAll().Where(predicate);
}
}
public class UserRepository : Repository<User>
{
public UserRepository(DbContext context)
: base(context)
{
}
public override IQueryable<User> GetAll()
{
return _context.Set<DBUser>()
.Select(u => new User
{
Id = u.USER_ID,
Name = u.USER_NAME,
Email = u.USER_MAIL
});
}
}
now to use it you will just call the find or get all on the repository...
using (var context = new CompanyDbContext())
{
var repo = new UserRepository(context);
var list = repo.Find(a=>a.Id >= 2).ToList();
list.ForEach(a => Console.WriteLine("Id: {0}, Name {1}, email {2}", a.Id, a.Name, a.Email));
}
It is not fully generic since you will need to pass a repository for each domain class you need to use, but it may be an acceptable compromise
Hope this helps