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);
}
}
ASP.NET Web API基于OData的增删改查,以及处理实体间关系的更多相关文章
- [转]ASP.NET Web API基于OData的增删改查,以及处理实体间关系
本文转自:http://www.cnblogs.com/darrenji/p/4926334.html 本篇体验实现ASP.NET Web API基于OData的增删改查,以及处理实体间的关系. 首先 ...
- 【转载】ASP.NET MVC Web API 学习笔记---联系人增删改查
本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查.目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的.下面我们通过创建一个简单的Web API来管理联系 ...
- ASP.NET MVC Web API 学习笔记---联系人增删改查
本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查. 目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的. 下面我们通过创建一个简单的Web API来管理 ...
- [转]ASP.NET web API 2 OData enhancements
本文转自:https://www.pluralsight.com/blog/tutorials/asp-net-web-api-2-odata-enhancements Along with the ...
- 在ASP.NET MVC4中实现同页面增删改查,无弹出框02,增删改查界面设计
在上一篇"在ASP.NET MVC4中实现同页面增删改查,无弹出框01,Repository的搭建"中,已经搭建好了Repository层,本篇就剩下增删改查的界面了......今 ...
- Android 系统API实现数据库的增删改查和SQLite3工具的使用
在<Android SQL语句实现数据库的增删改查>中介绍了使用sql语句来实现数据库的增删改查操作,本文介绍Android 系统API实现数据库的增删改查和SQLite3工具的使用. 系 ...
- Mybatis_3.基于注解的增删改查
1.实体类User.java public class User { private int id; private String name; private int age; //getter.se ...
- Java API实现Hadoop文件系统增删改查
Java API实现Hadoop文件系统增删改查 Hadoop文件系统可以通过shell命令hadoop fs -xx进行操作,同时也提供了Java编程接口 maven配置 <project x ...
- [转]ASP.NET Web API对OData的支持
http://www.cnblogs.com/shanyou/archive/2013/06/11/3131583.html 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的 ...
随机推荐
- 【API】注册表编程基础-RegCreateKeyEx、RegSetValueEx
1.环境: 操作系统:Windows 10 x64 编译器:VS2015 2.关键函数 LONG WINAPI RegCreateKeyEx( _In_ HKEY hKey, _In_ LPCTSTR ...
- android 通用 Intent
通用 Intent 本文内容显示详细信息 闹钟 日历 相机 联系人/人员应用 电子邮件 文件存储 本地操作 地图 音乐或视频 新笔记 电话 搜索 设置 发送短信 网络浏览器 使用 Android 调试 ...
- String,StringBuffer和StringBuilder的区别
(1)String类的API概述是这样的:String类代表字符串,Java程序中的所有字符串字面值都作为此类的实例体现.字符串是常量,它们的值在创建之后不能更改.可见,String是对象且为不可变对 ...
- (三)发布Dubbo服务
我们现在来学习下发布Dubbo服务,主要参考dubbo开发包里的demo源码:由浅入深的讲解下这个小demo: github地址:https://github.com/apache/incubator ...
- android短信验证
短信验证demo http://download.csdn.net/detail/crazy1235/8315279#comment 使用MOB平台开发,用法详见: http://blog.csdn. ...
- 整理OpenResty+Mysql+Tomcat+JFinal+Cannal+HUI
阿里云运维主机 118.190.89.22 26611 1.CentOS6.9下安装OpenResty 2.CentOS6.9下安装MariaDB10.2.11 3.使用Intellij IDEA把J ...
- *CI框架装载器Loader.php源码分析
http://www.bitscn.com/pdb/php/201411/404680.html 顾名思义,装载器就是加载元素的,使用CI时,经常加载的有: $this->load->li ...
- C++ 编程错误记录
C3646 未知重写说明符 两个头文件相互引用造成的问题
- vmware工具克隆linux系统步骤及配置
我们在学习的时候使用vmware创建自己的虚拟机,但是我们有时学习环境需要多台计算机进行操作演示,如果安装创建虚拟机.再在虚拟机上安装操作系统.这样很花费我们的时间,而且还步能保证服务的一直性,这就用 ...
- 027 Spark的优化总结
1.四个部分