Search code examples
c#odata

OData client unable to send null values for Key properties


I have built an OData WebAPI Service exposing a couple of entities (using OData 4, System.Web.Odata v5.1.9).

The service accepts bost POST (for creating) and PATCH (for updating). Internally, both POST and PATCH are forwarded to an Oracle database, and both methods are handled through a single stored procedure which takes the Id as an input parameter. If the Id is null, a new record is created, otherwise the record with that Id is updated.

Recently one of our consumers complained that they can't properly insert new data. The problem is that they use the OData client to generate their model using the metadata. As the Id is not nullable a 0 is always sent to my service, prompting the database routine to do an update of record with Id 0 (which doesn't exist, raising an exception).

Now, there's a couple options I see to circumvent this:

  • in my controller check if Id is default(int) and if so pass NULL to the SP
  • stop using the OData client and have them construct their messages manually
  • separate the single stored procedure into two procedures (insert and update)
  • change the Id property to a string

My Entity class looks like this:

public class ContactDTO
    {
        [Key]
        public int Id { get; set; }

        [ForeignKey("Person")]
        public int? PersonId { get; set; }

        // snip some other properties
    }

Because the Id property is defined as [Key] it is shown in the metadata as Required:

<EntityType Name="ContactDTO">
    <Key>
        <PropertyRef Name="Id"/>
    </Key>
    <Property Name="Id" Type="Edm.Int32" Nullable="false"/>
    <Property Name="PersonId" Type="Edm.Int32"/>
</EntityType>

I have a feeling that I'm missing something here and that maybe I should just leave it as is, but if not, what are the steps I can take to make sure they can call my service properly


Solution

    1. Define the Id property as nullable and remove the KeyAttribute on it:

      public class ContactDTO
      {
          public int? Id { get; set; }    
      
          [ForeignKey("Person")]
          public int? PersonId { get; set; }
      } 
      
    2. Configure the Id property as a key and as optional:

      ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
      
      modelBuilder.EntityType<ContactDTO>().HasKey(_ => _.Id);
      
      modelBuilder.EntityType<ContactDTO>().Property(_ => _.Id).IsOptional();
      

    It produces the following metadata:

    <EntityType Name="ContactDTO">
        <Key>
            <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" />
        <Property Name="PersonId" Type="Edm.Int32" />
    </EntityType>