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);
    }
}
时间: 2024-10-05 06:44:03

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

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

本章节简单介绍一下使用ASP.NET MVC Web API 做增删改查.目前很多Http服务还是通过REST或者类似RESP的模型来进行数据操作的.下面我们通过创建一个简单的Web API来管理联系人 说明:为了方便数据不使用真正的数据库,而是通过内存数据模拟 1.       Web API中包含的方法 Action HTTP method Relative URI GetAllContact GET /api/contact GetContact GET /api/contact /id

[转]ASP.NET web API 2 OData enhancements

本文转自:https://www.pluralsight.com/blog/tutorials/asp-net-web-api-2-odata-enhancements Along with the release of Visual Studio 2013 came a new release of ASP.NET MVC (called MVC 5) and ASP.NET Web API (called Web API 2). Part of the enhancements to the

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

在上一篇"在ASP.NET MVC4中实现同页面增删改查,无弹出框01,Repository的搭建"中,已经搭建好了Repository层,本篇就剩下增删改查的界面了......今天的阳光真特么好,写完本篇,好出去在阳光下溜溜狗.散散步什么的,正所谓文武之道一张一弛,走神了,进入正题. 首先是一个View Model,在这里定义验证规则,提交和保存数据的时候还必须和领域模型映射. using System; using System.ComponentModel.DataAnnotat

基于视图的增删改查操作(颠覆传统思维吧)

视图是关系型数据库提供的一个非常强大好用的功能,它提供了一种基于基本表(相对视图的虚拟表而言)的数据提取重组和分隔技术. 视图通过对一个或者多个基本表进行数据提取和重新组织,将数据以用户希望的方式重新呈现. 需要注意的是,视图的主要目的就是重新组织多个基础表的数据以新的方式展现,重点是数据展示,并不涉及到增删改的功能.(另一个主要功能是数据隔离) 对于现有市场上不同的数据库来说,对于视图的增删改都不支持,或者说支持的很不好,有很多约束条件. 有人说过,产品功能是有限的,用户需求是无限的,真理.我

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

Java API实现Hadoop文件系统增删改查 Hadoop文件系统可以通过shell命令hadoop fs -xx进行操作,同时也提供了Java编程接口 maven配置 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.ap

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

http://www.cnblogs.com/shanyou/archive/2013/06/11/3131583.html 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的世界中,有关通信的最重要的概念也是契约.XML具有强大对数据的描述能力,Atom格式和AtomPub都建立在XML之上,在Google和微软的推动下,也已经成为标准.但是,Atom/AtomPub和ODBC/OLEDB这样的真正数据交互协议相比较,还有着根本上的欠缺:缺乏数据类型的具体描述,降低了交

ASP.NET Web API对OData的支持

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 在SOA的世界中,最重要的一个概念就是契约(contract).在云计算的世界中,有关通信的最重要的概念也是契约.XML具有强大对数据的描述能力,Atom格式和AtomPub都建立在XML之上,在Google和微软的推动下,也已经成为标准.但是

[转]Using $select, $expand, and $value in ASP.NET Web API 2 OData

本文转自:https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value by Mike Wasson+ Web API 2 adds support for the $expand, $select, and $value options in OData. These options allow a client to

Node.js、express、mongodb 入门(基于easyui datagrid增删改查)

前言 从在本机(win8.1)环境安装相关环境到做完这个demo大概不到两周时间,刚开始只是在本机安装环境并没有敲个Demo,从周末开始断断续续的想写一个,按照惯性思维就写一个增删改查吧,一方面是体验下node.js的魔力,二就是看看node.js.express和mongoose的API,其次就是把自己入门的过程记录下来,方便自己查看,再就是对入门的朋友起一个引导的作用. 敲demo的过程中感觉最爽的就是npm(Node Package Manager)是一个Node.js的包管理和分发工具.