Asp.Net Web API 2第十八课——Working with Entity Relations in OData

前言

阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html 。 

本文的示例代码的下载地址为http://pan.baidu.com/s/1o6lqXN8

大多数的数据集定义实体间的关系:客户有订单、书籍有作者、产品有供应商。客户端可以使用OData操作实体间的关系。给定一个产品,你可以找到该产品的供应商。您也可以创建或者删除关系。例如,您也可以为一个产品设置一个供应商。

本教程将会展示在Asp.Net Web API中支持这些操作。本文的教程是建立在上一节的教程之上http://www.cnblogs.com/aehyok/p/3545824.html。

Add a Supplier Entity添加一个供应商实体类

首先我们需要来添加一个Supplier的实体类

1namespace OData.Models 2{ 3 public class Supplier 4 { 5 [Key] 6 public string Key { get; set; } 7 public string Name { get; set; } 8 } 9} 10

这个类使用了一个字符串类型的实体键。在实践中,这可能比使用整形键不太常见的。 但它是值得的看到OData如何处理除了整数以外的其他键类型。

接下来,我们将通过在Product类上添加一个Supplier的属性来建立一个关系。

1 public class Product 2 { 3 public int ID { get; set; } 4 public string Name { get; set; } 5 public decimal Price { get; set; } 6 public string Category { get; set; } 7 // New code 8 [ForeignKey("Supplier")] 9 public string SupplierId { get; set; } 10 public virtual Supplier Supplier { get; set; } 11 } 12

添加一个新的DbSet 到ProductServiceContext 类,从而使实体框架将包括Supplier 在数据库表中。

1 public class ProductServiceContext : DbContext 2 { 3 public ProductServiceContext() : base("name=ProductServiceContext") 4 { 5 } 6 7 public DbSet<Product> Products { get; set; } 8 9 ///New Code 10 public DbSet<Supplier> Suppliers { get; set; } 11 12 } 13

在WebApiConfig.cs,添加一个“Suppliers”实体的EDM模型:

1 ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); 2 builder.EntitySet<Product>("Products"); 3 // New code: 4 builder.EntitySet<Supplier>("Suppliers"); 5

Navigation Properties导航属性

 为了得到一个产品的供应商,客户端发送了一个Get请求:

1GET /Products(1)/Supplier 2

 在Product类型上有一个Supplier的导航属性。在这个实例中,Supplier是一个单一的项。但是一个导航属性也能返回一个集合(一对多或者多对多的 关系)。

为了支持这个请求,在ProductsController上添加如下方法:

1 // GET /Products(1)/Supplier 2 public Supplier GetSupplier([FromODataUri] int key) 3 { 4 Product product = db.Products.FirstOrDefault(p => p.ID == key); 5 if (product == null) 6 { 7 throw new HttpResponseException(HttpStatusCode.NotFound); 8 } 9 return product.Supplier; 10 } 11

key这个参数就是这个Product的键。这个方法返回关联的实体——在这个实例中,就是一个 Supplier对象。方法的名称和参数的名称都是非常重要的。总之,如果导航属性被命名为一个“X”,你需要添加一个被命名为“GetX”的方法。这个方法必须采用一个命名为“key”的参数,用来匹配父类数据类型的key。

它也是很重要的在键参数上拥有【FromOdataUri】的属性。当它从请求的URL中解析键时,这个属性将会告诉Web API去使用Odata语法规则。

Creating and Deleting Links

OData支持创建和删除两个实体之间的关系。在OData术语中,这个关系就是一个“link”。每个link有一个携带entity/$links/entity的Url。例如,由产品到供应商的链接看起来像这样:

1/Products(1)/$links/Supplier 2

为了创建一个新的链接,这个客户端发送了一个post请求到这个链接URI。请求的消息体就是目标实体的URI。例如,假设有一个供应商的键为“CTSO”。为了创建一个链接由“ Product(1)”到” Supplier('CTSO')“,客户端发送一个请求如下:

1POST http://localhost/odata/Products(1)/$links/Supplier 2Content-Type: application/json 3Content-Length: 50 4{"url":"http://localhost/odata/Suppliers('CTSO')"} 5 6

对于删除一个链接,客户端发送了一个DELETE 请求到链接URI。

Creating Links

为启用一个客户端去创建产品-供应商的链接,需要在ProductsController类中添加如下的代码:

1[AcceptVerbs("POST", "PUT")] 2public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link) 3{ 4 if (!ModelState.IsValid) 5 { 6 return BadRequest(ModelState); 7 } 8 9 Product product = await db.Products.FindAsync(key); 10 if (product == null) 11 { 12 return NotFound(); 13 } 14 15 switch (navigationProperty) 16 { 17 case "Supplier": 18 string supplierKey = GetKeyFromLinkUri<string>(link); 19 Supplier supplier = await db.Suppliers.FindAsync(supplierKey); 20 if (supplier == null) 21 { 22 return NotFound(); 23 } 24 product.Supplier = supplier; 25 await db.SaveChangesAsync(); 26 return StatusCode(HttpStatusCode.NoContent); 27 28 default: 29 return NotFound(); 30 } 31} 32

这个方法有三个参数:

第一个key:就是引导到父类实体的键

第二个navigationProperty: 导航属性的名称。例如,最合适的导航属性Supplier。

第三个link:被链接实体的OData的URI。这个值是从消息体中获得。例如,这个链接URI可能是” http://localhost/odata/Suppliers('CTSO')“,也就是供应商中有ID="CTSO"。

这个方法用这个链接去查找Supplier。如果匹配的供应商被发现,这个方法将会设置Product实体类的Supplier的属性,并且保存结果到数据库。

其中最难的部分是解析链接URI。从根本上来说,你需要模拟发送一个get请求到那个URI。接下来的辅助方法将会展示如何处理它。这个方法调用Web API路由过程,返回一个OData实体,展现被转换的OData路径。对于一个链接URI,这个片段数中应该有一个实体键。

1// Helper method to extract the key from an OData link URI. 2private TKey GetKeyFromLinkUri<TKey>(Uri link) 3{ 4 TKey key = default(TKey); 5 6 // Get the route that was used for this request. 7 IHttpRoute route = Request.GetRouteData().Route; 8 9 // Create an equivalent self-hosted route. 10 IHttpRoute newRoute = new HttpRoute(route.RouteTemplate, 11 new HttpRouteValueDictionary(route.Defaults), 12 new HttpRouteValueDictionary(route.Constraints), 13 new HttpRouteValueDictionary(route.DataTokens), route.Handler); 14 15 // Create a fake GET request for the link URI. 16 var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link); 17 18 // Send this request through the routing process. 19 var routeData = newRoute.GetRouteData( 20 Request.GetConfiguration().VirtualPathRoot, tmpRequest); 21 22 // If the GET request matches the route, use the path segments to find the key. 23 if (routeData != null) 24 { 25 ODataPath path = tmpRequest.GetODataPath(); 26 var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault(); 27 if (segment != null) 28 { 29 // Convert the segment into the key type. 30 key = (TKey)ODataUriUtils.ConvertFromUriLiteral( 31 segment.Value, ODataVersion.V3); 32 } 33 } 34 return key; 35} 36

Deleting Links

对于删除一个链接,在ProductsController类中添加如下代码:

1public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty) 2{ 3 Product product = await db.Products.FindAsync(key); 4 if (product == null) 5 { 6 return NotFound(); 7 } 8 9 switch (navigationProperty) 10 { 11 case "Supplier": 12 product.Supplier = null; 13 await db.SaveChangesAsync(); 14 return StatusCode(HttpStatusCode.NoContent); 15 16 default: 17 return NotFound(); 18 19 } 20} 21

在这个例子中,这个导航属性是一个简单的Supplier实体。如果导航属性是一个集合,对于删除一个链接的URI必须在被关联的实体中有一个键。例如:

1DELETE /odata/Customers(1)/$links/Orders(1) 2

这里展示的则是1对多的关系中,删除其中的一个的例子。

这个请求就是从客户1中移除订单为1的。这个DeleteLink方法将会有如下签名:

1void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty); 2

简单测试结果

 1、http://localhost:3629/Odata/Products(1)/Supplier

2、

 

 将ID=2的Supplier修改为WING

请求Header

1POST http://localhost/odata/Products(2)/$links/Supplier 2Content-Type: application/json 3Content-Length: 50 4

请求Body

1{"url":"http://localhost/odata/Suppliers('WING')"} 2

现在再次查看http://localhost/Odata/Products

3、DELETE  http://localhost/odata/Products(2)/$links/Supplier那么这样就可以将上面的SupplierId=WING修改为null

然后再次执行http://localhost/Odata/Products查看

* *

总结

本文所参考链接为http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/working-with-entity-relations

本文示例代码下载地址为http://pan.baidu.com/s/1o6lqXN8

代码交流 2021