通过 RIA Services 进行 POCO 实体





0/5 (0投票)
本文演示了如何从 Entity Framework v4 (EF4) 生成纯旧类对象 (POCO),并在 SilverLight 4 版本中通过 RIA Services 使用它们。
引言
使用纯旧类对象 (POCO) 的目的是为了可移植性、测试、数据安全性、数据验证和提供数据服务,将数据与 Entity Framework v4 (EF4) .edmx 文件分离。进一步的抽象是将 POCO 类放在与 EDMX 文件不同的 DLL 中。本文通过演示如何使用 .tt 文件加上 .tt 文件 (T4) 生成器来生成纯旧类对象 (POCO),然后通过 RIA Services SP1 Beta 将这些不依赖于数据上下文的 POCO 传递到 Silverlight v4。在此示例中,您必须安装以下软件:
- Visual Studio 2010
- Entity Framework v4
- SilverLight 4 Tools for Visual Studio
- T4 POCO Entity Generator for C#
- RIA Services SP1 Beta
此外,您还必须有一个要连接的数据库源;我使用的是 SQL Server,但您可以使用任何数据库。
背景
Microsoft 并没有让这件事变得容易——使用与 EDMX 分离 (即在单独的 DLL 中) 的 POCO 实体,结合 T4、EF4 和 RIA Services 的支持或文档非常少,因为这项技术非常新。事实上,那些实现了 EF3.5 和早期版本 RIA Services 的开发者会发现它发生了相当大的变化。
连接到您的数据库
- 在 Visual Studio 中,单击“文件”->“新建项目”。您将选择项目模板 **ASP.NET 动态数据实体 Web 应用程序**。如果看不到此模板,请单击“其他语言”并选择“Visual C#”。使用以下信息:
- 项目名称:BasicDataViewer
- 类型:ASP.NET 动态数据实体 Web 应用程序
- 目的:此项目用于存储您的 EDMX 文件,并可以显示 SQL Server、Oracle 或其他 EF4 生成的实体在一个网站中,以便您确认复杂的关联。
- 项目名称:BasicDataViewer
- 右键单击新项目 BasicDataViewer,选择“添加”->“新建项”。您将添加一个 **ADO.NET 实体数据模型**,可以在 Visual C# -> 数据模板目录中找到。
- 项目名称:BasicDataViewer
- 项名称:DBData
- 类型:ADO.NET 实体数据模型
- 目的:此项将帮助您创建一个实体模型,以逻辑映射到您的数据库 (映射由 EF4 在您保存 .edmx 时自动完成)。
- 单击“确定”后,您需要按照向导完成数据源的连接。选择“从数据库生成”并创建新连接。勾选 **将您的连接设置保存到 Web.Config** (在实体数据模型向导的第二步底部有一个复选框)。在向导的第三步中,您将选择要包含在模型中的数据库表。单击“确定”后,它应该会创建一个基本模型大纲。
- 在 .edmx 编辑器中,右键单击没有任何表或关联显示的空白区域,然后选择“属性”。您将更改 .edmx 的以下属性:
- 元数据工件处理 = 复制到输出目录
- 命名空间 = DBDataModel
- 实体容器名称 = DBData
- 重建解决方案后,编辑您的 web.config 文件并更新以下设置 (如果 web.config 中被注释掉了,则取消注释此行)。
DefaultModel.RegisterContext(typeof(DBData), new ContextConfiguration() { ScaffoldAllTables = false });
- 按 F5 编译并调试您的项目。它应该在一个带有关系的网站中显示您的数据。如果未显示或出现错误,您需要解决它们 (旧数据库可能因为允许 `null` 字符而报错,在这种情况下,搜索该字段;您可能需要编辑 .edmx 的后端将 `nullable="true"` 设置为 `true`)。
使用 T4 (POCO 实体生成器) 创建 POCO DLL
- 在先前创建的 `BasicDataViewer` 项目的 .edmx 编辑器中,右键单击没有任何表或关联显示的空白区域,然后选择“添加代码生成项”。您将在 Visual C# 模板类型下创建一个 **ADO.NET POCO 实体生成器**。
- 项目名称:BasicDataViewer
- 项名称:DBDataModelPOCO
- 类型:ADO.NET POCO 实体生成器
- 目的:此项将生成 T4 文件,用于创建您的 POCO 实体。它应该在项目中添加两个项,名为 DBDataModelPOCO.Context.tt 和 DBDataModelPOCO.tt
- 在您的解决方案中创建一个新的 **类库** 项目 (文件 -> 添加 -> 新建项目)。如果自动创建了 `Class1.cs`,则将其删除。
- 项目名称:Entities
- 类型:类库
- 目的:此项目将包含我们的 POCO 实体以及验证、键属性和可能的业务逻辑。
- 项目名称:Entities
- 通过右键单击 Entities 项目并选择“添加引用”,添加对框架程序集 `System.Data.Entity` 的引用。
- 按住 <SHIFT> 键,将 DBDataModelPOCO.Context.tt 和 DBDataModelPOCO.tt 文件从 `BasicDataViewer` 项目拖到 Entities 项目。您会注意到在 .tt 文件下有自动生成的类。
- 双击每个 .tt 文件进行编辑;您将更改 .edmx 的位置,以便它知道如何构建类。
string inputFile = @"..\BasicDataViewer\DBData.edmx";
- 接下来,右键单击每个 .tt 文件并选择“运行自定义工具”;这将重新创建您的 POCO 实体并确认与 .edmx 的连接。
- 通过右键单击 Entities 类库项目并选择“生成”选项来生成它。
- 通过右键单击 `BasicDataViewer` 项目,选择“添加引用”、“项目”选项卡、“Entities”,将对 `Entities` 项目的引用添加到 `BasicDataViewer` 项目中。
- 编辑 `BasicDataViewer` web.config 文件并更新以下设置。
DefaultModel.RegisterContext(typeof(Entities.DBData), new ContextConfiguration() { ScaffoldAllTables = true});
- 最后,按 F5 编译并运行项目,测试 POCO 实体是否被正确访问。如果出现错误,您可能需要解决它们。我此时没有遇到任何错误。
在 Silverlight 中使用 RIA Services 访问 POCO 实体
- 通过右键单击 Entities 项目并选择“添加引用”,将对 `System.Componentmodel.DataAnnotation` 的引用添加到 Entities 项目中。
- 步骤 3 中需要更新的类将因您在 .emdx 文件中设置的表而异。通过双击 .tt 文件下的类并查找原始属性,可以轻松确定要更新的类。不要太担心弄错,如果您没有正确设置某个类,编译器会抱怨您没有键。这是我的结构,我只有一个名为 Zip.cs 的类。
- DBDataModelPOCO.Context.tt
- DBDatamodelPOCO.Context.cs
- DBDataModelPOCO.tt
- DBDataModelPOCO.cs
- Zip.cs
- DBDataModelPOCO.Context.tt
- 对于所有非复杂类型,您还需要定义键和元数据。在此示例中,我只访问一个名为 `Zip` 的表。因此,我编辑 DBDataModelPOCO.tt 下的 Zip.cs 以包含元数据和键属性。元数据是您进行验证和业务逻辑的地方。这些元数据类应分解到单独的文件中,并通过 Zip 类中的 `MetadataTypeAttribute` 属性进行引用,但我将其放在了内部类中,以便您了解它们是“伙伴”。
//------------------------------------------------------------------------------ // <auto-generated> // This code was generated from a template. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> //------------------------------------------------------------------------------ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; // ********** You are going to add this using System.ComponentModel.DataAnnotations; namespace Entities { // ********** You are going to add this [MetadataTypeAttribute(typeof(Entities.Zip.ZipMetadata))] public partial class Zip { // I created this internal class right here so that RIA can use the class internal sealed class ZipMetadata { private ZipMetadata() { } // I created this key class right here so that RIA can use the class [Key] public string ZipCode { get; set; } } #region Primitive Properties public virtual string ZipCode { get; set; } public virtual string City { get; set; } public virtual string County { get; set; } public virtual string State { get; set; } public virtual decimal TaxRate { get; set; } public virtual byte[] tstamp { get; set; } #endregion } }
- 接下来,我们将向解决方案添加一个 **WCF RIA Services 类库** 项目。右键单击解决方案并选择“添加项目”,然后从 Silverlight 模板下选择 **WCF RIA Services 类库**。
- 项目名称:EntRIAServices
- 类型:WCF RIA Services 类库
- 目的:这为您的 Silverlight 应用程序提供了 RIA 服务。它将创建两个子项目,我们感兴趣的是有一个 .Web 扩展的子项目,名为 EntRIAServices.Web;另一个子项目名为 EntRIAServices。
EntRIAServices
~ 这是主项目EntRIAServices
~ 这是一个子项目EntRIAServices.Web
~ 这是一个子项目
- 项目名称:EntRIAServices
- 删除在两个子项目中自动创建的 `Class1.cs` 文件。
- 通过右键单击 `EntRIAServies.Web` 子项目并选择“添加引用”,添加对框架程序集 `System.Data.Entity` 的引用。
- 通过右键单击 `EntRIAServies.Web` 子项目并选择“添加引用”,添加对 `Entities` 项目的引用。
- 重新生成整个解决方案,并检查错误。您在这里遇到的任何错误都应与步骤 3 相关。
- 向 `EntRIAServices.Web` 子项目添加一个名为 `EntityDomainService` 的 **域服务类**。通过右键单击 `EntRIAServices.Web` 子项目,选择“添加”->“新建项”->“域服务类”来执行此操作。在出现的“添加新域服务”屏幕中,**请务必在“可用 DataContext/ObjectContext 类”下选择 <Empty Domain Service Class>,并确保选中“启用客户端访问”**。
- 项目名称:EntRIAServices.Web
- 项名称:EntityDomainService
- 类型:域服务类
- 目的:这基本上是 RIA 服务的 WCF 部分;它是您的服务的代码生成器,我不知道为什么 Microsoft 这样命名它。我们正在创建一个空的域服务,因为我们不使用 Linq (我们有我们的实体)。
- 现在我们有了 RIA 服务代码生成器,我们将为其添加访问点。这可以通过创建 `iQueryable`、`Invoke` 或 `IEnumerable` 接口来完成,请参阅我下面的注释,了解如何获取正确的连接字符串。我在这里遇到的许多错误都与引用元数据有关,元数据是一组由我们的 .edmx 文件创建的文件 (属性“元数据工件处理”=“复制到输出”) (保存时创建),用于定义您的数据库结构如何映射到 POCO 实体;最好将它们嵌入为资源。
namespace EntRIAServices.Web { using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.ServiceModel.DomainServices.Hosting; using System.ServiceModel.DomainServices.Server; // TODO: Create methods containing your application logic. [EnableClientAccess()] public class EntityDomainService : DomainService { // For this I copied the connection string from the // web.config file of the BasicDataViewer file // (i.e. it's where by .EDMX file is located and the // .EDMX generated it for me because I selected // Save your connection settings to your Web.config when I created it) // I had to modify it to point to the physical location of the // metadata files generated by my // .EDMX also, I changed the escape characters to single quotes // after selecting the Metadata Artifact property to //"Copy to Output Directory" private Entities.DBData _context = new Entities.DBData ("metadata=C:\\Users\\Owner\\Documents\\Visual Studio 2010\\ Projects\\BasicDataViewer\\BasicDataViewer\\bin\\DBData.csdl|C:\\ Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\ BasicDataViewer\\BasicDataViewer\\bin\\DBData.ssdl|C:\\Users\\ Owner\\Documents\\Visual Studio 2010\\Projects\\BasicDataViewer\\ BasicDataViewer\\bin\\DBData.msl;provider=System.Data.SqlClient; provider connection string='Data Source=SERVER\\DB_NAME; Initial Catalog=YOUR_DATABASE_NAME;User ID=YOUR_DB_LOGIN; Password=YOUR_DB_PWD;MultipleActiveResultSets=True'"); [Query] public IQueryable GetZips() { return (IQueryable)_context.Zips.AsQueryable(); } } }
- 重新生成整个解决方案,这将在 `EntRIAServices` 子项目下创建一个名为 `Generated_Code` 的隐藏文件夹,这就是您的 RIA 服务连接。您可以忽略它,或者通过显示此子项目的所有文件来检查它。如果生成后没有出现,则说明出了问题,您需要回溯您的步骤。
- 现在,我们将向解决方案添加一个 **SilverLight 业务应用程序** 项目,以使用我们的 RIA 服务。您可以通过右键单击解决方案并选择“添加”->“新建项目”,然后在 Silverlight 已安装模板下选择“Silverlight 业务应用程序”来执行此操作。
- 项目名称:BusinessApplication1
- 类型:Silverlight 业务应用程序
- 目的:使用 RIA 服务并通过用户界面进行数据编辑。
- 项目名称:BusinessApplication1
- 第 12 步将创建两个项目,一个名为 `BusinessApplication1`,另一个名为 `BusinessApplication1.Web`。您需要在 `BusinessApplication1` 项目中引用 `EntRIAServices` 项目 (右键单击 `BusinessApplication1` 并选择“添加”->“添加引用”)。此外,您还需要在 `BusinessApplication1.Web` 项目中引用 `EntRIAServices.Web` 项目 (右键单击 `BusinessApplication1.Web` 并选择“添加”->“引用”)。
- 重新生成整个解决方案,两次。所有错误都应该会消失,因为代码已被生成以支持您的 RIA 服务。
- 双击 `BusinessApplication1` 项目的 MainPage.xaml。这将打开 Silverlight UI 编辑器。现在,单击“数据”->“显示数据源”。您的实体连接应该会显示所有项。将其拖到 UI 工作区。这应该会在 Silverlight 工作区创建一个 POCO 实体对象的网格。
- 按 F5 编译并运行解决方案。它应该显示一个网格,其中包含从数据库填充的 POCO。如果未显示或出现未找到错误,请确保您没有尝试返回过多记录 (504 错误),并且您的 RIA 服务 Generated_Code 隐藏文件夹位于 `EntRIAServices` 子项目中 (500 错误)。如果问题不是这些,并且您无法解决 RIA 错误,请尝试使用
Fiddler2
。
创建托管 POCO 实体的域服务工厂
您需要创建一个域服务工厂来正确托管您的 POCO 实体。使用 POCO 会出现一个问题,即必须通过 Linq 包含关系才能托管数据。Microsoft 撰写了一篇关于使用 Include 语句共享实体的文章,但这不适用于 POCO,因为它们应该不依赖于上下文。此外,通常情况下,您可能想要连接两个 .edmx 文件,或者在实体之间提供高度自定义的业务关系,甚至可能想要在提供数据之前进行用户访问检查。
请参阅下面的评论和我相关的文章创建域服务工厂来托管 POCO 实体。
结论
这是本解决方案的一个示例,由于压缩率很高,您需要使用 7Zip 来提取这些文件。
从数据库模型的 .edmx 实体模型创建 POCO 实体模型,该模型是应用程序数据服务的模型,以便 RIA Services 为您的 Silverlight 应用程序建模,这只是为了执行一点点创建、读取、更新和删除 (CRUD),开销很大,但我一直对 MVVM 结构有同感,因为它使更改底层数据库变得困难。
如果您想听听我对 RIA Services 的看法:它对于它所做的来说设置太复杂,而且我发现它容易出错;然而,一旦您设置好了六个项目,我可以看到它在“快速”建模和托管逻辑数据库方面非常强大。如果我可以在部署项目时通过 .edmx 上的一个属性来使用 POCO,并且相关的业务逻辑以某种方式与可见模型绑定,那就更容易了。无论如何,我希望我能帮助到所有正在敲键盘尝试解决 RIA Services 和 EF4 的用户,但那是不可能的,所以我希望这篇文章能有所帮助。
诚挚地,
Joseph Wykel, M.B.A., M.E., B.A.