如何处理来自单独 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;
}
}
在类型配置中,你可以看到我有几点需要指出:
我在迁移中使用 EF 类型配置。我附上了我的网络 root/table (NetworkTypeConfig) 的类型配置。 NetworkBaseTypeConfiguration 基础 class 只是将 table 指向“网络”架构,因此每个 table 的代码都不必指定它。但是那个 Base class 也派生自一个叫做 BaseTypeConfiguration 的 class。 class 为类型配置编排了 table 的创建....基本上允许您覆盖您需要的功能...您看到网络当前正在配置列、关系和忽略的关系,但后两个现在实际上没有做任何事情。
我们使用的是 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() { }
}
现在是真正的问题...这些是我尝试过的...
使用这样的类型配置...仅引用 Id 字段,据我所知,我从 EF 的角度进行了这项工作。但是我没有 DDD 应该提供我的模型的封装。我在 NetworkService class 中而不是在 Network 实体中管理所有内容。
如果我完全删除 created_by_user_id 列并使用纯 DDD 指导(至少指导是我可以收集的),我 运行 进入 CreatedByUser 属性 无法由 EF 上下文填写...有道理...网络上下文不理解“用户”。我知道我可以通过存储过程支持这一点,但我不知道该怎么做,因为我更希望它在代码中。我也可以在之后查找用户并添加到返回的 ViewModel 中,但这很糟糕必须进行 2 次数据库调用。
如果我保留由用户 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 知道是谁创建了一行。
我正在开发一个全新的应用程序,并决定第一次尝试 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;
}
}
在类型配置中,你可以看到我有几点需要指出:
我在迁移中使用 EF 类型配置。我附上了我的网络 root/table (NetworkTypeConfig) 的类型配置。 NetworkBaseTypeConfiguration 基础 class 只是将 table 指向“网络”架构,因此每个 table 的代码都不必指定它。但是那个 Base class 也派生自一个叫做 BaseTypeConfiguration 的 class。 class 为类型配置编排了 table 的创建....基本上允许您覆盖您需要的功能...您看到网络当前正在配置列、关系和忽略的关系,但后两个现在实际上没有做任何事情。
我们使用的是 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() { }
}
现在是真正的问题...这些是我尝试过的...
使用这样的类型配置...仅引用 Id 字段,据我所知,我从 EF 的角度进行了这项工作。但是我没有 DDD 应该提供我的模型的封装。我在 NetworkService class 中而不是在 Network 实体中管理所有内容。
如果我完全删除 created_by_user_id 列并使用纯 DDD 指导(至少指导是我可以收集的),我 运行 进入 CreatedByUser 属性 无法由 EF 上下文填写...有道理...网络上下文不理解“用户”。我知道我可以通过存储过程支持这一点,但我不知道该怎么做,因为我更希望它在代码中。我也可以在之后查找用户并添加到返回的 ViewModel 中,但这很糟糕必须进行 2 次数据库调用。
如果我保留由用户 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 知道是谁创建了一行。