Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error: Cannot apply PATCH to navigation property. So what do I do?

I have a Web API 2.2 OData 4 service the following model using entity framework:

public class Company
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<CompanyType> CompanyTypes { get; set; }
}

public class CompanyType
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Company> Companies { get; set; }
}

And a bit of fluent mapping:

modelBuilder.Entity<Company>().HasMany(x => x.CompanyTypes).WithMany(x => x.Companies).Map(x =>
{
  x.MapLeftKey("CompanyId");
  x.MapRightKey("CompanyTypeId");
  x.ToTable("CompaniesCompanyTypes");
});

On my CompanysController I have a patch method and was hoping to be able to send the following request and successfuly update the company name and create a couple of company types records for that company:

PATCH http://localhost:50113/MessagingService/odata//CompanyDTOs(1)/ HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-16
Host: localhost:50113
Content-Length: 174

{"Name":"Cheesy Peas Ltd","CompanyTypes":[{"Id":1,"Name":"Parent"},{"Id":2,"Name":"Subsidiary"}]}

But I receive a:

{
  "error":{
    "code":"","message":"The request is invalid.","innererror":{
      "message":"delta : Cannot apply PATCH to navigation property 'CompanyTypes' on entity type 'Core.Models.Company'.\r\n","type":"","stacktrace":""
    }
  }
}

I assume that I'm asking too much of my PATCH request, but does anyone have a suggestion for how I should achieve this? The simpler the better - I'm not keen on adding complexity to my client application if I can avoid it and would prefer to use PATCH rather that PUT.

Thanks very much. If you're interested in this question and need more details, please ask.

EDIT

Specifically, what I'm after is to be able to insert a record in a many-to-many table when updating a record in a part of that relationship. Eg. If a Student has many Classes and a Class has many Students then when I update Student A I might also want to add a reference to Class One, delete a reference to Class Two and leave a reference to Class Three intact.

I've done some reading of the OData documentation here:

http://docs.oasis-open.org/odata/odata-atom-format/v4.0/cs02/odata-atom-format-v4.0-cs02.html#_Toc372792732

But it's a bit of a mystery to me. I'm sure there must be a way of doing this using things like "@odata.bind" etc.

Any opinions out there?

like image 912
user2363071 Avatar asked Sep 11 '14 15:09

user2363071


1 Answers

Based on this workitem, you need to create a collection of primitive types instead of list complex type.

Entity

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<ProductType> ProductTypes { get; set; }
}
public class ProductDto : Product
{
    public ICollection<int> ProductTypeIds { get; set; }
    public ICollection<string> ProductTypeNames { get; set; }
}
public class ProductType
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Action

// PATCH odata/Products(5)
[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<ProductDto> patch)
{
    object productTypeIds;
    patch.TryGetPropertyValue("ProductTypeIds", out productTypeIds);
    object productTypeNames;
    patch.TryGetPropertyValue("ProductTypeNames", out productTypeNames);

    // TODO: Implement update to database.

    return Updated(new ProductDto()); // for demo purpose
}

Config

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntitySet<ProductDto>("ProductDtos");
builder.EntitySet<ProductType>("ProductType");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());

Fiddler

PATCH http://localhost:59829/odata/Products(1) HTTP/1.1

User-Agent: Fiddler
Host: localhost:59829
Content-Length: 83
Content-Type: application/json

{"Id":1, "Name":"A", "ProductTypeIds":[1,2], "ProductTypeNames":["AA", "BB"] }

Result

Result

Hope that helps.

like image 159
Yuliam Chandra Avatar answered Oct 18 '22 19:10

Yuliam Chandra