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);
    }
}

 

posted @ 2015-10-31 21:54  Darren Ji  阅读(2726)  评论(0编辑  收藏  举报

我的公众号:新语新世界,欢迎关注。