如何处理来自单独 EF Core schemas/contexts 的关系?

How to handle relationships from separate EF Core schemas/contexts?

我正在开发一个全新的应用程序,并决定第一次尝试 DDD,所以在这方面请耐心等待,但请纠正我对如何实施的理解有误的地方。我现在已经观看了大量有关该主题的 Pluralsight 视频,但它们不处理这些更高级的用例,因此不确定去哪里。尝试了几件事,但我不知道该怎么做。教程交易后您首先遇到的事情之一。 :)

我有一个相当复杂的域,但我只会展示对这个问题很重要的简单示例部分。我有 2 个模式......身份和网络。在这些模式中,我有 2 个 table。在Identity中,我对系统中的用户有一个正常的aspnet_users table(从现在开始的用户)。在网络模式中,我有一个聚合根称为网络。网络是我领域很大一部分的核心,只是为了长篇解释而收集的项目。在网络中,我可以有 0 个、1 个或多个项目...例如 'event'。当用户在网络中创建事件时,我需要能够捕获创建该事件的用户。不是严格的业务要求,也不是数据库要求...在网络 table 上粘贴一个 created_by_user_id 字段并称之为好。

这就是我的问题开始的地方。虽然当用户处于相同的限界上下文中时这没什么大不了的,但我的用户 table 在 IdentityDbContext 中,事件 table 在 NetworkDbContext 中。

我已经附上了以下要点的相关 classes...如果您认为缺少您需要的东西,请询问,我会提供。我没有在用户周围添加内容,因为它确实只是 Microsoft aspnet 用户 table 实现。

这里可以看到,网络域实体包含“Name”,它是一个Value Object,包裹了网络的Name。这一切都很好。没有导航 属性 的 CreateByUserId 以非 DDD 方式工作正常。当我开始使它对 DDD 友好时,我想删除 CreatedByUserId 字段并且只有 CreatedByUser。问题是 this 指向的用户在身份上下文中,而不是网络上下文中。

域 CLASS - 网络

public class Network : IntegerEntity
    {
        public NetworkName Name { get;  set; }

        public int CreatedByUserId { get; private set; }
        public User CreatedByUser { get; private set; }


        private Network() { }

        public Network(NetworkName name, User createdByUser)
        {
            Name = name;
            CreatedByUser = createdByUser;
        }

        public Network(int id, NetworkName name, User createdByUser) : base(id)
        {
            Name = name;
            CreatedByUser = createdByUser;
        }
    }

在类型配置中,你可以看到我有几点需要指出:

  1. 我在迁移中使用 EF 类型配置。我附上了我的网络 root/table (NetworkTypeConfig) 的类型配置。 NetworkBaseTypeConfiguration 基础 class 只是将 table 指向“网络”架构,因此每个 table 的代码都不必指定它。但是那个 Base class 也派生自一个叫做 BaseTypeConfiguration 的 class。 class 为类型配置编排了 table 的创建....基本上允许您覆盖您需要的功能...您看到网络当前正在配置列、关系和忽略的关系,但后两个现在实际上没有做任何事情。

  2. 我们使用的是 Postgres....它不区分大小写,因此我们选择将所有 table 和字段小写,这样我们就不必编写带引号的查询。这是 .HasColumnName 的东西……一切正常,但我确实需要它来处理所有 tables/columns/nav 道具。

类型配置 - 网络

 public class NetworkTypeConfiguration : NetworkBaseTypeConfiguration<Network, int>
    {
        public NetworkTypeConfiguration() : base(TableEnums.CoreTables.Networks.GetName()) { }


        public override void ConfigureColumns()
        {
            base.ConfigureColumns();

            Builder.Property(x => x.Name)
                .IsRequired()
                .HasColumnName(NetworksEnums.NetworksColumns.Name.GetName())
                .HasConversion(x => x.Name, y => NetworkName.Create(y).Value);

            Builder.Property(x => x.CreatedByUserId)
                .IsRequired()
                .HasColumnName(NetworksEnums.NetworksColumns.Created_By_User_Id.GetName());
        }

        public override void ConfigureRelationships()
        {
            //Builder.HasOne(x => x.CreatedByUser)
            //    .WithMany()
            //    .HasForeignKey(x => x.CreatedByUserId)
            //    .IsRequired();

            //Builder.HasOne<User>()
            //    .WithMany()
            //    .HasForeignKey(x => x.CreatedByUserId)
            //    .IsRequired();
        }

        public override void ConfigureIgnoredRelationships()
        {
            //Builder.Ignore(x => x.CreatedByUser);
            //Builder.Ignore(x => x.CreatedByUserId);
            
        }
    }

类型配置 - 基本类型

public abstract class BaseTypeConfiguration<TEntity, TKey> : IEntityTypeConfiguration<TEntity> where TEntity : class, IEntity<TKey>
        {
            private const string _COLUMN_ID = "id";
    
            private readonly string _schemaName;
            private readonly string _tableName;
    
            public EntityTypeBuilder<TEntity> Builder { get; internal set; }
    
            public BaseTypeConfiguration(string schemaName, string tableName)
            {
                _schemaName = schemaName;
                _tableName = tableName;
            }
    
            public virtual void Configure(EntityTypeBuilder<TEntity> builder)
            {
                Builder = builder;
    
                ConfigureSchema();
                ConfigureTable();
                ConfigureColumns();
                ConfigureRelationships();
                ConfigureIgnoredRelationships();
            }
    
            public virtual void ConfigureSchema()
            {
                Builder.Metadata.SetSchema(_schemaName);
            }
    
            public virtual void ConfigureTable()
            {
                Builder.ToTable(_tableName);
            }
            
            public virtual void ConfigureColumns()
            {
                Builder.HasKey(x => x.Id);
    
                Builder.Property(x => x.Id)
                    .HasColumnName(_COLUMN_ID);
            }
    
            public virtual void ConfigureRelationships() { }
            public virtual void ConfigureIgnoredRelationships() { }
        }

现在是真正的问题...这些是我尝试过的...

  1. 使用这样的类型配置...仅引用 Id 字段,据我所知,我从 EF 的角度进行了这项工作。但是我没有 DDD 应该提供我的模型的封装。我在 NetworkService class 中而不是在 Network 实体中管理所有内容。

  2. 如果我完全删除 created_by_user_id 列并使用纯 DDD 指导(至少指导是我可以收集的),我 运行 进入 CreatedByUser 属性 无法由 EF 上下文填写...有道理...网络上下文不理解“用户”。我知道我可以通过存储过程支持这一点,但我不知道该怎么做,因为我更希望它在代码中。我也可以在之后查找用户并添加到返回的 ViewModel 中,但这很糟糕必须进行 2 次数据库调用。

  3. 如果我保留由用户 ID 创建的列,但我在 CreatedByUser 上添加了 .HasOne,那么当我 运行 迁移代码时,EF 会创建 User/etc tables 在上下文中。不确定这是否能解决问题,因为它试图在迁移时创建用户的 tables,这是身份上下文所做的重复,因此我的迁移失败。

我应该在这里做什么?我有一个严格的业务要求,我必须跟踪谁创建了事件,逻辑上就是 aspnet_user 记录。

我读过诸如事件溯源模型之类的东西,可以在系统中推动变化……比如在身份域中创建用户时触发事件,然后在网络上下文中创建适当的条目。 ..即...为网络用户单独 table。我必须这样做才能支持 DDD 吗?在我们跟踪所有这些数据时,基本上每个 table 我都希望有 CreatedBy/UdpatedBy 用户。 似乎有很多额外的工作需要支持。

我找不到直接的方法来完成我在这里需要做的事情,但我能够想出一个可行的解决方案。这不是一个理想的解决方案,但它确实有效。

我有效地使用了上面“第 3 期”中的概念。我为 EF 创建了一个新的 DbContext,它完全负责架构迁移。此 EF 上下文包含数据库中每个 table 的 DbSet,并且仅在数据库迁移期间使用。每个模式特定上下文仍用于系统中的数据库事务。

之所以可行,是因为迁移上下文现在理解整个数据库中的所有 table,并且不会复制位于边界之间边界的“边缘”table上下文....即...在这种情况下是用户。我在这里看到的唯一真正的缺点是在 2 个上下文中管理相同 table 的代码重复。这似乎仍然比在我创建的每个上下文中创建精简的“用户”table 少很多工作,这样我就可以 link 知道是谁创建了一行。