EF Core:多个 DbContext 与一个数据库

问题描述 投票:0回答:3

我正试图重新引发这个话题。它曾经在十多年前开始,虽然有一些评论是几年前的,但现在已经关闭了。再说一遍,这对我来说仍然有点不清楚。

很多人对此提出了自己的看法,虽然有一些很好的理由和意见,但实际方法和总体结论仍然不清楚,至少对我来说是这样。

我的情况:我正在尝试创建一种比必要的应用程序更复杂的方式来研究和测试事物。到目前为止,我一直只使用一个 DbContext,现在我希望通过创建一个新的身份/安全性来将其分离(我知道 IdentityDbContext 存在,虽然这是一个更智能的解决方案,但我想尝试一下)。 当我设法配置第二个 DbContext 并创建迁移时,迁移状态已重置为“初始”并创建新表(而不是使用旧表)。

我的问题:应用新 DbContext 的引入以及如何将到目前为止的迁移快照复制到其中(以便继续序列)的一个很好的实际示例是什么。另外,将迁移快照的未来状态复制到原始 DbContext 等。

以下是我乱写的一些代码示例:

这是 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;
        }
   }

新引入的 DbContext - OnModelCreating、Ignore Reservations 以及之后的所有内容,以便专注于 3 个重要的表(是否有更好的方法来做到这一点)。

    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>();
        }
    }

到目前为止一直在使用的 DbContext。忽略应该在其中的表,即 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 - 配置示例,它是一个分隔两个上下文的表(但包含在两个上下文中)。

    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();
        }
    }

可能值得注意的是,我还决定将 SecurityDbContext 逻辑分离到不同的项目。

请随时给我所有的建议和现实世界的经验。我将不胜感激!

c# .net entity-framework asp.net-core .net-6.0
3个回答
4
投票

您应该为每个 DbContext 生成不同的迁移。如果您希望将不同的实体放在同一个数据库表中,请在每个配置中使用 ToTable() 方法明确说明这一点。此外,它们的配置应该匹配。最后,您还应该明确数据库名称,因此在每个 OnModelCreating 中您应该传入 builder.HasDefaultScheme() 相同的值。

假设我有一个从 IdentityDbContext<> 继承的 IdentityDbContext,这是在 asp 中获得的默认值,但有一个从 IdentityUser 继承的 AppUser,所以我有一个配置。然后我就会有类似的东西:

public class IdentityDbContext : IdentityDbContext<AppUser>
{
    //Other stuff

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.HasDefaultSchema("TestDb");
        builder.ApplyConfiguration(new AppUserConfiguration())
        base.OnModelCreating(builder);
    }

}

然后我有一个继承自DbContext的WriteDbContext,并且我有一个客户的配置:

public class WriteDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    //Other stuff

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.HasDefaultSchema("TestDb");
        builder.ApplyConfiguration(new CustomerConfiguration())
        base.OnModelCreating(builder);
     }
}

此时我需要为每个 DbContext 生成迁移,然后应用它们。然后我会将所有身份信息和客户放在同一个数据库中。

我还可以有一个只能用于读取的 CustomerReadModel,因此它没有任何逻辑、私有字段,并且可能具有到其他 ReadModel 的导航。只要它们都具有相同的配置,例如它们中的 FirstName 都配置为 nvarchar(50),如果客户有一个地址(作为实体),则 CustomerReadModel 也有一个或者它有一个 AddressReadModel配置为地址等,并且在这两种配置中我都有 builder.ToTable("Customers") 都指向相同的客户数据库表:

public class ReadDbContext: DbContext
{
    public DbSet<CustomerReadModel> Customers{ get; set; }

    //Other stuff

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.HasDefaultSchema("TestDb");
        builder.ApplyConfiguration(new CustomerReadModelConfiguration())
        base.OnModelCreating(builder);
    }
}

现在不应为 ReadDbContext 添加迁移,因为数据库已配置。

如果您愿意,您也可以查看这些(免责声明:它们是我的):

https://www.youtube.com/watch?v=QkHfBNPvLms

https://www.youtube.com/watch?v=MNtXz4WvclQ

编辑:我前段时间做了一个演示,其中包含上述一些内容:https://github.com/spyroskatsios/MakeYourBusinessGreen

编辑2:根据David的建议,我添加了用于注册DbContexts的代码。在这个例子中我使用Sqlserver:

  private static IServiceCollection ConfigureSqlServer(this IServiceCollection services, IConfiguration configuration)
  {
    services.AddDbContext<WriteDbContext>(options =>
    {
        options.UseSqlServer(configuration.GetConnectionString("SqlDb"));
    });

    services.AddDbContext<ReadDbContext>(options =>
    {
        options.UseSqlServer(configuration.GetConnectionString("SqlDb"))
            .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Disable tracking since it's only for read purposes
    });

    services.AddDbContext<IdentityDbContext>(options =>
    {
        options.UseSqlServer(configuration.GetConnectionString("SqlDb"));
    });

    return services;
  }

1
投票

我最终所做的是创建另一个继承自相同 DbContextExtend 基础的 SecurityDbContext。此外,ReservationsDbContext 也是从同一基础继承的。

所有迁移都通过 ReservationsDbContext 进行,而数据库活动则通过各自的上下文进行。

最后,没有必要将迁移从一个 DbContext 复制到另一个 DbContext。


0
投票

也只是抛出我的答案,但对于我的情况,我需要为每个数据库上下文有一个单独的数据库和迁移(代码优先方法),给出:

  • 我的DbContext
  • 另一个DbContext

两者都在扩展

DbContext
,但每次我需要为
MyDbContext
生成/添加迁移时,它也会带来来自
AnotherDbContext

的更新

通过在我的上下文中应用正确的配置,我能够实现我想要的结果

OnModelCreating()

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.ApplyConfiguration(InitializeYourConfigurationClassHere());
  ...

  base.OnModelCreating(modelBuilder);
}

我希望它能有所帮助!干杯!

© www.soinside.com 2019 - 2024. All rights reserved.