Entity Framework 6 Recipes 2nd Edition(9-1)译->用Web Api更新单独分离的实体

第九章 在N层结构的应用程序中使用EF

不是所有的应用都能完全地写入到一个单个的过程中(就是驻留在一个单一的物理层中),实际上,在当今不断发展的网络世界,大量的应用程序的结构包含经典的表现层,应用程,和数据层,并且它们可能分布在多台计算机上,被分布到一台单独的计算机上的应用程序的某个领域的逻辑层,并不过多地涉及代理服务器编码,序列化,和网络协议,应用程序可以跨越很多设备,从小到一个移动设备到大到一个包含企业所有账户信息的数据服务器。

幸运的是,EF可应用于WCF,WEB Api等诸如此类的多层框架中。

在本章,我们将尽量涵盖EF中多层应用中的使用,N层是指应用程序的表示层,业务逻辑层,和数据层等被分别布暑在不同的服务器上。这种物理上独立分布有助于可扩展性,可维护性,及程序的后期延伸性,但当一个处理需要跨计算机的时候,会带来性能上的影响。N层架构给EF的状态跟踪带来额外的挑战。首先,EF的ContextObject获取数据发送给客户端后被销毁,而在客户端上的数据修改没有被跟踪。

在更新前,必须根据递交的数据新建一个Context Object,很明显,这个新的对象不知道前一个对象的存在,包括实体的原始值。在本章,我们将看到处理这种跟踪挑战上的工具方法。

在EF的之前版本中,一个开发者能利用“跟踪实体”模板,能帮助我们跟踪被被分离的实体。然后在EF6中,它已经被弃用,但是遗留的ObjectContext将支持跟踪实体,本章将关注基本的用于N层的创建,读取,更新和删除操作。此外,将深入探讨实体和代理的序列化,并发,和实体跟踪的工作方式。

9-1.用Web Api更新单独分离的实体

问题

你想利用基于Rest的Web服务来插入,删除,更新到数据存储层。此外,你想通过EF6的Code First方式来实现对数据访问的管理。在此例中,我们效仿一个N层的场景,控制台应用的客户端调用暴露基于REST服务的Web Api应用。每层使用单独的VS解决方案,这样更有利于效仿N层的配置和调试。

解决方案

假设有一个如9-1图所示的模型

Entity Framework 6 Recipes 2nd Edition(9-1)译->用Web Api更新单独分离的实体

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

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服务中分离出来。

附:创建示例用到的数据库的脚本文件

上一篇:微信JSSDK 获取配置信息


下一篇:微信小程序地图获取地点信息(打卡签到功能为例)-2020-7-26