本篇体验实现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);
}
}

ASP.NET Web API基于OData的增删改查,以及处理实体间关系的更多相关文章

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

    本文转自:http://www.cnblogs.com/darrenji/p/4926334.html 本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先 ...

  2. 【转载】ASP.NET MVC Web API 学习笔记---联系人增删改查

    本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查.目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的.下面我们通过创建一个简单的Web API来管理联系 ...

  3. ASP.NET MVC Web API 学习笔记---联系人增删改查

    本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查. 目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的. 下面我们通过创建一个简单的Web API来管理 ...

  4. [转]ASP.NET web API 2 OData enhancements

    本文转自:https://www.pluralsight.com/blog/tutorials/asp-net-web-api-2-odata-enhancements Along with the ...

  5. 在ASP.NET MVC4中实现同页面增删改查,无弹出框02,增删改查界面设计

    在上一篇"在ASP.NET MVC4中实现同页面增删改查,无弹出框01,Repository的搭建"中,已经搭建好了Repository层,本篇就剩下增删改查的界面了......今 ...

  6. Android 系统API实现数据库的增删改查和SQLite3工具的使用

    在<Android SQL语句实现数据库的增删改查>中介绍了使用sql语句来实现数据库的增删改查操作,本文介绍Android 系统API实现数据库的增删改查和SQLite3工具的使用. 系 ...

  7. Mybatis_3.基于注解的增删改查

    1.实体类User.java public class User { private int id; private String name; private int age; //getter.se ...

  8. Java API实现Hadoop文件系统增删改查

    Java API实现Hadoop文件系统增删改查 Hadoop文件系统可以通过shell命令hadoop fs -xx进行操作,同时也提供了Java编程接口 maven配置 <project x ...

  9. [转]ASP.NET Web API对OData的支持

    http://www.cnblogs.com/shanyou/archive/2013/06/11/3131583.html 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的 ...

随机推荐

  1. 【C++】cmdline——轻量级的C++命令行解析库

    1.说明 cmdline是一个轻量级的c++命令行参数解析工具,全部源码只有一个cmdline.h头文件. 2.代码 20171210_命令行进行解析.cpp // 20171210_命令行进行解析. ...

  2. Replication容量和错误日志

    gtid排错 set sql_log_bin=off;  #人为关闭二进制日志

  3. 005_系统运维之SLA与SLO的关系

    服务水平协议(简称:SLA,全称:service level agreement)是在一定开销下为保障服务的性能和可靠性,服务提供商与用户间定义的一种双方认可的协定.通常这个开销是驱动提供服务质量的主 ...

  4. SNMP中MIB2所有主要节点

    系统组:system组包含以下对象集(.1.3.6.1.2.1.1): 对象名:sysDescr(1) OID:system.1 对象类型:DisplayString[255] 访问模式:只读 描述: ...

  5. 2018-2019-2 网络对抗技术 20165301 Exp4 恶意代码分析

    2018-2019-2 网络对抗技术 20165301 Exp4 恶意代码分析 实验内容 系统运行监控 使用如计划任务,每隔一分钟记录自己的电脑有哪些程序在联网,连接的外部IP是哪里.运行一段时间并分 ...

  6. Swagger+IdentityServer4测试授权验证

    1.Bearer授权操作,添加如下代码 services.AddSwaggerGen(options => { options.AddSecurityDefinition("Beare ...

  7. video.js视频播放器

    免费视频播放器videojs中文教程 Video.js是一款web视频播放器,支持html5和flash两种播放方式.更多关于video.js的介绍,可以访问官方网站介绍,我之前也写过一篇关于vide ...

  8. 【Java】 二叉树的遍历(递归与循环+层序遍历)

    在[Java] 大话数据结构(9) 树(二叉树.线索二叉树)一文中,已经实现了采用递归方法的前.中.后序遍历,本文补充了采用循环的实现方法.以及层序遍历并进行了一个总结. 递归实现 /* * 前序遍历 ...

  9. q.size()

    在队列中,q.empty()比q.size()要慢,如果可能就用q.size(); 比如spfa的 while(q.size()>0) better than while(!q.empty())

  10. java 反射获取类的静态属性值

    public class AppTest { private NodeClass nodeClass; public static String hehe = "hehe"; pu ...