实体框架(DDD),与两个外键的关系

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

我正在尝试使用 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,所以如果有任何帮助,我将不胜感激。

c# entity-framework .net-core domain-driven-design
1个回答
0
投票

第一个问题是 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。

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