【问题标题】:Entity Framework Core Keyless Navigation Problem实体框架核心无键导航问题
【发布时间】:2020-05-09 01:42:36
【问题描述】:

我正在为我的公司构建概念验证。我们有一个现有的平台,我们将用现代技术来替换它。目前,我仅限于使用我们现有的具有多个无键表的数据库。我正在构建一个 Blazor WebAssembly 应用程序,该应用程序使用 gRPC 调用 .net 核心 Web 应用程序。我的问题是我正在使用 EF Core 与现有数据库进行通信,但是在调用 DbContext.Attach(shipment)时出现异常@

"The navigation '' cannot be added because it targets the keyless entity type 'Document'. Navigations can only target entity types with keys."

Document 是被Attached 到DbContext 的实体上的导航属性。

Shipment 是一个非常庞大且复杂的数据结构,具有很多层。有没有办法避免在模型结构中为每个实体 DbContext.Entry(entity).State = EntityState.Added 设置实体状态?

或者可能将 DbSet 的 CRUD 操作映射到存储过程?

编辑

这是一个大大缩短的架构版本,但这就是与问题相关的全部内容。

public class Shipment
{
    public int Id { get; set; }
    // 60 other properties

    public virtual ICollection<Document> Documents { get; set; }
}

public class Document
{
    public int ShippingId { get; set; }
    public string DocumentType { get; set; }
    public byte[] Content { get; set; }

    public virtual Shipment Shipment { get; set; }
}

当我尝试将Shipment 实体附加到DbContext 或手动设置其状态时,我得到了上述异常。

同样,我目前无法更改数据库架构,还有许多其他类似于 Document 的模型没有密钥或添加密钥的方法。

编辑 2

我知道这需要进一步解释。显然,DbContext 将无法插入/更新/删除,因为没有密钥,它不知道如何操作。我只需要在获取Shipping 实体并设置导航属性Documents 时能够阅读。但是保存Shipping时,完全忽略导航属性即可。

【问题讨论】:

  • 您有可以分享的架构或数据库图吗?我很难理解您希望无键​​表格的导航属性如何工作。
  • 考虑阅读Keyless Entity Types 文档。它们有很多限制,基本上只能用于数据读取,不能用于添加/更新/删除。即只有 CRUD 的 R 部分。
  • 如错误提示,将 Key 添加到 Document 实体。您可以使用 Data annotations [Key] 将属性表示为 pk。
  • @LinkedListT 正如我上面所说的几次,我无法更改数据库架构。
  • 我们不要求更改数据库中的架构。实体具有将数据库对象链接到 c# 类的映射文件(模型)。模型需要更新。

标签: c# entity-framework .net-core entity-framework-core asp.net-core-2.1


【解决方案1】:

在试图证明@jdweng 错误并生成异常时,我曾多次看到实体模型在数据库中没有支持字段,因此我偶然发现了这个解决方案。

我将public Guid Id { get; set; } = Guid.NewGuid(); 添加到我所有的无钥匙实体中,然后使用ModelBuilderentity.HasNoKey(); 替换为entity.HasAlternateKey(x =&gt; x.Id);

HasAlternateKey 的摘要:如果指定属性上尚不存在此实体类型的替代键,则在模型中为此实体类型创建一个备用键。这将强制属性为只读。

这正是我想要的。

【讨论】:

  • 如何将新属性Id添加到模型中,而不像builder.Ignore(e =&gt; e.Id)一样将其从EntityTypeBuilder中的映射中排除?
  • @user3231784 你的语法很混乱。您是否希望将 Id 排除在映射到数据库之外?
  • 抱歉语法问题。我有一个类似的问题,我了解到您已在模型类中添加了一个新属性 Id 以定义一个键。而无密钥数据库表不包含这样的列。如果是这样,我认为应该需要entity.Ignore(e =&gt; e.Id)?我想知道在这种情况下是否可以定义entity.HasAlternateKey(e =&gt; e.Id)?但也许我理解错了。
  • @user3231784 您不需要调用entity.Ignore(e =&gt; e.Id),因为entity.HasAlternateKey(e =&gt; e.Id) 已经这样做了。但是,它使对象完全只读。
  • 我的问题是我试图将一个对象保存到数据库中,但它包含一组无键对象。 EF 没有办法确定子对象是新对象还是已经存在,因此它引发了异常。使用entity.HasAlternateKey(e =&gt; e.Id),子对象将被完全忽略。未保存或更新。
【解决方案2】:

如果该 Document 类反映了文档表的完整架构,那么您不能也可能不应该尝试通过 EF 导航属性关联文档。虽然架构可以通过没有 PK 的表的单向关系完美运行,但 EF 无法以这种方式管理关系。您如何准确地将一份文件与另一份文件区分开来?系统可能永远不需要,但作为一个旨在可靠地加载和管理数据的 ORM,这是一个要求。

通常在表没有 PK 的情况下,只要有足够的字段来唯一区分记录,就可以通过定义键来满足 EF。例如 ShippingId + CreatedAt,如果需要,可能还有 CreatedBy。但是,对于您概述的架构,只有二进制内容不能用作密钥的一部分。

如果您无法调整架构以引入无意义的键(所有表都应该有一个 PK),那么我会说您唯一的选择是就 EF 而言删除关联,并通过 Shipment 完全单独加载文档。对于很少使用的潜在大型二进制数据,具有导航属性可能具有潜在危险,因为尝试 Include 它们或使其延迟加载代理被序列化程序之类的东西意外触发可能会使系统瘫痪。

【讨论】:

  • 我同意你所说的一切,并认为最终会是这样。但是,Shipment 实体中有几十个导航属性,或者嵌套得更深。所以我希望有一种方法可以在阅读时获取所有这些值,以避免手动进行数十次额外调用来加载所有只读字段。
  • 只要相关实体有 PK,或者至少有足够的字段来唯一标识一行并且可以在 EF 中配置为 Key,那么您就可以使用导航属性。它只是像 Document 这样的实体,缺少您需要离开并单独处理的标识符。如果这些类型的表很常见,我会探讨为什么不允许模式修改。您不需要向表中添加 PK 来满足这一点,只需一个唯一的(最好是索引的)标识列,EF 可以将其映射为键。大多数数据库可以自动附加和填充这些。
  • 架构修改不是立即的选择,因为我们有数百个客户,而且这种类型的全局更改过于密集。
猜你喜欢
  • 2016-11-27
  • 2021-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 2020-07-08
  • 2021-08-23
相关资源
最近更新 更多