OData V4 客户端添加子实体

OData V4 client adding child entity

我有一个父项 (Order) 和一个子项 (OrderDetail),其中 Order 已经存在于数据库中并且 OrderDetail 也存在于数据库中。
我真正想要做的就是添加另一个绑定到订单的 OrderDetail 记录。

我走了好几条路,我什至不确定哪条路是正确的。
让我们假设它们之间的导航已经正常工作。
我可以 $expand=OrderDetails 很好,我也可以 Orders(1)/OrderDetails 很好,然后从 OrderDetails 做相反的事情。

基于此Updating the Data Service,我需要做的就是调用 AddRelatedObject,然后将对象添加到 OrderDetails 集合。

// Add the new item with a link to the related Order
context.AddRelatedObject(order, "OrderDetails", newOrderDetail);
// Add the new order detail to the collection
order.Order_Details.Add(newOrderDetail);
newOrderDetail.Order = order;

看起来很简单。
然而,当我执行 context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate) 时,它会抛出一个错误。

{"error":{"code":"","message":"No HTTP resource was found that matches the request URI 'http://localhost/Test/odata/Orders(1)/OrderDetails'.","innererror":{"message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/navigation'.","type":"","stacktrace":""}}}

但是如果我导航到列出的 URL,它会显示数据。
Fiddler 时间到了。
在 Fiddler 中,我可以看到这是 POST 到 URL 而不是 GET。
它应该是 POST 但不是 URL 列出的。
POST 应该是 /odata/OrderDetails

第 2 轮

// Add the new item with a link to the related Order
context.AttachTo("OrderDetails", newOrderDetail);
// Add a link between Order and the new OrderDetail
context.AddLink(order, "OrderDetails", newOrderDetail);
// Add the new order detail to the collection
order.Order_Details.Add(newOrderDetail);
newOrderDetail.Order = order;

仍然是一个有错误的 POST,但 URL 略有不同,发布的 json 只有“/odata/OrderDetail(0)”,它现在也有“ $ref.

{"error":{"code":"","message":"No HTTP resource was found that matches the request URI 'http://localhost/Test/odata/Orders(1)/OrderDetails/$ref'.","innererror":{"message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/navigation/$ref'.","type":"","stacktrace":""}}}

通过快速网络搜索,我找到了这篇文章 Entity Relations in OData v4 Using ASP.NET Web API 2.2
这篇文章说我需要在订单控制器中添加一个 "CreateRef"。
我确实在 Orders 控制器中创建了一个 "CreateRef" 并确定它被调用但是文章假定 OrderDetail 存在于数据库中。
它没有发布 json OrderDetail 对象。

第 3 轮

// Add the new item with a link to the related Order
context.AttachTo("OrderDetails", newOrderDetail);
// Attach a link between Order and the new OrderDetail
context.AttachLink(order, "OrderDetails", newOrderDetail);
// Add the new order detail to the collection
order.Order_Details.Add(newOrderDetail);
newOrderDetail.Order = order;

嗯,这看起来好多了。
没有错误,但它没有完全工作。
它向 /odata/OrderDetails(0) 发送了一个 PUT,它确实发送了 json OrderDetail 对象,但这应该是一个 POST 而不是 PUT .

我觉得我很接近,但我似乎无法弄清楚如何让它正常工作。

有什么想法吗?

我遇到了同样的问题,今天找到了解决方案。

看看http://aspnetwebstack.codeplex.com/discussions/457028 :

There is no inbuilt convention to handle POST requests to ~/entityset(key)/navigation. You have to build one yourself. Check out this a sample code for that.

您需要先创建一个 EntitySetRoutingConvention :

public class CreateNavigationPropertyRoutingConvention : EntitySetRoutingConvention
{
    public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        if (odataPath.PathTemplate == "~/entityset/key/navigation" && controllerContext.Request.Method == HttpMethod.Post)
        {
            IEdmNavigationProperty navigationProperty = (odataPath.Segments[2] as NavigationPathSegment).NavigationProperty;
            controllerContext.RouteData.Values["key"] = (odataPath.Segments[1] as KeyValuePathSegment).Value; // set the key for model binding.
            return "PostTo" + navigationProperty.Name;
        }

        return null;
    }
}

然后,你必须在WebApiConfig.Register中注册它:

var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new CreateNavigationPropertyRoutingConvention());
server.Configuration.Routes.MapODataRoute("odata", "", GetEdmModel(), new DefaultODataPathHandler(), routingConventions);

请注意此示例适用于 oData v3,但可以轻松转换为 v4。

然后,您只需将父对象添加到上下文中,并对所有子对象使用 AddRelatedObject。 您的请求将发送到您的 ParentController:

中的此空白处
public HttpResponseMessage PostToOrders([FromODataUri] int key, Order order)
    {
        // create order.
        return Request.CreateResponse(HttpStatusCode.Created, order);
    }

经过反复试验,我发现了一些有用的东西。

// create a new order detail
OrderDetail newOrderDetail = new OrderDetail();
// set the orderID on the new order detail
newOrderDetail.OrderID = order.ID;
// add the order back as a link on the order detail
newOrderDetail.Order = order;
// add the order detail to the order detail collection on the order
order.OrderDetails.Add(newOrderDetail);
// add the order detail to the context
context.AddToOrderDetail(newOrderDetail);
// now update context for the order
context.UpdateObject(order);
// now save
context.SaveChanges();