第九章 在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图所示的模型

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

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

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

  1. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

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

    9-2. 用WCF更新单独分离的实体 问题 你想通过WCF为一个数据存储发布查询,插入,删除和修改,并且使这些操作尽可能地简单 此外,你想通过Code First方式实现EF6的数据访问管理 解决方案 ...

  3. Entity Framework 6 Recipes 2nd Edition(9-3)译->找出Web API中发生了什么变化

    9-3. 找出Web API中发生了什么变化 问题 想通过基于REST的Web API服务对数据库进行插入,删除和修改对象图,而不必为每个实体类编写单独的更新方法. 此外, 用EF6的Code Fri ...

  4. Entity Framework 6 Recipes 2nd Edition(9-4)译->Web API 的客户端实现修改跟踪

    9-4. Web API 的客户端实现修改跟踪 问题 我们想通过客户端更新实体类,调用基于REST的Web API 服务实现把一个对象图的插入.删除和修改等数据库操作.此外, 我们想通过EF6的Cod ...

  5. Entity Framework 6 Recipes 2nd Edition(13-2)译 -> 用实体键获取一个单独的实体

    问题 不管你用DBFirst,ModelFirst或是CodeFirst的方式,你想用实体键获取一个单独的实体.在本例中,我们用CodeFirst的方式. 解决方案 假设你有一个模型表示一个Paint ...

  6. Entity Framework 6 Recipes 2nd Edition(13-3)译 -> 为一个只读的访问获取实体

    问题 你想有效地获取只是用来显示不会更新的操作的实体.另外,你想用CodeFirst的方式来实现 解决方案 一个非常常见行为,尤其是网站,就是只是让用户浏览数据.大多数情况下,用户不会更新数据.在这种 ...

  7. Entity Framework 6 Recipes 2nd Edition(13-4)译 -> 有效地创建一个搜索查询

    问题 你想用LINQ写一个搜索查询,能被转换成更有效率的SQL.另外,你想用EF的CodeFirst方式实现. 解决方案 假设你有如下Figure 13-6所示的模型 Figure 13-6. A s ...

  8. Entity Framework 6 Recipes 2nd Edition(13-5)译 -> 使POCO的修改追踪更高

    问题 你正在使用POCO,你想提高修改跟踪的性能,同时使内存消耗更少.另外,你想通过EF的CodeFirst方式来实现. 解决方案 假设你有一个关于Account(帐户)和相关的Payments(支付 ...

  9. Entity Framework 6 Recipes 2nd Edition(13-9)译 -> 避免Include

    问题 你想不用Include()方法,立即加载一下相关的集合,并想通过EF的CodeFirst方式实现. 解决方案 假设你有一个如Figure 13-14所示的模型: Figure 13-14. A ...

随机推荐

  1. 关于DOM的操作以及性能优化问题-重绘重排

     写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...

  2. 微信企业号 获取AccessToken

    目录 1. AccessToken介绍 2. 示例代码 1. AccessToken介绍 1.1 什么是AccessToken AccessToken即访问凭证,业务服务器每次主动调用企业号接口时需要 ...

  3. .NetCore中的日志(2)集成第三方日志工具

    .NetCore中的日志(2)集成第三方日志工具 0x00 在.NetCore的Logging组件中集成NLog 上一篇讨论了.NetCore中日志框架的结构,这一篇讨论一下.NetCore的Logg ...

  4. redux学习

    redux学习: 1.应用只有一个store,用于保存整个应用的所有的状态数据信息,即state,一个state对应一个页面的所需信息 注意:他只负责保存state,接收action, 从store. ...

  5. 【原】FMDB源码阅读(一)

    [原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...

  6. 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)

    其实一直以来也没有准备在园子里发这样的文章,相对来说,算法改进放在园子里还是会稍稍显得格格不入.但是最近邮箱收到的几封邮件让我觉得有必要通过我的博客把过去做过的东西分享出去更给更多需要的人.从论文刊登 ...

  7. ASP.NET MVC5----常见的数据注解和验证

    只要一直走,慢点又何妨. 在使用MVC模式进行开发时,数据注解是经常使用的(模型之上操作),下面是我看书整理的一些常见的用法. 什么是验证,数据注解 验证 从全局来看,发现逻辑仅是整个验证的很小的一部 ...

  8. git提交项目到已存在的远程分支

    今天想提交项目到github的远程分支上,那个远程分支是之前就创建好的,而我的本地关联分支还没创建.   之前从未用github提交到远程分支过,弄了半个钟,看了几篇博文,终于折腾出来.现在把步骤整理 ...

  9. 自建git node pm2 (不赘述,就说遇见的问题)

    //======================[git]部分 主题部分还是按照网上的办法进行安装. 安装的话  分为两个办法(一个是yum (contos办法)  或者sudo(ubuntu办法) ...

  10. AutoMapper使用中的问题

    指定值只会执行一次 public class MomanBaseProfile : Profile { public MomanBaseProfile() { CreateMap<Request ...