Entity Framework 6 Recipes 2nd Edition(9-1)譯->用Web Api更新單獨分離的實體

第九章 在N層結構的應用程序中使用EF數據庫

 

不是全部的應用都能徹底地寫入到一個單個的過程當中(就是駐留在一個單一的物理層中),實際上,在當今不斷髮展的網絡世界,大量的應用程序的結構包含經典的表現層,應用程,和數據層,而且它們可能分佈在多臺計算機上,被分佈到一臺單獨的計算機上的應用程序的某個領域的邏輯層,並不過多地涉及代理服務器編碼,序列化,和網絡協議,應用程序能夠跨越不少設備,從小到一個移動設備到大到一個包含企業全部帳戶信息的數據服務器。json

 

幸運的是,EF可應用於WCF,WEB Api等諸如此類的多層框架中。api

在本章,咱們將盡可能涵蓋EF中多層應用中的使用,N層是指應用程序的表示層,業務邏輯層,和數據層等被分別布暑在不一樣的服務器上。這種物理上獨立分佈有助於可擴展性,可維護性,及程序的後期延伸性,但當一個處理須要跨計算機的時候,會帶來性能上的影響。N層架構給EF的狀態跟蹤帶來額外的挑戰。首先,EF的ContextObject獲取數據發送給客戶端後被銷燬,而在客戶端上的數據修改沒有被跟蹤。服務器

在更新前,必須根據遞交的數據新建一個Context Object,很明顯,這個新的對象不知道前一個對象的存在,包括實體的原始值。在本章,咱們將看處處理這種跟蹤挑戰上的工具方法。網絡

在EF的以前版本中,一個開發者能利用「跟蹤實體」模板,能幫助咱們跟蹤被被分離的實體。而後在EF6中,它已經被棄用,可是遺留的ObjectContext將支持跟蹤實體,本章將關注基本的用於N層的建立,讀取,更新和刪除操做。此外,將深刻探討實體和代理的序列化,併發,和實體跟蹤的工做方式。架構

9-1.用Web Api更新單獨分離的實體併發

問題app

你想利用基於Rest的Web服務來插入,刪除,更新到數據存儲層。此外,你想經過EF6的Code First方式來實現對數據訪問的管理。在此例中,咱們效仿一個N層的場景,控制檯應用的客戶端調用暴露基於REST服務的Web Api應用。每層使用單獨的VS解決方案,這樣更有利於效仿N層的配置和調試。框架

解決方案async

假設有一個如9-1圖所示的模型

 

9-1. 一個訂單模型

咱們的模型表示訂單。咱們想要把模型和數據庫代碼放到一個Web Api服務後面,以便任何客戶均可以經過HTTP來插入,更新和刪除訂單數據。爲了建立這個服務,執行如下操做:

1.新建一個 ASP.NET MVC 4 Web 應用項目,命名爲「Recipe1.Service」,並在嚮導中選擇Web API模板。。

2. 向項目中添加一個新的「控制器」,命名爲「OrderController」.

3. 添加Order類,代碼如Listing 9-1所示:

Listing 9-1. Order Entity Class

public class Order
{
    public int OrderId { get; set; }
    public string Product { get; set; }
    public int Quantity { get; set; }
    public string Status { get; set; }
    public byte[] TimeStamp { get; set; }
}
View Code

4. 在「Recipe1.Service 」項目中添加EF6的引用。最好是藉助 NuGet 包管理器來添加。在」引用」上右擊,選擇」管理 NuGet 程序包.從「聯機」標籤頁,定位並安裝EF6包。這樣將會下載,安裝並配置好EF6庫到你的項目中。

5. 而後添加一個新的類「Recipe1Context」,鍵入如Listing 9-2的代碼,並確保該類繼承自EF6的DbContext

297

Listing 9-2. Context Class

public class Recipe1Context : DbContext
{
  public Recipe1Context() : base("Recipe1ConnectionString") { }
  public DbSet<Order> Orders { get; set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Order>().ToTable("Chapter9.Order");
    // Following configuration enables timestamp to be concurrency token
    modelBuilder.Entity<Order>().Property(x => x.TimeStamp)
                      .IsConcurrencyToken()
                      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
  }
}

 

6. 在Web.Configk中ConnectionStrings節裏插入鏈接數據庫的配置,如Listing 9-3:

Listing 9-3. Connection String for the Recipe1 Web API Service

<connectionStrings>

  <add name="Recipe1ConnectionString"  connectionString="Data Source=.;Initial Catalog=EFRecipes;Integrated                 Security=True;MultipleActiveResultSets=True"providerName="System.Data.SqlClient" />

</connectionStrings>

7. 把Listing 9-4所示的代碼插入到Global.asax的 Application_Start 方法中

Listing 9-4. Disable the Entity Framework Model Compatibility Check

protected void Application_Start()
{
    // Disable Entity Framework Model Compatibilty
    Database.SetInitializer<Recipe1Context>(null);

    ...
}

8. 最後,用Listing 9-5所示代碼替換OrderController裏的代碼。

Listing 9-5. Code for the OrderController

public class OrderController : ApiController
{
    // GET api/order
    public IEnumerable<Order> Get()
    {
        using (var context = new Recipe1Context())
        {
            return context.Orders.ToList();
        }
    }

    // GET api/order/5
    public Order Get(int id)
    {
        using (var context = new Recipe1Context())
        {
        return context.Orders.FirstOrDefault(x => x.OrderId == id);
     }
  }   
// POST api/order   public HttpResponseMessage Post(Order order)   {     // Cleanup data from previous requests     Cleanup();     using (var context = new Recipe1Context())     {       context.Orders.Add(order);       context.SaveChanges();       // create HttpResponseMessage to wrap result, assigning Http Status code of 201,       // which informs client that resource created successfully       var response = Request.CreateResponse(HttpStatusCode.Created, order);       // add location of newly-created resource to response header       response.Headers.Location = new Uri(Url.Link("DefaultApi",new { id = order.OrderId }));       return response;     }   }   // PUT api/order/5   public HttpResponseMessage Put(Order order)   {     using (var context = new Recipe1Context())     {       context.Entry(order).State = EntityState.Modified;       context.SaveChanges();       // return Http Status code of 200, informing client that resouce updated successfully       return Request.CreateResponse(HttpStatusCode.OK, order);     }   }   // DELETE api/order/5   public HttpResponseMessage Delete(int id)   {     using (var context = new Recipe1Context())     {       var order = context.Orders.FirstOrDefault(x => x.OrderId == id);       context.Orders.Remove(order);       context.SaveChanges();          // Return Http Status code of 200, informing client that resouce removed successfully       return Request.CreateResponse(HttpStatusCode.OK);     }   }   private void Cleanup()   {     using (var context = new Recipe1Context())     {       context.Database.ExecuteSqlCommand("delete from chapter9.[order]");     }   } }

 

須要着重指出的是,咱們能夠利用大量的工具(好比代碼生成模板)生成可運做的控制器, 保存上述修改。

接下來建立一個調用上述Web API服務的客戶端。

9. 新建一個包含控制檯應用程序的解決方案,命名爲」 Recipe1.Client「.

10. 添加與Listing 9-1一樣的order實體類

最後,用Listing 9-6的代碼替換program.cs裏的代碼

Listing 9-6. Our Windows Console Application That Serves as Our Test Client

 

private HttpClient _client;

private Order _order;

private static void Main()

{

Task t = Run();

t.Wait();

Console.WriteLine("\nPress <enter> to continue...");

Console.ReadLine();

}

private static async Task Run()

{

// create instance of the program class

var program = new Program();

program.ServiceSetup();

program.CreateOrder();

// do not proceed until order is added

await program.PostOrderAsync();

program.ChangeOrder();

// do not proceed until order is changed

await program.PutOrderAsync();

// do not proceed until order is removed

await program.RemoveOrderAsync();

}

private void ServiceSetup()

{

//指定調用Web API的URL

_client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") };

//接受請求的頭部內容

            //經過JSON格式返回資源

_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

}

private void CreateOrder()

{

//建立新訂單

_order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" };

}

private async Task PostOrderAsync()

{

// 利用Web API客戶端API調用服務

var response = await _client.PostAsJsonAsync("api/order", _order);

Uri newOrderUri;

if (response.IsSuccessStatusCode)

{

// 爲新的資源捕獲Uri

newOrderUri = response.Headers.Location;

// 獲取從服務端返回的包含數據庫自增Id的訂單

_order = await response.Content.ReadAsAsync<Order>();

Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri);

}

else

Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);

}

private void ChangeOrder()

{

// 更新訂單

_order.Quantity = 10;

}

private async Task PutOrderAsync()

{

//構造HttpPut調用Web API服務中相應的Put方法

var response = await _client.PutAsJsonAsync("api/order", _order);

if (response.IsSuccessStatusCode)

{

// 獲取從服務端返回的更新後包含新的quanity的訂單

_order = await response.Content.ReadAsAsync<Order>();

Console.WriteLine("Successfully updated order: {0}", response.StatusCode);

}

else

Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);

}

private async Task RemoveOrderAsync()

{

// 移除訂單

var uri = "api/order/" + _order.OrderId;

var response = await _client.DeleteAsync(uri);

if (response.IsSuccessStatusCode)

Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode);

else

Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);

}

客戶端輸出結果如 Listing 9-6:

==========================================================================

Successfully created order:Here is URL to new resource: http://localhost:3237/api/order/1054

Successfully updated order: OK

Sucessfully deleted order: OK

==========================================================================

 

它是如何工做的?

先運行Web API應用程序.這個Web API應用程序包含一個MVC Web Controller, 啓動後會打開首頁。至此網站和服務已經可用。接下來打開控制檯應用程序,在program.cs前面設置一個斷點,運行控制檯應用程序。首先咱們用URI和一些配置創建與Web API服務的管道鏈接,接受由WEB AP服務端返回的JSON格式的頭部信息,而後咱們用PostAsJsonAsync方法發送WEB API請求,並從HttpClient對象裏返回信息建立新的Order對象.若是你在WEB AP服務端controller的Post Action方法裏添加斷點,你將看到它接收到一個Order對象參數並把它添加到訂單實體的Context中.確保該對象狀態爲Added,並讓Context跟蹤它.最後,調用SaveChanges方法把新的訂單插入到數據庫。並封裝一個HTTP狀態碼201和URI定位新建立的資源到HttpResponseMessage對象返回給調用它的應用程序. 當使用 ASP.NET Web API, 要確保咱們的客戶端生成一個HTTP Post請求來插入新的數據,該HTTP Post請求調用相應的Web API controller 中的Post action方法。

再查看客戶端,咱們執行下一個操做,改變訂單的quantity,用HttpClient 對象的PutAsJsonAsync方法把新的訂單發送給Web API.若是WEB API服務的Web API controller裏的 Put Action 方法添加斷點, 能夠看到該方法參數接收到一個訂單對象. 接關調用context 對象的Entry 方法傳遞訂單實體的引用,而後設置State爲 Modified 狀態. 隨後調用SaveChanges產生一個SQL更新語句.會更新訂單的全部列. 在此小節,咱們看到了如何只更新想要更新的屬性. 並返回給調用者一個爲200 HTTP狀態碼。再看客戶端,咱們最後調用移除操做,它會把狀態從數據庫中刪除. 咱們經過把訂單的Id附加到URI中,並調用Web API 的DeleteAsync。在服務端,咱們從數據庫中獲取目標訂單,並傳給訂單的context 對象的Remove方法,使訂單狀態設置爲deleted.

隨後調用SaveChanges產生一個SQL的刪除語句,並把訂單從數據庫中刪除。在此小節,咱們將EF數據操做封裝在Web API服務後,客戶端可能經過HttpClient對象調用服務,經過Web API的 HTTP 方法發佈, 利用Post action方法來添加新記錄, Put action方法更新一條記錄, 和Delete action方法刪除一條記錄.同時咱們學習到了EF6的Code First方式,固然在實際應用中,咱們可能更願意建立一個單獨的層(VS的類庫項目),把EF6數據庫訪問代碼從Web API服務中分離出來。

 

附:建立示例用到的數據庫的腳本文件

相關文章
相關標籤/搜索