你的位置:首页 > ASP.net教程

[ASP.net教程]Entity Framework 6 Recipes 2nd Edition(9


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的方法。

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