我正在尝试使用 Fluent API 对 EntityTypeConfifuration 进行建模。我想在 EF 中反映的更简单版本的表如下:
考试
身份证 | 数据 |
---|---|
1 | 行数据 |
考试跟踪项目
身份证 | 追踪值 | Sha1Hash |
---|---|---|
4 | abc | 0x23123 |
考试ExamTrackedItem
身份证 | 考试号 | TrackedFieldId |
---|---|---|
1 | 1 | 4 |
班级代表如下:
考试
public class Exam : Entity, IAggregateRoot
{
public int Id { get; private set; }
private readonly List<ExamExamTrackedItem> _examExamTrackedItems;
}
考试跟踪项目
public class ExamTrackedItem
{
public int Id { get; set; }
public string Value { get; set; }
public byte[] Sha1Hash { get; set; }
}
考试ExamTrackedItem
public class ExamExamTrackedItem
{
public int Id { get; set; }
public int ExamId { get; set; }
public int ExamTrackedItemId { get; set; }
public ExamTrackedItem TrackedItem { get; set; }
}
有什么方法可以模拟这种关系,以便在考试课程中使用新方法:
public void AddTrackedItem(ExamTrackedItem item)
哪个在 ExamTrackedItem 表中创建新条目并在 ExamTrackedItem 中创建正确的引用?我能够像这样建模:
internal sealed class ExamEntityTypeConfiguration : IEntityTypeConfiguration<Exam>
{
public void Configure(EntityTypeBuilder<Exam> builder)
{
builder.ToTable("ExamRoot", "medData");
builder.HasKey(b => b.Id);
builder.OwnsMany<ExamMedService>("_examMedServices", x =>
{
x.WithOwner().HasForeignKey("ExamId");
x.ToTable("ExamMedService", "medData");
x.Property<decimal?>("Price").HasColumnName("Price");
x.HasKey("Id");
});
builder.OwnsMany<ExamExamTrackedItem>("_examExamTrackedItems", x =>
{
x.WithOwner().HasForeignKey("ExamId");
x.ToTable("ExamExamTrackedItem", "medData");
x.Property<ExamTrackedItemType>("TrackedItemType").HasColumnName("TrackedItemType");
x.Property<int>("ExamTrackedItemId").HasColumnName("ExamTrackedItemId");
x.HasKey("Id");
x.OwnsOne<ExamTrackedItem>("TrackedItem", y =>
{
y.WithOwner().HasPrincipalKey("ExamTrackedItemId");
y.ToTable("ExamTrackedItem", "medData");
y.Property<string>("Value").HasColumnName("Value");
y.Property<byte[]>("Sha1Hash").HasColumnName("Sha1Hash");
y.HasKey("Id");
});
});
}
}
但是添加新的 ExamTrackedItem 生成的 SQL 不符合我的需求:
SET NOCOUNT ON;
INSERT INTO [medData].[ExamExamTrackedItem] ([ExamId], [ExamTrackedItemId], [TrackedItemType])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2);
INSERT INTO [medData].[ExamTrackedItem] ([Id], [Sha1Hash], [Value])
VALUES (@p3, @p4, @p5);
我宁愿拥有这样的东西:
INSERT INTO [medData].[ExamTrackedItem] ([Id], [Sha1Hash], [Value])
VALUES (@p3, @p4, @p5);
declare @v_newId in = SCOPE_IDENTITY()
INSERT INTO [medData].[ExamExamTrackedItem] ([ExamId], [ExamTrackedItemId], [TrackedItemType])
values(@p0, v_newId , @p2)
这可能是微不足道的,但我现在正在学习 EF,所以如果有任何帮助,我将不胜感激。
第一个问题是 TrackedItem 是否可以与多个考试关联?如果可以的话,您想要一种多对多的关系。如果不是,则考虑一对多关系,其中 TrackedItems 仅包含 ExamId。虽然拥有关系在多对多场景中是可能的,但它是一个相当奇怪的组合。拥有关系的要点基本上是说 TrackedItems 只能通过考试访问,但多对多关系本质上是关联。您通常使用它们来表达哪些跟踪项目与考试相关联,以及哪些考试与跟踪项目相关联。拥有是关于所有权,而不是关联。
就多对多关系而言,只要连接表确实只需要关联 FK,您就可以简化实体定义以引用两端的集合。例如,在 Exam 中,您不需要 ExamExamTrackedItem 的集合,它可以引用 ExamTrackedItem 的集合,其中连接表在后台映射。在这种情况下,ExamExamTrackedItem 不需要专用的 Id 列,它可以仅使用 ExamId + ExamTrackedItemId 作为复合 PK。这使得 DDD 方法变得更简单,因为 Exam 包含其 TrackedItem 引用的集合。例如:
public void AddTrackedItem(ExamTrackedItem item)
{
ArgumentNullException.ThrowIfNull(item, nameof(item));
if(!ExamTrackedItems.Any(x => x.ExamTrackedItemId == item.ExamTrackedItemId))
ExamTrackedItems.Add(item);
}
EF 将自动处理关联连接表记录的创建。
您需要连接实体的情况是连接表上除了两个 FK 之外还有其他数据列。例如,如果您想要记录 TrackedItem 与考试关联时的 CreatedAt DateTime,或者软删除的 IsActive 标志。在这些情况下,您需要 Exam.ExamExamTrackedItems,因此 DDD 方法的工作方式会略有不同:
public void AddTrackedItem(ExamTrackedItem item)
{
ArgumentNullException.ThrowIfNull(item, nameof(item));
if(!ExamExamTrackedItems.Any(x => x.ExamTrackedItemId == item.ExamTrackedItemId))
ExamExamTrackedItems.Add(new ExamExamTrackedItem(item));
}
此处,Exam 的 AddTrackedItem 方法将负责创建连接实体来包装新的关联跟踪项目。
使用集合导航属性时,重要的是您要确保在添加项目之前,预先加载集合(Exam.ExamTrackedItems 或 Exam.ExamExamTrackedItems)或启用延迟加载代理作为后备。在使用导航属性时,我通常建议使用影子 FK 并避免尝试通过 FK 更新实体的诱惑。相反,让 EF 管理 FK。如果关系设置正确,通过添加到集合来设置引用将看到 EF 自动填充关联的 FK。