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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM