C# 如何发布OData实体并将其同时链接到多个现有实体?

C# 如何发布OData实体并将其同时链接到多个现有实体?,c#,odata,asp.net-core-webapi,odata-v4,C#,Odata,Asp.net Core Webapi,Odata V4,如何将实体发布到OData端点,同时将其与正文中的其他现有实体关联 考虑以下类结构(示例): Invoice和Person都是我域中的实体(因此Id属性)。假设两者都暴露在各自的EntitySet中,因此: GEThttp://host/odata/People(1) 返回Id=1的Person GEThttp://host/odata/Invoices(2) ?$expand='发卡机构,收件人' 返回Id=2的Invoice,并在有效负载中展开Issuer和Recipient 现在考

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


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

Invoice
Person
都是我域中的实体(因此
Id
属性)。假设两者都暴露在各自的EntitySet中,因此:

  • GEThttp://host/odata/People(1) 

    返回
    Id=1的
    Person

  • GEThttp://host/odata/Invoices(2) ?$expand='发卡机构,收件人'

    返回
    Id=2的
    Invoice
    ,并在有效负载中展开
    Issuer
    Recipient

现在考虑以下要求:

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

如何“告诉”OData框架我希望将给定的导航属性与现有实体关联?如何通知我的控制员行动,这是我的意图

理想情况下,我希望帖子主体的外观如下:

  • POSThttp://host/odata/Invoices

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

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

  • 颁发者
    属性加载所需的“人员(1)”。如果不存在,则应返回错误的请求
  • 收件人
    属性加载所需的“人员(2)”。如果不存在,则应返回错误的请求
  • 创建一个新的
    发票
    实例,并从上面分配
    发卡机构
    收件人
    ,然后将其保存到数据库中
  • 我知道OData支持使用
    entity/relation/$ref
    语法使用特殊的PUT/POST URL配置事后关系。有了这样的语法,我可以做如下事情:

  • POSThttp://host/odata/Invoices

    {“Property1”:“someValue”,“Property2”:“100”}

  • PUThttp://host/odata/Invoices(x) /Issuer/$ref

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

  • PUThttp://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变得不灵活。

    最简单的解决方案是直接在模型中公开
    ForeignKey
    属性。即使是MS文档中使用的模型也会暴露FKs

    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更加灵活。 这也使您能够更好地控制模型的数据库实现,同时仍然不依赖于数据库引擎

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

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

    现在在
    补丁
    POST
    中,我们可以直接使用id链接
    个人
    记录

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

    小心点:
    许多模型设计人员选择不公开
    ForeignKey
    属性的原因之一是,当您尝试发送或处理ForeignKey和相关实体时,存在歧义。
    对于
    补丁
    没有混淆,不能包含相关实体,对于
    POST
    但是,如果请求中提供了相关实体以及FK,则相关实体通常被解释为创建/插入请求,FK被忽略

    这在不同的实现框架中是不同的,但最终当同时提供FK和实体时,只能使用其中一个,这通常是因为实体更详细/具体,将优先于FK使用

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

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

    ODataV4是专门声明性的,设计为针对单个资源的操作只影响该资源
    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
    }
    
    {
        "Issuer_Person_Id": 1,
        "Recipient_Person_Id": 2,
        "Property1": "someValue",
        "Property2": "100",
        ...
    }