《Entity Framework 6 Recipes》中文翻译——第九章EntityFramework在N层架构程序中的应用(一)
不是所有的应用程序都可以被巧妙地捆绑到一个单一的进程(即驻留在一个物理服务器上)。事实上,在这个不断日益网络化的世界中,许多应用程序架构支持演示,应用程序和数据的经典逻辑层,并分布在多台计算机上部署身体。虽然逻辑分层在一台计算机上的应用程序可以容纳在一个单一的应用程序域,而无需使用代理,编组,序列化和网络协议,即由小到移动设备的东西在发现企业应用服务器跨应用备受关注数据中心需要考虑所有这些因素考虑在内。幸运的是,实体框架与像微软的Windows通讯基础,或Microsoft的Web API框架技术结合在一起,非常适合这些类型的n层应用程序。
在本章中,我们将讨论广泛的使用Entity Framework和n层应用程序的方法。需要明确的是,N层定义为一个应用程序体系结构,其中表现层,业务逻辑层和数据访问层,处理层是物理上分离的多个服务器。这种物理分离可以帮助提高应用程序上午可扩展性,可维护性和应用程序的未来的可扩展,但往往会对性能产生负面影响,因为当处理应用程序操作时,我们正在越过物理机器边界。
N层体系结构增加了一些特殊的功能区实现跟踪实体更改的特点。最初,数据是用一个实体框架来获取的,当数据被发送到客户端时,一个实体框架上下文对象被销毁。在客户端上,对数据的更改没有被跟踪。在更新时,必须创建一个新的上下文对象来处理提交的数据。显然,新的上下文对象不知道以前的上下文对象,也不知道原始实体的值。在本章中,我们将看看一些方法,你可以实现,以帮助弥合这一差距。
在过去的实体框架版本中,开发人员可以利用一个名为“自跟踪实体”的特殊模板,它提供了内置的管道,以帮助跟踪断开实体对象的更改。然而,在实体框架6,自跟踪实体的方法已经过时。虽然传统的对象上下文将支持自跟踪实体,但最近的数据库上下文对象不支持自跟踪实体。在本章着重处理通常会会在你的N层应用程序使用的基本的创建、读取、更新和删除操作。此外,我们将采取深入探讨实体和代理序列化,并发性,和跟踪对象上下文以外的实体变化的挑战。
9.1 使用Web API更新单个断开上下文的实体
问题
如果你想要利用基于Web API的服务进行插入、删除和更新数据存储区。如果你想要实现实体框架6中的Code First方法来管理数据访问。
在这个例子中,我们模拟一个N层的情况下,一个单独的客户端应用程序(控制台应用程序)是一个独立的网站(Web API项目),揭示了基于REST的服务。注意:每层是包含在一个单独的Visual Studio解决方案,以便更容易配置,调试,和多层应用的模拟。
解决方案
你有如下一张表
我们的模型代表订单。我们要把模型和数据库的代码放在Web API服务中,使任何客户中的消费者HTTP可以插入、更新和删除订单数据。要创建服务,执行以下步骤:
1、创建一个新的ASP.NET MVC4 Web应用程序项目,选择Web API模板从项目模板向导。将项目命名为Recipe1.Service。
2、向项目添加一个新的Web Controller应用到Order Controller。
3、添加Order实体类
public class Order { public int Order Id { get; set; } public string Product { get; set; } public int Quantity { get; set; } public string Status { get; set; } public byte[] Time Stamp { get; set; } }
4、在Recipe1.Service项目中添加实体框架6动态库的引用,可以用NuGet包管理器进行实现。在参考上右击,然后选择NuGet包管理器。从联机选项卡,找到并安装实体框架6包。这样做会下载、安装并配置实体框架6库到您的项目中。
5、添加一个名为Recipe1Context的新类,具体代码如下,请确保该类继承之Entity Framework的 DbContext 类
public class Recipe1Context : Db Context { public Recipe1Context() : base("Recipe1Connection String") { } public Db Set<Order> Orders { get; set; } protected override void On Model Creating(Db Model Builder model Builder) { model Builder.Entity<Order>().To Table("Chapter9.Order"); // Following configuration enables timestamp to be concurrency token model Builder.Entity<Order>().Property(x => x.Time Stamp) .Is Concurrency Token() .Has Database Generated Option(Database Generated Option.Computed); } }
6、之后再Web.Config文件的Connection Strings目录下添加Connection String 的链接字符串,具体代码如下
<connection Strings> <add name="Recipe1Connection String" connection String="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; Multiple Active Result Sets=True" provider Name="System.Data.Sql Client" /> </connection Strings>
7、然后在Global.asax文件的application_start方法添加如下代码。此代码将禁用实体框架模型兼容性检查。
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.Set Initializer<Recipe1Context>(null); ... }
8、最后使用一下代码替换OrderControlle中的代码
public class Order Controller : Api Controller { // GET api/order public IEnumerable<Order> Get() { using (var context = new Recipe1Context()) { return context.Orders.To List(); } } // GET api/order/5 public Order Get(int id) { using (var context = new Recipe1Context()) { return context.Orders.First Or Default(x => x.Order Id == id); } } // POST api/order public Http Response Message Post(Order order) { // Cleanup data from previous requests Cleanup(); using (var context = new Recipe1Context()) { context.Orders.Add(order); context.Save Changes(); // create Http Response Message to wrap result, assigning Http Status code of 201, // which informs client that resource created successfully var response = Request.Create Response(Http Status Code.Created, order); // add location of newly-created resource to response header response.Headers.Location = new Uri(Url.Link("Default Api", new { id = order.Order Id })); return response; } }
// PUT api/order/5 public Http Response Message Put(Order order) { using (var context = new Recipe1Context()) { context.Entry(order).State = Entity State.Modified; context.Save Changes(); // return Http Status code of 200, informing client that resouce updated successfully return Request.Create Response(Http Status Code.OK, order); } } // DELETE api/order/5 public Http Response Message Delete(int id) { using (var context = new Recipe1Context()) { var order = context.Orders.First Or Default(x => x.Order Id == id); context.Orders.Remove(order); context.Save Changes(); // Return Http Status code of 200, informing client that resouce removed successfully return Request.Create Response(Http Status Code.OK); } } private void Cleanup() { using (var context = new Recipe1Context()) { context.Database.Execute Sql Command("delete from chapter9.[order]"); } } }
但必须指出的是,当使用实体框架与MVC或者Web API时,ASP.NET框架包含了大量的脚手架(即代码生成tempates),可以生成一个正常运作的控制器,包含你的实体框架管道代码,节省您手动构建的功夫。接下来我们创建客户端解决方案,这将使用Web API服务。
9、新建一个Console application项目,命名Recipe1.Client
10、添加一个与前面一样的Order实体类
最后,用下面的代码替换program.cs 文件中的代码
private Http Client _client; private Order _order; private static void Main() { Task t = Run(); t.Wait(); Console.Write Line("\n Press <enter> to continue..."); Console.Read Line(); } private static async Task Run() { // create instance of the program class var program = new Program(); program.Service Setup(); program.Create Order(); // do not proceed until order is added await program.Post Order Async(); program.Change Order(); // do not proceed until order is changed await program.Put Order Async(); // do not proceed until order is removed await program.Remove Order Async(); } private void Service Setup() { // map URL for Web API cal _client = new Http Client { Base Address = new Uri("http://localhost:3237/") }; // add Accept Header to request Web API content // negotiation to return resource in JSON format _client.Default Request Headers.Accept. Add(new Media Type With Quality Header Value("application/json")); } private void Create Order() { // Create new order _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; } private async Task Post Order Async() { // leverage Web API client side API to call service var response = await _client.Post As Json Async("api/order", _order); Uri new Order Uri; if (response.Is Success Status Code) { // Capture Uri of new resource new Order Uri = response.Headers.Location; // capture newly-created order returned from service, // which will now include the database-generated Id value _order = await response.Content.Read As Async<Order>(); Console.Write Line("Successfully created order. Here is URL to new resource: {0}", new Order Uri); } else Console.Write Line("{0} ({1})", (int)response.Status Code, response.Reason Phrase); } private void Change Order() { // update order _order.Quantity = 10; } private async Task Put Order Async() { // construct call to generate Http Put verb and dispatch // to corresponding Put method in the Web API Service var response = await _client.Put As Json Async("api/order", _order); if (response.Is Success Status Code) { // capture updated order returned from service, which will include new quanity _order = await response.Content.Read As Async<Order>(); Console.Write Line("Successfully updated order: {0}", response.Status Code); } else Console.Write Line("{0} ({1})", (int)response.Status Code, response.Reason Phrase); } private async Task Remove Order Async() { // remove order var uri = "api/order/" + _order.Order Id; var response = await _client.Delete Async(uri); if (response.Is Success Status Code) Console.Write Line("Sucessfully deleted order: {0}", response.Status Code); else Console.Write Line("{0} ({1})", (int)response.Status Code, response.Reason Phrase); }
则输出结果为:
工作原理
通过运行启动Web API应用程序。在Web API应用程序包含一个基于web的MVC控制器,启动时,会弹出一个主页。在这一点上,该网站正在运行,并且它的服务是可用的。
接下来打开控制台应用程序,设置关于在Program.cs文件的第一行代码断点,运行控制台应用程序。首先,我们建立一些基本的管道,映射Web API服务URI和配置Accept头,这将让Web API服务,以JSON格式返回数据。然后,我们创建一个订单对象,其中我们通过调用POST Action方法从HTTP客户端以Json异步方式发送到Web API服务。如果您在订单的Web API控制器类的Action方法上打上断点,你会看到它接收的参数是订单类型的对象,并将其添加到上下文对象Order实体集中。把对象标记为添加的对象,并导致上下文开始跟踪它。最后,我们调用保存更改方法将新数据插入到底层数据存储。然后,我们换的201 HTTP状态代码和新创建的资源的URI位置到一个HTTP响应消息对象,并将其返回给调用应用程序。当使用ASP.NET Web API,我们希望确保我们的客户插入新的数据时产生一个HTTP POST动作。相应的HTTP POST动作将调用在Web API控制器相应的控制器操作方法。
返回到客户端,我们执行我们下一个操作,改变订单的数量,并通过调用Put以Json异步方法发送实体从HTTP客户端返回给Web API服务。如果你在 Order Web API controller 类的Put Action 方法中添加一个断点,你会看到它接收的订单对象作为服务的一个参数。从上下文对象,我们调用输入法,传递Order实体引用。然后,由状态属性设置为修改后附加到底层上下文对象的实体。保存更改后续调用生成SQL语句。在这种情况下,我们更新所有列的顺序。在以后的方法,我们将看到我们如何能够只更新已更改的属性。我们通过发送一个HTTP响应方法为200的HTTP状态代码请求完成操作。
在这个配方,我们已经看到,我们可以封装后面的Web API服务实体框架的数据操作。客户端可以通过使用由Web API客户端API暴露HTTP客户端对象消费该服务。秉承网页API的基于HTTP动词调度,我们利用邮政动作方法添加一个新的记录,认沽操作方法更新记录,删除操作方法删除记录。另外,在配方中,我们使用代码优先approach.In生产应用中实现实体框架,我们将最有可能创建一个单独的层(Visual Studio的类项目)的实体框架数据访问代码从Web API服务分开。