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).在云计算的 ...
随机推荐
- BAT脚本加防火墙455端口
@echo off mode con: cols=85 lines=30 :NSFOCUSXA title WannaCry勒索病毒安全加固工具 color 0A cls echo. echo. ec ...
- C# 将某个方法去异步执行
C# 将某个方法去异步执行 Task.Run(() => { string msgerror = SendPhoneCode.NewSendByTemplate(apply.PhoneNum, ...
- Node.js模块定义总结
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统.模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这 ...
- 关于获取Windows下性能参数的总结
Windows下特定进程或者所有进程的CPU.物理内存.虚拟内存等性能参数的获取方法小结,包括如何在MFC中以及如何使用C#语言来获取参数. VC API:GlobalMemoryStatus 获取全 ...
- Grafana 短信报警
一.分析 需求 Grafana支持短信渠道报警 要求 使用开发提供的短信API接口 请求url: http://192.168.1.1:8088/alerting/sendSms?mobile=手机号 ...
- JS模块化编程(二):require.js基本用法
require.config() 接受一个配置对象 常用属性: paths: shim: 配置不兼容的模块 baseUrl: 引用模块的文件基目录
- Java编程的逻辑 (34) - 随机
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...
- Codeforces Round #380 Div.2 F - Financiers Game
F - Financiers Game 这种两人博弈一般都可以用两个dp写, 一个dp描述第一个人的最优态, 第二个dp描述第二个人的最优态,难点在于优化空间... 我感觉这个空间开得有点玄学.. d ...
- 配置Gitlab使用LDAP认证
1. 通过SSH登陆Gitlab服务器. 2. 进行以下配置文件夹. [root@c720141 ~]# cd /etc/gitlab/ 3. 打开gitlab.rb配置文件,并加入以下配置. git ...
- swagger restful api form映射实体对象和body映射实体对象配置
实体Model @ModelAttribute一个具有如下三个作用: ①绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数 ...