你的位置:首页 > ASP.net教程

[ASP.net教程]ASP.NET Web API基于OData的增删改查,以及处理实体间关系


 

本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系。

 

首先是比较典型的一对多关系,Supplier和Product。

 

public class Product{  public int Id { get; set; }  public string Name { get; set; }  public decimal Price { get; set; }  public string Category { get; set; }  [ForeignKey("Supplier")]  public int? SupplierId { get; set; }  public virtual Supplier Supplier { get; set; }}public class Supplier{  public int Id { get; set; }  public string Name { get; set; }  public ICollection<Product> Products { get; set; }}  

 


Product有一个针对Supplier的外键SupplierId,可以为null。


Entity Framework的配置部分略去。

 

在WebApiConfig中有关OData的部分配置如下:

 

  public static class WebApiConfig  {    public static void Register(HttpConfiguration config)    {      // Web API 配置和服务      // Web API 路由      config.MapHttpAttributeRoutes();      config.Routes.MapHttpRoute(        name: "DefaultApi",        routeTemplate: "api/{controller}/{id}",        defaults: new { id = RouteParameter.Optional }      );      //有关OData      //使用ODataConventionModelBuilder创建EDM使用了一些惯例      //如果要对创建EDM有更多的控制,使用ODataModelBuilder      ODataModelBuilder builder = new ODataConventionModelBuilder();      builder.EntitySet<Product>("Products");//创建EntityDataModel(EDM)      builder.EntitySet<Supplier>("Suppliers");      config.MapODataServiceRoute(        routeName: "ODataRoute",        routePrefix: "odata",         model:builder.GetEdmModel());    }  }

 

有关ProductsController

 

 

public class ProductsController : ODataController{  ProductsContext db = new ProductsContext();    private bool ProductExists(int key)  {    return db.Products.Any(p => p.Id == key);  }  protected override void Dispose(bool disposing)  {    db.Dispose();    base.Dispose(disposing);  }  ...}

 

 

和OData相关的,都要继承ODataController这个基类。

 

● 获取所有

 

 

[EnableQuery]public IQueryable<Product> Get(){  return db.Products;}

 

当为某个action配置上[EnableQuery]特性后,就支持OData查询了。

 

● 根据Product的主键查询

 

[EnableQuery]public SingleResult<Product> Get([FromODataUri] int key){  IQueryable<Product> query = db.Products.Where(p => p.Id == key);  return SingleResult.Create(query);}

 

→[FromODataUri] int key中的key值可以从如下uri中获取:

 

GET http://localhost:63372/odata/Prodducts(11)

 

以上的11将赋值给key。

 

→ SingleResult可以接受0个或1个Entity。

 

● 根据Product的主键获取其导航属性Supplier

 

//GET /Products(1)/Supplier//相当于获取Poduct的导航属性Supplier//GetSupplier中的Supplier是导航属性的名称,GetSupplier和key的写法都符合惯例//[EnableQuery(AllowedQueryOptions =System.Web.OData.Query.AllowedQueryOptions.All)][EnableQuery]public SingleResult<Supplier> GetSupplier([FromODataUri] int key){  var result = db.Products.Where(p => p.Id == key).Select(m => m.Supplier);  return SingleResult.Create(result);}

 

以上,GetSupplier的语法符合惯例,Supplier和Product的导航属性名称保持一致。

 

● 添加Product

 

public async Task<IHttpActionResult> Post(Product product){  if(!ModelState.IsValid)  {    return BadRequest(ModelState);  }  db.Products.Add(product);  await db.SaveChangesAsync();  return Created(product);}

 

以上,首先是验证,然后是添加,最后把新添加的Product放在Create方法中返回给前端。


● Product的部分更新

 

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product){  if(!ModelState.IsValid)  {    return BadRequest(ModelState);  }  var entity = await db.Products.FindAsync(key);  if (entity == null)  {    return NotFound();  }  product.Patch(entity);  try  {    await db.SaveChangesAsync();  }  catch (DbUpdateConcurrencyException)  {    if(!ProductExists(key))    {      return NotFound();    }    else    {      throw;    }  }  return Updated(entity);}

 

以上,Delta<Product>这个泛型类可以追踪Product的变化,最后使用其实例方法Patch把变化告知实体entity, Patch成功就把Product放在Updated方法中返回给前端。

 

● 更新Product

 

public async Task<IHttpActionResult> Put([FromODataUri] int key, Product product){  if(!ModelState.IsValid)  {    return BadRequest(ModelState);  }  if(key != product.Id)  {    return BadRequest();  }  db.Entry(product).State = System.Data.Entity.EntityState.Modified;  try  {    await db.SaveChangesAsync();  }  catch (DbUpdateConcurrencyException)  {    if (!ProductExists(key))    {      return NotFound();    }    else    {      throw;    }  }  return Updated(product);}

 

这里,首先判断实体的ModelState,然后判断从前端传来的Product主键key是否和前端传来的Product的主键相等,在处理Entity Framwork单元提交变化的时候catch一个DbUpdateConcurrencyException异常,防止在更新的时候该Product刚好被删除掉。最终,也把Product放在Updated方法返回给前端。

 

● 删除Product

 

public async Task<IHttpActionResult> Delete([FromODataUri] int key){  var product = await db.Products.FindAsync(key);  if(product==null)  {    return NotFound();  }  db.Products.Remove(product);  await db.SaveChangesAsync();  return StatusCode(HttpStatusCode.NoContent);}

 

● 创建Product与Supplier的实体关系

 

/// <summary>/// 创建Product与Supplier的关系/// 如果为Product.Supplier创建关系,使用PUT请求/// 如果为Supplier.Products创建关系,使用POST请求/// </summary>/// <param name="key">Product的主键</param>/// <param name="navigationProperty">Product的导航属性</param>/// <param name="link"></param>/// <returns></returns>[AcceptVerbs("POST", "PUT")]public async Task<IHttpActionResult> CreateRef([FromODataUri] int key, string navigationProperty, [FromBody] Uri link){  //现保证Product是存在的  var product = db.Products.SingleOrDefault(p => p.Id == key);  if (product == null)    return NotFound();  switch(navigationProperty)  {    case "Supplier":      //获取Supplier的主键      var supplierId = Helpers.GetKeyFromUri<int>(Request, link);      var supplier = db.Suppliers.SingleOrDefault(s => s.Id == supplierId);      if (supplier == null)        return NotFound();      product.Supplier = supplier;      break;    default:      return StatusCode(HttpStatusCode.NotImplemented);  }  await db.SaveChangesAsync();  return StatusCode(HttpStatusCode.NoContent);}

 

以上,如果创建Product的Supplier关系,就使用PUT请求,如果创建Supplier的Products关系,就使用POST请求。

 

前端发出PUT请求,uri为:http://localhost:54714/odata/Products(1)/Supplier/$ref

 

意思是说需要为编号为1的Product创建一个Supplier。

 

需要创建的Supplier来自哪里呢?需要从前端的body中传递过来,格式如下:

 

{"@odata.id":"http://localhost:54714/odata/Suppliers(2)"}

 

在CreateRef方法中,形参key用来接收这里的Product主键1, 形参navigationProperty用来接收Supplier,形参link用来接收来自body的有关一个具体Supplier的完整uri,即http://localhost:54714/odata/Suppliers(2)。

 

$ref放在Products(1)/Supplier/之后,表示现在处理的是编号为1的Product和某个Supplier之间的关系。

 

Helpers.GetKeyFromUri<int>方法用来取出http://localhost:54714/odata/Suppliers(2)中某个Supplier的主键2。


Helpers.GetKeyFromUri<T>方法如下:

 

//把uri split成segment,找到key的键值,并转换成合适的类型public static class Helpers{  public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)  {    if (uri == null)    {      throw new ArgumentNullException("uri");    }    var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);    string serviceRoot = urlHelper.CreateODataLink(      request.ODataProperties().RouteName,      request.ODataProperties().PathHandler, new List<ODataPathSegment>());    var odataPath = request.ODataProperties().PathHandler.Parse(      request.ODataProperties().Model,      serviceRoot, uri.LocalPath);    var keySegment = odataPath.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();    if (keySegment == null)    {      throw new InvalidOperationException("The link does not contain a key.");    }    var value = ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, Microsoft.OData.Core.ODataVersion.V4);    return (TKey)value;  }}

 

● 删除Product与Supplier的实体关系

 

/// <summary>/// 删除Product与Supplier的关系/// </summary>/// <param name="key">Product主键</param>/// <param name="navigationProperty">Product的导航属性</param>/// <param name="link">Suppliers(1)的所在地址</param>/// <returns></returns>[HttpDelete]public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, string navigationProperty, [FromBody] Uri link){  var product = db.Products.SingleOrDefault(p => p.Id == key);  if (product == null)    return NotFound();  switch(navigationProperty)  {    case "Supplier":      product.Supplier = null;      break;    default:      return StatusCode(HttpStatusCode.NotImplemented);  }  await db.SaveChangesAsync();  return StatusCode(HttpStatusCode.NoContent);}

 

前端发出DELETE请求:http://localhost:54714/odata/Products(1)/Supplier/$ref

 

DeleteRef方法中,形参key用来接收Product的主键1,形参navigationProperty用来接收Supplier。

 

SuppliersController,与Product类似

 

public class SuppliersController : ODataController{  ProductsContext db = new ProductsContext();  [EnableQuery]  public IQueryable<Product> GetProducts([FromODataUri] int key)  {    return db.Suppliers.Where(m => m.Id.Equals(key)).SelectMany(m => m.Products);  }  [EnableQuery]  public IQueryable<Supplier> Get()  {    return db.Suppliers;  }  [EnableQuery]  public SingleResult<Supplier> Get([FromODataUri] int key)  {    IQueryable<Supplier> result = db.Suppliers.Where(s => s.Id == key);    return SingleResult.Create(result);  }  /// <summary>  /// 删除某个Supplier与某个Product之间的关系  /// DELETE http://host/Suppliers(1)/Products/$ref?$id=http://host/Products(1)  /// </summary>  /// <param name="key">Supplier的主键</param>  /// <param name="relatedKey">Product的主键字符串</param>  /// <param name="navigationProperty">Supplier的导航属性</param>  /// <returns></returns>  [HttpDelete]  public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, [FromODataUri] string relatedKey, string navigationProperty)  {    var supplier = db.Suppliers.SingleOrDefault(p => p.Id == key);    if (supplier == null)      return NotFound();    switch(navigationProperty)    {      case "Products":        var productId = Convert.ToInt32(relatedKey);        var product = db.Products.SingleOrDefault(p => p.Id == productId);        if (product == null)          return NotFound();        product.Supplier = null;        break;      default:        return StatusCode(HttpStatusCode.NotImplemented);    }    await db.SaveChangesAsync();    return StatusCode(HttpStatusCode.NoContent);  }  protected override void Dispose(bool disposing)  {    db.Dispose();    base.Dispose(disposing);  }}