9-2. 用WCF更新单独分离的实体

问题

你想通过WCF为一个数据存储发布查询,插入,删除和修改,并且使这些操作尽可能地简单

此外,你想通过Code First方式实现EF6的数据访问管理

解决方案

假设有如Figure 9-2所示模型.

Figure 9-2. 博客的posts(博文)和comments(评论)模型

模型表示博客的文章和评论之间的关系. 为了简化,我们去掉了很多属性,像作者,发文时间等。我们想把所有的数据库代码封装到WCF后,让客户端通过通过WCF读取,更新,删除,和插入Posts或Comments. 下面是创建WCF服务的步骤:

1. 创建一个新的Visual Studio 解决方案, 添加一个 c# 类库项目.并命名为Recipe2.

2. 在“Recipe1.Service ”项目中添加EF6的引用。最好是借助 NuGet 包管理器来添加。在”引用”上右击,选择”管理 NuGet 程序包.从“联机”标签页,定位并安装EF6包。这样将会下载,安装并配置好EF6库到你的项目中。

3. 向Recipe2项目添加三个类: Post, Comment, 和Recipe2Context. Post 和

Comment 用POCO 实体类,并直接遇到到Post 和 Comment 表. Recipe2Context是提供EF6服务的DbContext对象。确保WCF的实体类拥有

DataContract 和 DataMember 特性,如Listing 9-7所示.

Listing 9-7. POCO 类 Post, Comment,和 Recipe2Context

[DataContract(IsReference = true)]

public class Post

{

public Post()

{

Comments = new HashSet<Comment>();

}

[DataMember]

public int PostId { get; set; }

[DataMember]

public string Title { get; set; }

[DataMember]

public virtual ICollection<Comment> Comments { get; set; }

}

[DataContract(IsReference=true)]

public class Comment

{

[DataMember]

public int CommentId { get; set; }

[DataMember]

public int PostId { get; set; }

[DataMember]

public string CommentText { get; set; }

[DataMember]

public virtual Post Post { get; set; }

}

public class EFRecipesEntities : DbContext

{

public EFRecipesEntities()

: base("name= Recipe2ConnectionString")

{

}

public virtual DbSet<Post> Posts{get;set;}

public virtual DbSet<Comment> Comments{get;set;}

}

4. 向Recipe2项目中添加一个 App.config 文件,把如 Listing 9-8所示的连接字符串复制进去.

Listing 9-8.   Recipe2类库的连接字符串

<connectionStrings>

<add name="Recipe2ConnectionString"

connectionString="Data Source=.;

Initial Catalog=EFRecipes;

Integrated Security=True;

MultipleActiveResultSets=True"

providerName="System.Data.SqlClient" />

</connectionStrings>

5. 向解决方案中添加一个WCF服务应用程序. 使用默认名称Service1(译注:vs2013默认的是WcfService1)

. 用 Listing 9-9替换IService1.cs中的代码

Listing 9-9. The Service Contract for Our Service

[ServiceContract]

public interface IService1

{

[OperationContract]

void Cleanup();

[OperationContract]

Post GetPostByTitle(string title);

[OperationContract]

Post SubmitPost(Post post);

[OperationContract]

Comment SubmitComment(Comment comment);

[OperationContract]

void DeleteComment(Comment comment);

}

6. 用Listing 9-10里的代码替换 Service1.svc.cs 文件中的代码. 添加对Recipe2类库项目的引用,以便正确引用实体类. 添加EF6的引用.

Listing 9-10所示代码实现服务(确保项目引用了System.Data.Entity 和System.Security)

public class Service1 : IService

{

public void Cleanup()

{

using (var context = new EFRecipesEntities())

{

context.Database.ExecuteSqlCommand("delete from chapter9.comment");

context. Database.ExecuteSqlCommand ("delete from chapter9.post");

}

}

public Post GetPostByTitle(string title)

{

using (var context = new EFRecipesEntities())

{

context.Configuration.ProxyCreationEnabled = false;

var post = context.Posts.Include(p => p.Comments)

.Single(p => p.Title == title);

return post;

}

}

public Post SubmitPost(Post post)

{

using(var context=new EFRecipesEntities())//译者:添加

{                                                                         //

context.Entry(post).State =

//id=0表示添加,其它情况表示更新

post.PostId == 0 ? EntityState.Added : EntityState.Modified;

context.SaveChanges();

return post;

}//译者:添加

}

public Comment SubmitComment(Comment comment)

{

using (var context = new EFRecipesEntities())

{

context.Comments.Attach(comment);

if (comment.CommentId == 0)

{

// 插入

context.Entry(comment).State = EntityState.Added);

}

else

{

//设置单个属性状态为modified, 实体状态也会是 modified, 但只更新单个的属性,而不是整个实体

context.entry(comment).Property(x => x.CommentText).IsModified = true;

}

context.SaveChanges();

return comment;

}

}

public void DeleteComment(Comment comment)

{

using (var context = new EFRecipesEntities())

{

context.Entry(comment).State = EntityState.Deleted;

context.SaveChanges();

}

}

}

7.最后,添加一个windows控制台项目,运用它来测试WCF服务,拷贝Listing 9-11里的代码到Program类里,

. 右击控制台项目里的“引用”,选择“添加服务引用”, 并选择Service1 服务(译注:在添加引用前,最好先生成一次服务,不然可能出现引用不了的情况)

. 还需要添加第一步创建的Recipe2类库项目.

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

class Program

{

static void Main(string[] args)

{

using (var client = new ServiceReference2.Service1Client())

{

//清除之前的数据

client.Cleanup();

//插入一个Post

var post = new Post { Title = "POCO Proxies" };

post = client.SubmitPost(post);

// 更新这个 post

post.Title = "Change Tracking Proxies";

client.SubmitPost(post);

// 添加一个 comment

var comment1 = new Comment {

CommentText = "Virtual Properties are cool!",

PostId = post.PostId };

var comment2 = new Comment {

CommentText = "I use ICollection<T> all the time",

PostId = post.PostId };

comment1 = client.SubmitComment(comment1);

comment2 = client.SubmitComment(comment2);

// 更新一个 comment

comment1.CommentText = "How do I use ICollection<T>?";

client.SubmitComment(comment1);

// 删除comment 1

client.DeleteComment(comment1);

//获取 posts的所有 comments

var p = client.GetPostByTitle("Change Tracking Proxies");

Console.WriteLine("Comments for post: {0}", p.Title);

foreach (var comment in p.Comments)

{

Console.WriteLine("\tComment: {0}", comment.CommentText);

}

}

}

}

译注:有两个地方一定要注意,1,Post,Comment类与数据库的映射以及它们之间的关系,上述代码中并未列出。2,需要把步骤4列出的连接字符串复制到Service1项目的web.config里.另:仅从9-1和9-2来看,发现原书有不少错误,甚至是代码上的,有些地方我直接修改过来,也没特别作说明。

控制台输出的结果如下面的 Listing 9-11所示:

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

Comments for post: Change Tracking Proxies

Comment: I use ICollection<T> all the time

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

它是如何工作的?

启动我们用来测试WCF服务的控制台项目,,创建一个WCF服务实例,因为使用using{}块,所以可以确保代码执行出此块,会立即调用Dispose()。防止引起异常,我们调用Cleanup()方法,先把数据库里已有的数据删除,接下来两行代码,我们调用服务的SubmitPost()方法,(这个方法的实现查看Listing 9-10),该方法里先判断PostId的值,如果为0,表示是一个新添加的Post对象,并把它的状态设置为Added. 否则,它就是一个已存在于数据库的Post对象,我们要对它进行更新操作,因为把它的状态设置Modified. 尽管有几分粗糙,但这种方法能判定Post对象状态(是新的还是已存在的),相比ID依赖于整型在运行时初始值为0,一个更好的方法是,传递一个额外的参数到SubmitPost()方法,或是创建一个单独的InsertPost()方法。

最佳实践:如果要插入这个post, 把它的state设置成EntityState.Added. 否则, 把它的state设置成EntityState.Modified. 根据EntityState值会产生一条Insert或是updateSQL语句。

在这个post对象插入后,该对象的PostId会变更成正确的值.

插入Comment或更新Comment一个单独的属性类似于插入或更新post,但有一个很在的不同:我们的业务规则是只更新comment的CommentText 属性.该属性包含comment的主体, 且我们不想更新其它属性.因此,我们给CommentText 作上 modified标志.EF将会生成一个简单的只更新CommentText列的Sql语句.如果我们修改Comment多个属性,我们就需要想办法跟踪到哪个属性在客户端被修改了. 但是用这种方式我们就不需要在客户端用一个复杂的方法来跟踪实体的哪个属性发生变化。

为了删除一个 comment,我们调用context 对象的Entity() 方法, 并传递一个state被设置成Deleted 的 comment 作为参数, 因为comment 被设置为Deleted,所以保存时会生成一条delete的SQL语句。

最后, GetPostByTitle() 为每个符合条件的Post预先加载所有,以post和相关的comments对象图的形式返回. 因为我们应用了POCO类, 我们想要EF返回一个包含post和comment类的动态代理对象.可惜的是, WCF 不能序列化一个代理对象. 然而我们把 ProxyCreationEnabled 设置成false, 就可以简单的使代理类不质疑EF实际返回的对象。如果我们企图序列这个代理对象,会收到如下的错误信息:

基础连接已经关闭: 服务器关闭了本应保持活动状态的连接(译注:测试过)。我们甚至可以在构造函数中使用  ProxyCreationEnabled = false,迫使它在所有对所有服务方法有效果

在本节, 我们通过WCF利用POCO对象进行CRUD操作. 由于没有保存客户端的状态信息,我们只能分别创建插入,修改,删除post和comment的方法。

.在本章的其它节里我们将减少服务端的方法来简化客户端与服务器的通信。

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

Entity Framework 6 Recipes 2nd Edition(9-2)译-用WCF更新单独分离的实体相关推荐

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

    第九章 在N层结构的应用程序中使用EF 不是所有的应用都能完全地写入到一个单个的过程中(就是驻留在一个单一的物理层中),实际上,在当今不断发展的网络世界,大量的应用程序的结构包含经典的表现层,应用程, ...

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

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

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

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

  4. Entity Framework 6 Recipes 2nd Edition(10-6)译 - TPT继承模型中使用存储过程

    10-6. TPT继承模型中使用存储过程 问题 想在一个TPT继承模型中使用存储过程 解决方案 假设已有如Figure 10-6所示模型. 在模型里, Magazine(杂志) and DVD继承于基 ...

  5. Entity Framework 6 Recipes 2nd Edition(12-1)译 - 当SaveChanges( ) 被调用时执行你的代码...

    第12章定制EF 在本章的小节里,定制实体对象和EF处理的一些功能.这些小节将涵盖很多"幕后"的事情,能让你的代码更加统一解决一些事情,比如用一个业务规则中心统一地为实体执行验证. ...

  6. Entity Framework 6 Recipes 2nd Edition(9-7)译-在WCF服务中序列化代理

    9-7. 在WCF服务中序列化代理 问题 从一个查询里返回一个动态代理对象,想要把它序列为一个POCO(Plain-Old CLR Objects)对象. 实现基于POCO实体对象, 在运行时,EF会 ...

  7. Entity Framework 6 Recipes 2nd Edition(10-5)译 - 在存储模型中使用自定义函数

    10-5. 在存储模型中使用自定义函数 问题 想在模型中使用自定义函数,而不是存储过程. 解决方案 假设我们数据库里有成员(members)和他们已经发送的信息(messages) 关系数据表,如Fi ...

  8. Entity Framework 6 Recipes 2nd Edition(10-3)译 - 返回结果是一个标量值

    10-3. 返回结果是一个标量值 问题 想取得存储过程返回的一个标量值. 解决方案 假设我们有如Figure 10-2所示的ATM机和ATM机取款记录的模型 Figure 10-2. 一个ATM机和A ...

  9. Entity Framework 6 Recipes 2nd Edition(11-5)译 - 从”模型定义”函数返回一个匿名类型...

    11-5. 从"模型定义"函数返回一个匿名类型 问题 想创建一个返回一个匿名类型的"模型定义"函数 解决方案 假设已有游客(Visitor) 预订(reserv ...

最新文章

  1. webform(七)分页
  2. 浅析企业开展网站建设具有哪些实际意义?
  3. [LeetCode] Binary Tree Postorder Traversal 二叉树的后序遍历
  4. matlab 读取nc
  5. LUA和C#关于字符串中\0的处理
  6. python帮助文档快捷键_Pycharm快捷键手册
  7. 之前8年都在上班工资16000,厌倦了天天上班的日子,就裸辞了。现在很迷茫,下一步怎么办?
  8. 报表工具Style Report报表打印功能
  9. 取消计算机关机,系统自动关机怎么取消以及系统自动关机命令
  10. 《ARM64体系结构编程与实践》开始预订了
  11. 二等水准数据平差_二等水准测量方法与步骤
  12. 已故女孩在微博“复生”追星,你的数据资产谁说了算?
  13. 企业视频远程办公会议通话系统EasyRTC在Windows2012部署运行出现“计算机中丢失VCRUNTIME140.dll”如何解决?
  14. 计算机二级必过知识点大全,计算机二级ms-office办公软件必过知识点.doc
  15. MySQL连接错误实例
  16. Kaggle提示:TTA(测试时间增加),小,技巧,TTAtesttimeaugmentation,增强
  17. uni-app提交表单成功之后跳转首页
  18. 2 C++标准库(1- IO库、顺序容器和泛型算法)
  19. GUPNet:基于几何不确定性映射的单目3D检测网络(ICCV2021)
  20. linux中boot.log,messages,secure,dnf,cron日志文件的作用

热门文章

  1. pythonargmaxaxis1_keras.argmax中axis = -1的含义是什么?
  2. mysql将大表定时转储_mysql数据库数据定时封装转储
  3. linux如何分析系统的堆栈,Linux内核分析:操作系统是如何工作的?
  4. k1075停运吗_怀化火车站(怀化火车停运最新消息)
  5. php 怎么执行unoconv,web执行php调用exec(unoconv)命令失败解决方案
  6. matlab子函数调用变量,matlab中,怎么样用function自定义函数调用另一个函数名为输入?...
  7. easyexcel注解样式无效_【Java神器】用@ResponseExcel注解将Java List直接转换为Excel文件。不好意思,今晚又可以早点下班了!...
  8. python中用函数货币转换代码_python将人民币转换大写的脚本代码
  9. 成绩表格模板MySQL_Mysql数据统计脚本模板
  10. 深度学习tensorflow变量op