【问题标题】:How to POST OData entity and link it to multiple existing entities at the same time?如何发布 OData 实体并将其同时链接到多个现有实体?
【发布时间】:2022-01-13 12:47:47
【问题描述】:

如何将实体发布到 OData 端点,同时将其与正文中的其他现有实体关联?


考虑以下类结构(示例):

class Invoice
{
    public int Id { get; set; }

    public Person Issuer { get; set; }

    public Person Recipient { get; set; }

    // other properties
}

class Person
{
    public int Id { get; set; }

    // other properties
}

InvoicePerson 都是我域中的实体(因此是 Id 属性)。想象一下,两者都暴露在自己的实体集中,所以:

  • GET http://host/odata/People(1)

    返回PersonId = 1

  • GET http://host/odata/Invoices(2)?$expand='Issuer, Recipient'

    返回 InvoiceId = 2IssuerRecipient 在有效负载中展开

现在考虑以下要求:

我想在系统中创建一张新发票,该发票将与现有的发卡人和收件人相关联

如何“告诉”OData 框架我要将给定的导航属性与现有实体相关联?如何通知我的控制器操作这是意图?

理想情况下,我希望 POST 正文看起来像这样:

  • POST http://host/odata/Invoices

    { "发行人": "/odata/People(1)", "收件人": "/odata/People(2)", "Property1": "someValue", “属性 2”:“100”, ... }

一旦服务器收到此有效负载,它应该:

  1. Issuer 属性加载所需的“people(1)”Person。如果不存在,则应返回错误请求。
  2. Recipient 属性加载所需的“people(2)”Person。如果不存在,则应返回错误请求。
  3. 创建一个新的Invoice 实例并从上面分配IssuerRecipient,然后将其保存到数据库中。

我知道 OData 支持使用 entity/relation/$ref 语法使用特殊的 PUT/POST URL 在事后配置关系。有了这样的语法,我就可以做这样的事情:

  1. POST http://host/odata/Invoices

    { "Property1": "someValue", "Property2": "100" }

  2. PUT http://host/odata/Invoices(x)/Issuer/$ref

    {"@odata.id":"http://host/odata/People(1)"}

  3. PUT http://host/odata/Invoices(x)/Recipient/$ref

    {"@odata.id":"http://host/odata/People(2)"}

但是,我希望能够在一个应该以原子方式创建实例的 POST 操作中执行所有这些操作。

我尝试了一些想法来看看服务器会接受什么,这似乎通过了:

{
    "Issuer": { "@odata.id": "/odata/People(1)" },
    "Recipient": { "@odata.id": "/odata/People(2)" },
    "Property1": "someValue",
    "Property2": "100",
    ...
}

但我不知道我如何能够从中读取/解析 ID(例如它是如何在专用的 Ref 方法中完成的),或者即使 OData 标准支持这一点。

现在,我将仅在模型和服务器中传递 ID 属性,假设这总是意味着存在关系,但这远非理想,因为它不够通用,并且会使我的 API 不灵活。

【问题讨论】:

  • 您可以使用服务批处理端点发送批处理请求。有关批处理如何在此处工作的更多详细信息odata.org/documentation/odata-version-3-0/batch-processing
  • 如果请求来自 .NET OData 客户端 docs.microsoft.com/en-us/odata/client/batch-operations,请查看此链接
  • @JohnGathogo 我知道 OData 中的批处理支持,但现在,我希望服务器上的操作是事务性的:即,我不想仅插入 Invoice 实体发现我无法将它与IssuerRecipient 关联。在 POST 中的单个事务中执行所有这些可以让我更强大的数据完整性并简化客户端,因为它不必处理这些极端情况。批处理请求通常是相互独立的,所以我认为这不太合适。
  • “批处理请求通常是相互独立的,所以我认为这不太合适”——这种说法并不完全正确。如果您从客户端发送请求并调用 SaveChangesSaveChangesAsync 并使用 SaveChangesOptions.BatchWithSingleChangeset 作为参数,它保证您是一个事务。
  • 而且单次批处理并不局限于使用OData客户端时。如果您通过此链接odata.org/documentation/odata-version-3-0/batch-processing,您将在示例中看到如何使用批处理和变更集。变更集是一个原子工作单元。这是我实际使用过的东西

标签: c# odata asp.net-core-webapi odata-v4


【解决方案1】:

最简单的解决方案是直接在模型中公开ForeignKey 属性。甚至 MS Doc Entity Relations in OData v4 Using ASP.NET Web API 2.2 解释 $ref 中使用的模型也会暴露 FK。

class Invoice
{
    public int Id { get; set; }

    public int Issuer_Person_Id { get; set; }
    [ForeignKey(nameof(Issuer_Person_Id)]
    public Person Issuer { get; set; }

    public int Recipient_Person_Id { get; set; }
    [ForeignKey(nameof(Recipient_Person_Id)]
    public Person Recipient { get; set; }

    // other properties
}

这不会让您的 API不灵活,而是让您的 APIMORE 灵活。 这也使您可以更好地控制模型的数据库实现,同时仍与数据库引擎无关。

在启用延迟加载的环境中,如果您需要检查相关实体的存在而不需要将其加载到内存中,包括 FK 在内的一些额外的性能优势。

注意:通过在模型中包含 FK,$ref 语法和批处理仍然可以使用,但我们现在可以访问更实用的 FK Id 值,这些值可以在服务器端代码,就像发送值更容易一样。

现在在PATCHPOST 中,我们可以简单地使用ID 直接链接Person 记录。

客户端和服务器端需要相同级别的信息/理解来实现这一点,所以它仍然是通用目的$metadata 文档完整描述了哪些 FK 字段链接相关实体,但此处演示的良好命名约定会有所帮助

{
    "Issuer_Person_Id": 1,
    "Recipient_Person_Id": 2,
    "Property1": "someValue",
    "Property2": "100",
    ...
}

小心:
许多模型设计者选择公开ForeignKey属性的原因之一是,当您尝试发送或处理ForeignKey 相关实体时存在歧义.
对于PATCH 没有混淆,v4.0 规范专门告诉 use 忽略相关实体并且根本不应该发送它。

11.4.3 Update an Entity
如果更新指定了到单值导航属性的绑定和根据相同导航属性绑定到主体实体的键属性的依赖属性,则依赖属性将被忽略并根据绑定中指定的值。

对于POST,但是如果在请求中提供了相关实体以及 FK,则相关实体被假定为 deep insert 并且 FK 被忽略。

11.4.2.2 Create Related Entities when Creating an Entity
每个包含的相关实体都按照创建实体的规则进行处理,就好像它是针对原始目标 URL 发布的一样,该 URL 扩展了该相关实体的导航路径。

启用 FK 后,我的建议是在客户端采取措施,确保您不会尝试将 FK 和请求中的相关实体都发送回 API。

我同意您所建议的帖子中的 @odata.id 是一个合乎逻辑的结论,但是它引发了其他潜在的实现问题,这就是为什么协议提供了针对直接 CRUD 操作的概念代表 ForeignKey 引用的 $ref 端点。

OData V4.0 具有明确的声明性和设计性,因此针对单个资源的操作应仅影响该资源。这就是为什么我们不能在单个查询中PATCH 相关属性,因为与此引用问题一样,存在太多潜在的实现变体和解释它们可能如何工作,以保持规范简洁和受限就是这样。

在起草规范之前,利益相关方之间基本上无法就协议细节和如何处理深度更新的指导达成共识。 ASP.Net FX 和 Core 实现(截至本文为止)仅为 OData 4.0 Minimal Conformance Level OOTB。要提高一致性水平,您需要做很多事情。

批处理是在单个事务查询中执行“可能”影响多个资源的操作的首选机制,但是与仅公开 FK 相比,它是一个更复杂的解决方案!


虽然我们可以使用复杂的语法和批处理以其他方式实现这一点很好,但在模型中公开 FK Id 并使它们可供客户端访问,而不仅仅是在服务器逻辑中,还有许多其他实际好处, IMO 这些可能在正确的情况下是非常大的好处:

  • 优化网格中的数据检索
    如果 许多 行具有指向另一个表中相同记录的链接,则您只需从公用表中下载链接值一次。对于某些类型的数据,从公共表中下载所有可能的查找值会更有效,然后在您的表示层中,您可以根据 Id 加入结果。在某些用例中,这些查找值可能只需要在整个会话中下载一次。

  • ComboBox 关系分配
    有时间和地点,但是通过在模型中包含 FK,可以非常简单地绑定到表示层中的 ComboBoxDropDownList 实现以更改或分配相关实体,实现几乎与网格展示,将控件绑定FK,并在下拉列表中显示相关实体。

2022 年更新

OData v4.01 Minimal Conformance level 应该支持深度更新
但是 .Net 5 运行时使用的当前版本的 ODataLib (v8) 不支持此功能 OOTB,并且仍然仅最低限度地兼容 v4.0,尽管具有一些比以前更高级的功能。

11.4.3.1 Update Related Entities When Updating an Entity
具有 4.01 或更大值的 OData-Version 标头的有效负载可以包括嵌套实体和实体引用,它们指定完整的相关实体集,或表示已添加、删除或更改的相关实体的嵌套增量有效负载.这样的请求被称为“深度更新”。如果嵌套集合表示为与扩展导航属性相同,则成功更新请求中指定的嵌套实体和实体引用集表示根据该关系关联的完整实体集,不得包括添加的链接、删除的链接或删除的实体。

json 有效负载实现与您的建议相似

{
    "@type":"#container.Invoice",
    "Issuer": { "@id": "People(1)" },
    "Recipient": { "@id": "People(2)" },
    "Property1": "someValue",
    "Property2": "100",
    ...
}

现在还有一个嵌套增量表示,用于在单个请求中添加、删除或更新链接以及嵌套值,但这些高级机制尚未在 ODataLib 运行时中实现。 p>

【讨论】:

  • 您是否介意引用此声明的来源:“OData V4 是专门声明性的,其设计使得针对单个资源的操作应仅影响该资源。这就是为什么我们不能在单个资源中修补相关属性的原因询问”。根据规范真的不支持它,还是你只是说这是你个人认为更好的做法?我在问,因为我不记得看到你在那里引用的关于无法更新相关实体的限制。我的理解是,如果有效负载是有效的,那么服务器应该以一种或另一种方式尊重它
  • 深度更新或嵌套补丁是 OData v4.01 中引入的一个新概念:docs.oasis-open.org/odata/odata/v4.01/…,它是定义 4.0 和 4.01 一致性级别之间的关键区别之一。最初围绕深度更新的讨论未能就如何消除修补现有相关实体的属性或是否分配新实体之间的歧义达成共识。 ASP.Net Core 实现仅最低限度地符合 v4.0 OOTB。
  • 感谢@julealgon 我已经重写了我的大部分回复,因为我更熟悉最新版本的协议规范。
  • 非常有见地的更新克里斯,非常感谢。你介意我问你怎么知道所有这些细节吗?您是 MS OData 团队还是规范团队的一员?
  • 我从一开始就是 OData github 项目的积极观察者,并从 2014 年开始在我的商业解决方案中使用 OData v4,在此之前我是 WCF 服务的忠实粉丝,但当时使用 Self而是跟踪基于实体的 API 或 RPC。 OData 是我的 github 樱桃,我一直对开发支持工具的过程和演变着迷,并且经常在 git 问题列表中回忆起来自开发和设计团队的 cmets,就好像他们是面对面的对话一样。很难找到具体的引用来引用,但它让我对产品充满信心。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
  • 2013-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多