我的WCF4 Rest Service及Entity Framework with POCO之旅(三)——用Entity Framework和POCO Template实现数据模型及存储

既然这个系列标题中都含有Entity Framework和POCO,这两者若到第三篇还不出现那就太奇怪了。本文将介绍如何使用Entity Framework和POCO来实现数据模型的创建以及数据存储。

接着上文,我们现在已经有了一个微博服务。但是,一旦网站重启或者IIS重启,我们就会丢失之前所有发布的微博,这样的服务显然不是我们所能接受的,我们接下来要构建服务的数据存储。使用Entity Framework的Model-First设计方式,可以大大简化这个过程。

使用Entity Data Model创建数据模型

我们首先创建一个类库项目WcfRestServiceDemo.Data,然后添加新项“ADO.NET Entity Data Model”:
image

选择数据源,如果有现成的数据源的话可以从数据库导入。我们选择Empty model, 新创建数据模型。
image

从工具箱中找到Entity,拖入设计器,参考之前创建的数据类型,修改得到:
image

到这里数据模型就创建完了。下面我们要把它同步到SQL Server。

由模型生成数据库

到目前为止,模型还只存在C#项目中,虽然我们已经可以写代码来操纵数据,但实际上是无法运行的(或者说一运行就会挂掉的)。

要将模型同步到SQL Server,对设计器视图点右键,选择Generate Database from Model…
image

选择一个连接,没有的话就创建一个:
image

然后对应的SQL脚本就自动生成好了:
image

打开WcfRestServiceDemo.edmx.sql,右键选择Execute SQL,成功后模型就同步到SQL Server了。
image

修改服务代码

现在数据模型和数据存储都做好了,我们要修改服务代码来使用新的数据模型。

删去Microblog.cs,添加对WcfRestServiceDemo.Data和System.Data.Entity的引用,并修改MicroblogService.cs代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.ServiceModel;

using System.ServiceModel.Activation;

using System.ServiceModel.Web;

using WcfRestServiceDemo.Data;

 

namespace WcfRestServiceDemo.Service

{

    [ServiceContract(Namespace = "WcfRestServiceDemo")]

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

    public class MicroblogService

    {

        [WebGet(UriTemplate = "")]

        public List<Microblog> GetCollection()

        {

            using(var container = new WcfRestServiceDemoContainer())

            {

                return container.Microblogs.ToList();

            }

        }

 

        [WebInvoke(UriTemplate = "", Method = "POST")]

        public Microblog Create(Microblog microblog)

        {

            microblog.PublishTime = DateTime.Now;

            using(var container = new WcfRestServiceDemoContainer())

            {

                container.Microblogs.AddObject(microblog);

                container.SaveChanges();

            }

            return microblog;

        }

 

        [WebGet(UriTemplate = "{id}")]

        public Microblog Get(string id)

        {

            using(var container = new WcfRestServiceDemoContainer())

            {

                return container.Microblogs.FirstOrDefault(m => m.Id == int.Parse(id));

            }

        }

 

        [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]

        public void Delete(string id)

        {

            using(var container = new WcfRestServiceDemoContainer())

            {

                container.Microblogs.DeleteObject(

                    container.Microblogs.First(m => m.Id == int.Parse(id)));

                container.SaveChanges();

            }

        }

    }

}


要让Entity Framework正常工作,我们还需要把它生成的app.config文件中的ConnectionString复制到服务的web.config文件中:

<add name="WcfRestServiceDemoContainer" connectionString="metadata=res://*/WcfRestServiceDemo.csdl|res://*/WcfRestServiceDemo.ssdl|res://*/WcfRestServiceDemo.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.;Initial Catalog=WcfRestServiceDemo;Integrated Security=True;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />

这里附带一提,有的时候我们会遇到无法正确加载Model的情形,这时只要将上面那个连接字符串中的*替换成数据模型(*.edmx)所在的程序集名称(这里是WcfRestServiceDemo.Data),显式指定搜索路径即可。

好,都改好了,现在测试一下服务,我先手工往数据库中插入一条记录,然后访问
http://localhost:6421/microblogs/
image

怎么回事?Content, Id, PublishTime这些属性倒是一个也不少,但是也多了很多其他的东西。这是因为与POCO不同,基于EntityObject的Entity Data Model支持更改提醒、关系管理等许多其他功能,所以会包含更多的属性,只不过在我们这个应用场景中,并不需要这些支持。另一个问题是,这时默认的命名空间变成了
xmlns=http://schemas.datacontract.org/2004/07/WcfRestServiceDemo.Data
而且由于Microblog这个DataModel是自动生成的,我没有办法改它的DataContractAttribute(改了下次生成又会变回来):

[EdmEntityTypeAttribute(NamespaceName="WcfRestServiceDemo", Name="Microblog")]

[Serializable()]

[DataContractAttribute(IsReference=true)]

public partial class Microblog : EntityObject

{

    …

}

要解决上述问题,我们该请出下一位主角POCO了。

使用ADO.NET C# POCO Entity Generator生成POCO模型

POCO全称Plain-Old CLR Objects(中文不知道怎么说)。以前有很多人手写POCO模型,不过现在已经有了一个非常强大的基于T4模板的模型生成器。

打开Visual Studio 2010的Extension Manager,找到ADO.NET C# POCO Entity Generator并安装(或者在这里下载):
image

然后打开WcfRestServiceDemo.edmx,右键选择Add Code Generation Item…:
image

找到刚才安装的模板:
image

创建结果:
image

Model.Context.tt和Model.tt分别是Container和Entity的T4模板。现在再运行一下服务,获取所有微博:
image

为什么会Empty Response? 调试一下服务,发现返回的类型不是Microblog, 而是System.Data.Entity.DynamicProxies.Microblog_
C60A574FC06ABEF4672858332CE687DDE70D188AD3BD73ED2731E8854D30C927,而WCF并不认识这个类型。这是因为默认的Entity Data Model启用了LazyLoading和Proxy机制的缘故。这两个特性我以后也许会详细讲,不过目前我们只要把它们关闭就好。虽然Model设计器提供了下面这个属性可以修改:
image

但是当我改成False然后对Model.Context.tt右键选择Run Custom Tool 重新生成Container代码,却没有任何效果,Container构造器代码依旧如下:
image

如果有哪位高手知道应该如何设置,请告诉我。我们现在先通过扩展Container类的方法来解决这个问题,下一篇讲如何通过修改T4模板来改变生成的Container。

在WcfRestServiceDemo.Data中创建WcfRestServiceDemoContainer.cs,代码如下:

using System;



namespace WcfRestServiceDemo.Data

{

    partial class WcfRestServiceDemoContainer

    {

        public static void Go(Action<WcfRestServiceDemoContainer> todo, 

            bool isLazy = false)

        {

            Go<object>(entities =>

            {

                todo(entities);

                return null;

            },

            isLazy);

        }



        public static void GoAndSave(Action<WcfRestServiceDemoContainer> todo, 

            bool isLazy = false)

        {

            Go<object>(entities =>

            {

                todo(entities);

                entities.SaveChanges();

                return null;

            },

            isLazy);

        }



        public static T Go<T>(Func<WcfRestServiceDemoContainer, T> todo, 

            bool isLazy = false)

        {

            using(var entities = new WcfRestServiceDemoContainer())

            {

                entities.ContextOptions.LazyLoadingEnabled = isLazy;

                entities.ContextOptions.ProxyCreationEnabled = false;

                return todo(entities);

            }

        }



        public static T GoAndSave<T>(Func<WcfRestServiceDemoContainer, T> todo, 

            bool isLazy = false)

        {

            return Go(entities =>

            {

                var result = todo(entities);

                entities.SaveChanges();

                return result;

            },

            isLazy);

        }

    }

}

这里我顺便写了几个帮助方法,因为每次使用Container时写using很麻烦,关键是我经常改完数据忘记调SaveChanges().

最后修改MicroblogService.cs代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.ServiceModel;

using System.ServiceModel.Activation;

using System.ServiceModel.Web;

using WcfRestServiceDemo.Data;



namespace WcfRestServiceDemo.Service

{

    [ServiceContract(Namespace = "WcfRestServiceDemo")]

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]

    public class MicroblogService

    {

        [WebGet(UriTemplate = "")]

        public List<Microblog> GetCollection()

        {

            return WcfRestServiceDemoContainer.Go(container =>

                container.Microblogs.ToList());

        }



        [WebInvoke(UriTemplate = "", Method = "POST")]

        public Microblog Create(Microblog microblog)

        {

            microblog.PublishTime = DateTime.Now;

            WcfRestServiceDemoContainer.GoAndSave(container =>

                container.Microblogs.AddObject(microblog));

            return microblog;

        }



        [WebGet(UriTemplate = "{id}")]

        public Microblog Get(string id)

        {

            return WcfRestServiceDemoContainer.Go(container =>

                container.Microblogs.FirstOrDefault(m => m.Id == int.Parse(id)));

        }



        [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]

        public void Delete(string id)

        {

            WcfRestServiceDemoContainer.GoAndSave(container =>

                container.Microblogs.DeleteObject(

                    container.Microblogs.First(m => m.Id == int.Parse(id))));

        }

    }

}

再次访问http://localhost:6421/microblogs/

image

啊哈,又回到最初干净的状态了。只是Namespace问题以及属性名称的大小写问题还是没有解决,但是我们已经离目标更近一步了,我将在下一章讨论这些。

小结

本文介绍了:

  • 如何用Entity Framework的Model-First方式构建数据模型及数据存储
  • 如何改用POCO实现数据模型
  • 如何关闭LazyLoading和ProxyCreation
  • 以及一点点代码的sugar

我的WCF4 REST Service及Entity Framework with POCO之旅系列

你可能感兴趣的:(framework)