zoukankan      html  css  js  c++  java
  • [转]ASP.NET Web API基于OData的增删改查,以及处理实体间关系

    本文转自:http://www.cnblogs.com/darrenji/p/4926334.html

    本篇体验实现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);
        }
    }
    复制代码
     
     
  • 相关阅读:
    [团队项目] Scrum 项目 3.0 SCRUM 流程的步骤2: Spring 计划
    《构建之法》第6-7章读后感
    【操作系统】实验二 作业调度模拟程序
    团队项目2.0软件改进分析MathAPP
    团队项目
    结对编程2.0
    电影(网剧))项目
    实验0 了解和熟悉操作系统
    复利计算
    学习进度条
  • 原文地址:https://www.cnblogs.com/freeliver54/p/6922778.html
Copyright © 2011-2022 走看看