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

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

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

[当我使用此新映射创建新对象时,一切都会按预期方式进行:这些对象将以正确的值保存在数据库中,并位于外键列等中。这些对象,我实体的导航属性就实现了。

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


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

 ______           _____________
|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;

我不会“丢失”行(如果我有40个样本,我会得到40行)。因此,外键设置正确。

有人可以向我解释这种行为吗?

c# entity-framework sqlite ef-code-first
1个回答
0
投票

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

一对多模式将看起来像这样:

Sample
SampleID (PK)

SampleResult
SampleResultID (PK)
SampleID (FK)

一对一模式将如下所示:样品SampleID(PK)

SampleResult
SampleID (PK + FK)

因此,在实际上包含1到1行的1对多模式中,您将拥有Sample ID = 1,且SampleResult的SampleResultID = 16且SampleID = 1。如果仅将映射更改为1对1,EF会期望两个表上的PK相等。由于SampleResult的PK为SampleResultId,因此无法获得正确的记录,因为它会比较Sample.SampleId / w SampleResult.SampleResultId(而非SampleResult.SampleId)

您的新测试记录可能会起作用,因为您可能会发现:

Sample.SampleID = **220**

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

如果您使用身份(Int)列作为键,则在出现空链接或获取错误结果的链接时,此问题可能会有所不同。因为您使用的是GUID,并且每个ID相对通用,所以结果将是空链接。

我建议在加载样本/结果对以验证是否连接了正确的列时,在测试数据库上使用探查器捕获正在使用的SQL。

无需更改架构,只要将SampleResult.SampleResultId设置为Identity / Default,您就可以保证不存在一对多实例(单个样本有多个结果)能够更新EF中的映射,以将SampleResult上的SampleId视为键。这样,使用新的映射EF会将2链接在一起。这将意味着您之前的“新”测试记录将不再起作用,因为它们会将Sample.SampleID与SampleResult.SampleResultID结合使用,但是您的原始记录应该起作用,而新记录也应该起作用。 EF的键不必与表上的PK匹配,只要您使用的是DB-First(无迁移)并且满足模式即可。如果SampleResultId的DB列默认为NewSequentialId()NewId(),则无需担心。如果PK是用代码设置的,则可能需要将该属性映射为常规列,并记下使用SampleId的注意事项。如果您有任何其他基于SampleResultId链接到SampleResult的实体,则可能会“中断”。加入时,EF将把SampleId作为键。

[最后一点:我对Sample和SampleResult之间的一对多关系会导致性能问题这一结论感到有些警觉,可以通过重新映射为1-to-1来解决该问题。虽然一对一关系仅包含一个孩子会产生误导,但最终,EF在这两种情况下都将使用内部联接。我强烈怀疑还有其他事情在起作用。如果将NewId() / Guid.New用于新行,则随着系统的增长,GUID键可能会出现问题。这是因为聚簇索引中128位密钥的相对随机性可能导致索引表中的大量碎片。最好使用NewSequentialId()或基于代码的NewSequentialId()实现来生成唯一的但可排序的GUID,以将这种影响最小化,并将其与数据库表的常规索引维护相结合。

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