I am trying to rekindle this topic. It was once started over 10 years ago, and while there were some comments that are a couple years ago, it has now been closed. Yet again, this still is kind of unclear to me.
A lot of people put their opinions on it, and whilist there were some good reasons and opinions, the practical approach, and overall conclusion is still unclear, at least to me.
My situation: I am trying to create a way more complicated than neccesary app to research and test things. I've been using only one DbContext up until now and now I wish to separate it by creating a new one for identity/security (I am aware that IdentityDbContext exists, and while this is a smarter solution, I wanna play around). As I managed to configure the second DbContext and create a migration, the migration state has been resetted to "Initial" and new tables are created (instead of using the old ones).
My question: What is a good practical example where introduction of the new DbContext is applied and how to replicate the up until now Migration Snapshot to it (in order to continue the sequence). Also, to replicate the future state of Migration Snapshot to the original DbContext and so on.
Here are some code examples that I managed to scribble:
This is a part of the base class extension for DbContexts.
public class DbContextExtend : DbContext
{
protected readonly ICurrentUserService _userService;
protected readonly IDateTime _dateTime;
public DbContextExtend(DbContextOptions<ReservationDbContext> options) : base(options) { }
public DbContextExtend(DbContextOptions<ReservationDbContext> options,
IDateTime datetime,
ICurrentUserService userService) : base(options)
{
_dateTime = datetime;
_userService = userService;
}
public DbContextExtend(DbContextOptions<SecurityDbContext> options) : base(options) { }
public DbContextExtend(DbContextOptions<SecurityDbContext> options,
IDateTime datetime,
ICurrentUserService userService) : base(options)
{
_dateTime = datetime;
_userService = userService;
}
public DbSet<Audit> Audits { get; set; }
public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
//var auditEntries = await OnBeforeSaveChangesAsync();
var result = await base.SaveChangesAsync(cancellationToken);
//await OnAfterSaveChanges(auditEntries);
return result;
}
}
A freshly introduced DbContext - OnModelCreating, Ignore Reservations and everything after it in order to focus on the 3 tables of importance (is there a better way to do this).
public class SecurityDbContext : DbContextExtend, ISecurityDbContext
{
public SecurityDbContext(DbContextOptions<SecurityDbContext> options) : base(options) { }
public SecurityDbContext(DbContextOptions<SecurityDbContext> options,
IDateTime datetime,
ICurrentUserService userService) : base(options, datetime, userService) { }
public DbSet<User> Users { get; set; }
public DbSet<LoginDetails> LoginDetails { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new Configurations.SecurityConfiguration.RoleConfiguration());
modelBuilder.ApplyConfiguration(new Configurations.SecurityConfiguration.UserConfiguration());
modelBuilder.ApplyConfiguration(new Configurations.SecurityConfiguration.LoginDetailsConfiguration());
modelBuilder.Ignore<Reservation>();
}
}
The DbContext that was in use up untill now. Ignoring the table that should be out of it, the LoginDetails.
public class ReservationDbContext : DbContextExtend, IReservationDbContext
{
public ReservationDbContext(DbContextOptions<ReservationDbContext> options) : base(options) { }
public ReservationDbContext(DbContextOptions<ReservationDbContext> options,
IDateTime datetime,
ICurrentUserService userService) : base(options, datetime, userService) { }
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<LoginDetails> LoginDetails { get; set; }
public DbSet<EventType> EventTypes { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<Question> Questions { get; set; }
public DbSet<EventQuestion> EventQuestions { get; set; }
public DbSet<EventOccurrence> EventOccurrences { get; set; }
public DbSet<Ticket> Tickets { get; set; }
public DbSet<Reservation> Reservations { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new EventTypeConfiguration());
modelBuilder.ApplyConfiguration(new EventConfiguration());
modelBuilder.ApplyConfiguration(new QuestionConfiguration());
modelBuilder.ApplyConfiguration(new EventOccuranceConfiguration());
modelBuilder.ApplyConfiguration(new ReservationConfiguration());
modelBuilder.ApplyConfiguration(new TicketConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.Ignore<LoginDetails>();
}
}
UserConfiguration - an example of a configuration and it is a table that separates both contexts (but is contained in both).
public class UserConfiguration : AuditableEntityConfiguration<User>
{
public override void ConfigureAuditableEntity(EntityTypeBuilder<User> builder)
{
builder.HasKey(u => u.Id);
builder.Property(u => u.Email)
.HasMaxLength(128)
.IsRequired();
builder.Property(u => u.Name)
.IsRequired();
builder.Property(u => u.PhoneNumber)
.HasMaxLength(20)
.IsRequired(false);;
builder.Property(u => u.RoleId)
.IsRequired();
builder.HasOne(u => u.Role)
.WithMany(r => r.Users)
.HasForeignKey(u => u.RoleId);
builder.HasOne(u => u.LoginDetails)
.WithOne(ld => ld.User)
.HasForeignKey<LoginDetails>(u => u.UserId)
.IsRequired();
}
}
It might be worth noting that I also decided to separated SecurityDbContext logic to a different project.
Please feel free to give me all advice and real world experience that you can. I would greatly appreciate it!