Entity Framework - 更新的关系不检索从旧关系创建的数据

Entity Framework - Updated relationship does not retrieve data created from the old relationship

我最近更新了我的 Entity Framework(版本 6.2.0)映射以提高我的应用程序的性能,但没有修改数据库架构。基本上,我有一个 One-to-Many 的关系,我变成了一个 One-to-One 的关系。

当我使用这个新映射创建新对象时,一切都按预期工作:对象保存在数据库中,外键列中的值正确,等等。当我重新启动我的应用程序(并重置 DbContext)时并访问这些对象,我的实体的导航属性得到满足。

当我尝试访问在此 EF 映射更新之前创建的对象时出现问题。对于那些旧对象,导航属性为空,我无法访问关系的另一端。


具体来说,我有两个这样定义的表:

 ______           _____________
|Sample|         |Sample_Result|
|------|         |-------------|
|Id    |<--------|Result_Of    |
|______|  Fk     |_____________|

从商业的角度来看,一个样本只能有一个结果。但是我们曾经将它映射到 Entity Framework 中作为一对多关系如下:

[Table("Sample")]
public class SampleEntity
{
    public virtual IList<SampleResultEntity> Results { get; set; }
}

[Table("Sample_Result")]
public class SampleResultEntity
{
    [Required]
    [Column("Result_Of")]
    public Guid ResultOfFk { get; set; }

    [ForeignKey(nameof(ResultOfFk))]
    public virtual SampleEntity ResultOf { get; set; }
}

此映射的性能很糟糕,因此我最近将其更新为一对一关系:

[Table("Sample")]
public class SampleEntity
{
    public virtual SampleResultEntity Result { get; set; }
}

[Table("Sample_Result")]
public class SampleResultEntity
{
    [Required]
    [Column("Result_Of")]
    public Guid ResultOfFk { get; set; }

    public virtual SampleEntity ResultOf { get; set; }
}

并且在我的 dbContext 中,我添加了以下映射:

 modelBuilder.Entity<SampleResultEntity>()
            .HasRequired(sr => sr.ResultOf)
            .WithRequiredDependent(s => s.Result);

当我访问映射更新后创建的样本时,sample.Result 被定义。

当我访问映射更新之前创建的样本时,sample.Result 为空。

当我在两个表上使用内部联接查询数据库时,如下所示:

SELECT *
  FROM Sample s
  INNER JOIN Sample_Result sr ON s.Id = sr.Result_Of;

我没有 "lose" 行(如果我有 40 个样本,我得到 40 行)。所以外键设置好了。

谁能给我解释一下这种行为?

您可能需要概述完整的 table 结构,因为根据您的描述,您不能简单地将映射从 1-to-many 更改为基于模式的 1-to-1。

1-to-many 架构将如下所示:

Sample
SampleID (PK)

SampleResult
SampleResultID (PK)
SampleID (FK)

一对一架构如下所示: 样本 样本 ID (PK)

SampleResult
SampleID (PK + FK)

因此,在包含有效 1 到 1 行的 1-to-many 架构中,Sample ID=1,SampleResult 的 SampleResultID = 16,SampleID = 1。 如果您只是将映射更改为 1 对 1,EF 会期望两个 table 上的 PK 相等。由于 SampleResult 的 PK 是 SampleResultId,因此不可能获得正确的记录,因为它会比较 Sample.SampleId /w SampleResult.SampleResultId(而不是 SampleResult.SampleId)

您的新测试记录似乎有效,因为您可能会发现:

Sample.SampleID = **220**

SampleResult.SampleResultID = **220**
SampleResult.SampleID = 220

如果您为键使用标识 (Int) 列,则此问题的表现会有所不同,您可能会得到空的 links,或者得到 links 的结果不正确。因为您使用的是 GUID,并且每个 ID 都相对普遍唯一,所以结果将为空 links。

我建议在您的测试数据库上使用 Profiler 来捕获在加载 Sample/Result 对时使用的 SQL 以验证是否连接了正确的列。

在不改变schema的情况下,只要将SampleResult.SampleResultId设置为Identity/Default,就可以保证没有1-to-many 实例(单个样本的多个结果)您应该能够更新 EF 中的映射以将 SampleResult 上的 SampleId 视为键。这样,您的新映射 EF 将 link 2 放在一起。这将意味着您之前的 "new" 测试记录将不再有效,因为它们将 Sample.SampleID 与 SampleResult.SampleResultID 结合在一起,但您的原始记录应该有效,更新的记录也应该有效. EF 的密钥不必与 table 上的 PK 匹配,只要您使用的是 DB-First(无迁移)并且架构得到满足。如果 SampleResultId 的数据库列默认为 NewSequentialId()NewId(),那么您无需担心。如果 PK 在代码中设置,则您可能需要将 属性 映射为常规列并记下使用 SampleId。如果您有任何其他实体 link 正在根据 SampleResultId 访问 SampleResult,则可以 "break"。 EF在加入时会将SampleId视为Key。

最后一点:我对 Sample 和 SampleResult 之间的 1-to-many 关系导致性能问题的结论有点担心,而这些问题将由 re-mapping 解决为 1 -对-1。虽然 1-to-many 关系仅包含 1 child 有点误导,但最终 EF 将在这两种情况下使用内部联接。我强烈怀疑有其他事情在起作用。如果您对新行使用 NewId() / Guid.New,那么随着系统的增长,GUID 键可能会出现问题。这是因为聚集索引中 128 位密钥的相对随机性会导致索引 table 中出现大量碎片。最好使用 NewSequentialId()NewSequentialId() 的 code-based 实现来生成唯一但 sort-able GUID 以最小化这种影响,并将其与数据库的定期索引维护相结合tables.