Hi,
I am currently working on a DogCarePlatform that has both Owners as users and Dogsitters as users. I've reached a point where I need both of those entities to be seperate Identities and act like AspNetCoreUsers. I don't know how to create a second type of
Identity. This is my code up till now.
Dogsitter class:
public class Dogsitter : ApplicationUser
{
public Dogsitter()
{
this.Id = Guid.NewGuid().ToString();
this.Appointments = new HashSet<Appointment>();
this.Comments = new HashSet<Comment>();
}
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public decimal WageRate { get; set; }
public string Address { get; set; }
public decimal Rating { get; set; }
public ICollection<Comment> Comments { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
Owner class:
public class Owner : ApplicationUser
{
public Owner()
{
this.Id = Guid.NewGuid().ToString();
this.Comments = new HashSet<Comment>();
this.Dogs = new HashSet<Dog>();
this.Appointments = new HashSet<Appointment>();
}
public string Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public decimal Rating { get; set; }
public string Address { get; set; }
public ICollection<Comment> Comments { get; set; }
public ICollection<Dog> Dogs { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
Application user class
public class ApplicationUser : IdentityUser, IAuditInfo, IDeletableEntity
{
public ApplicationUser()
{
// Application user
this.Id = Guid.NewGuid().ToString();
this.Roles = new HashSet<IdentityUserRole<string>>();
this.Claims = new HashSet<IdentityUserClaim<string>>();
this.Logins = new HashSet<IdentityUserLogin<string>>();
}
// Audit info
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
// Deletable entity
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
public virtual ICollection<IdentityUserRole<string>> Roles { get; set; }
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
}
And this is the appointment class that needs to do the relations properly between Dogsitter and Owner:
public class Appointment : BaseDeletableModel<string>
{
public bool IsHappening { get; set; }
public int Timer { get; set; }
public decimal TaxSoFar { get; set; }
public DateTime Date { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string OwnerId { get; set; }
public virtual Owner Owner { get; set; }
public string DogsitterId { get; set; }
public virtual Dogsitter Dogsitter { get; set; }
}
After all the migrations and updates have been applied I am left with AspNetCoreUsers table that has all of the Dogsitter's and Owner's properties in one place. There are no Dogsitters or Owners tables in the end.
This is the ApplicationDbContext.cs:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private static readonly MethodInfo SetIsDeletedQueryFilterMethod =
typeof(ApplicationDbContext).GetMethod(
nameof(SetIsDeletedQueryFilter),
BindingFlags.NonPublic | BindingFlags.Static);
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Dogsitter> Dogsitters { get; set; }
public DbSet<Owner> Owners { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Dog> Dogs { get; set; }
public DbSet<Appointment> Appointments { get; set; }
public DbSet<Setting> Settings { get; set; }
public override int SaveChanges() => this.SaveChanges(true);
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.ApplyAuditInfoRules();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) =>
this.SaveChangesAsync(true, cancellationToken);
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default)
{
this.ApplyAuditInfoRules();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
protected override void OnModelCreating(ModelBuilder builder)
{
// Needed for Identity models configuration
base.OnModelCreating(builder);
builder.Entity<Appointment>()
.HasOne(p => p.Owner)
.WithMany(t => t.Appointments)
.HasForeignKey(m => m.OwnerId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Appointment>()
.HasOne(p => p.Dogsitter)
.WithMany(t => t.Appointments)
.HasForeignKey(m => m.DogsitterId)
.OnDelete(DeleteBehavior.Restrict);
ConfigureUserIdentityRelations(builder);
EntityIndexesConfiguration.Configure(builder);
var entityTypes = builder.Model.GetEntityTypes().ToList();
// Set global query filter for not deleted entities only
var deletableEntityTypes = entityTypes
.Where(et => et.ClrType != null && typeof(IDeletableEntity).IsAssignableFrom(et.ClrType) && et.BaseType == null);
foreach (var deletableEntityType in deletableEntityTypes)
{
var method = SetIsDeletedQueryFilterMethod.MakeGenericMethod(deletableEntityType.ClrType);
method.Invoke(null, new object[] { builder });
}
// Disable cascade delete
var foreignKeys = entityTypes
.SelectMany(e => e.GetForeignKeys().Where(f => f.DeleteBehavior == DeleteBehavior.Cascade));
foreach (var foreignKey in foreignKeys)
{
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
}
private static void ConfigureUserIdentityRelations(ModelBuilder builder)
{
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
private static void SetIsDeletedQueryFilter<T>(ModelBuilder builder)
where T : class, IDeletableEntity
{
builder.Entity<T>().HasQueryFilter(e => !e.IsDeleted);
}
private void ApplyAuditInfoRules()
{
var changedEntries = this.ChangeTracker
.Entries()
.Where(e =>
e.Entity is IAuditInfo &&
(e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entry in changedEntries)
{
var entity = (IAuditInfo)entry.Entity;
if (entry.State == EntityState.Added && entity.CreatedOn == default)
{
entity.CreatedOn = DateTime.UtcNow;
}
else
{
entity.ModifiedOn = DateTime.UtcNow;
}
}
}
}
I would really like some input here. Thanks in advance.