问题描述
如何将实体发布到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
}
Invoice
和Person
都是我的域中的实体(因此Id
属性)。假设两者都在各自的实体集中公开,因此:
GET http://host/odata/People(1)
返回
Person
Id = 1
GET http://host/odata/Invoices(2)?$expand='Issuer, Recipient'
返回
Invoice
,其中Id = 2
和Issuer
和Recipient
都在有效负载中展开
现在考虑以下要求:
如何"告诉"OData框架我希望将给定的导航属性与现有实体相关联?如何通知我的控制器操作这是您的意图?我要在系统中创建新发票,该发票将与现有的开票人和收件人相关联
理想情况下,我希望帖子正文如下所示:
POST http://host/odata/Invoices
{ "Issuer":"/odata/People(1)", "Recipient":"/odata/People(2)", "Property1":"omeValue", "Property2":"100", ..。 )
一旦服务器收到此负载,它应该:
- 加载
Issuer
属性所需的"People(1)"Person
。如果不存在,则应返回错误请求。 - 加载
Recipient
属性所需的"People(2)"Person
。如果不存在,则应返回错误请求。 - 新建
Invoice
实例,并从上面分配Issuer
和Recipient
,然后将其保存到数据库。
entity/relation/$ref
语法通过特殊的PUT/POST URL配置事后关系。使用这样的语法,我可以执行如下操作:
POST http://host/odata/Invoices
{ "Property1": "someValue", "Property2": "100" }
PUT http://host/odata/Invoices(x)/Issuer/$ref
{"@odata.id":"http://host/odata/People(1)"}
PUT http://host/odata/Invoices(x)/Recipient/$ref
/li>{"@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缺乏灵活性。
推荐答案
最简单的解决方案是直接公开模型中的ForeignKey
属性。即使在MS文档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僵化,而是使您的API更灵活。 这还使您可以更好地控制模型的数据库实现,同时仍不依赖于数据库引擎。
在启用延迟加载的环境中,如果您需要检查相关实体是否存在而无需将其加载到内存中,则包括FK具有一些额外的性能优势。
注意:通过将FK包含在模型中,
$ref
语法和批处理仍然可以使用,但我们现在可以访问更实用的FK ID值,这些值可以在服务器端代码中轻松验证,就像更容易发送值一样。
现在,在PATCH
或POST
中,我们可以直接使用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
如果更新既指定了对单值导航属性的绑定,又指定了根据同一导航属性绑定到主体实体的键属性的Dependent属性,则将忽略Dependent属性,并根据绑定中指定的值更新关系。对于
POST
但是,如果请求和FK中都提供了相关实体,则假定相关实体是深度插入,忽略FK。11.4.2.2 Create Related Entities when Creating an Entity
每个包含的相关实体都遵循创建实体的规则进行处理,就像它是根据原始目标URL发布的一样,该原始目标URL使用指向此相关实体的导航路径扩展。启用FKS后,我的建议是在客户端采取措施,以确保您不会尝试将请求中的FK和相关实体发回API。
我同意您建议的POST中的@odata.id是一个合乎逻辑的结论,但是它引发了其他潜在的实现问题,这就是协议提供针对表示ForeignKey引用的$ref
端点的直接CRUD操作的概念的原因。
PATCH
相关属性的原因,就像这个引用问题一样,有太多潜在的实现变体和对其工作方式的解释,它们保持规范的简洁性和约束性。
在起草规范之前,有关各方基本上无法就协议细节和如何处理深度更新的指导意见达成共识。ASP.NET FX和Core实现(截至本帖)仅为OData 4.0 Minimal Conformance LevelOOTB。要提高符合性级别,您需要做很多工作。
批处理是执行可能会影响单个事务查询中的多个资源的操作的首选机制,但与仅公开FKS!
相比,它是更复杂的解决方案虽然我们可以使用复杂的语法和批处理以其他方式实现这一点很好,但在模型中公开FK ID并使其可供客户端访问(而不仅仅是在服务器逻辑中)还有许多其他实际好处,在正确的方案中,这些可以真正带来很大的好处:
优化了网格中的数据检索
如果多个行具有指向另一个表中相同记录的链接,则只需从公用表下载一次链接值。对于某些类型的数据,从公用表下载所有可能的查找值会更有效,然后在表示层中,您可以根据ID连接结果。在某些用例中,这些查找值可能只需要在整个会话中下载一次。组合框关系分配
有时间和地点,但是通过在您的模型中包含FK,可以非常简单地绑定到表示层中的ComboBox
或DropDownList
实现来更改或分配相关实体,实现几乎与网格表示相同,绑定控件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运行时中实现。
这篇关于如何同时发布OData实体并将其链接到多个已存在的实体?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!