自跟踪实体演练Self-Tracking Entities Walkthrough

重要

我们不再建议使用自跟踪实体模板。 它将仅继续用于支持现有应用程序。 如果应用程序需要使用断开连接的实体图,请考虑其他替代方案,例如可跟踪实体,它与自跟踪实体类似,社区在更积极地开发这种技术,或使用低级别更改跟踪 API 编写自定义代码。

本演练演示了 Windows Communication Foundation (WCF)服务公开返回实体关系图的操作的情况。 接下来,客户端应用程序操作该图并将修改提交给使用实体框架验证和保存数据库更新的服务操作。

在完成本演练之前,请务必阅读 “自跟踪实体“ 页。

此演练完成以下操作:

  • 创建要访问的数据库。
  • 创建包含模型的类库。
  • 交换到 “自跟踪实体生成器” 模板。
  • 将实体类移到单独的项目中。
  • 创建一个 WCF 服务,该服务公开用于查询和保存实体的操作。
  • 创建使用服务的客户端应用程序(控制台和 WPF)。

我们将在本演练中使用 Database First,但相同的技术同样适用于 Model First。

先决条件Pre-Requisites

若要完成本演练,你将需要最新版本的 Visual Studio。

创建数据库Create a Database

随 Visual Studio 一起安装的数据库服务器因安装的 Visual Studio 版本而异:

  • 如果使用的是 Visual Studio 2012,则将创建一个 LocalDB 数据库。
  • 如果使用的是 Visual Studio 2010,则将创建 SQL Express 数据库。

接下来,生成数据库。

  • 打开 Visual Studio
  • 视图-> 服务器资源管理器
  • 右键单击 “数据连接-> 添加连接 …
  • 如果尚未从服务器资源管理器连接到数据库,则需要选择Microsoft SQL Server作为数据源
  • 连接到 LocalDB 或 SQL Express,具体取决于你安装的是哪个
  • 输入STESample作为数据库名称
  • 选择 “确定” ,系统会询问您是否要创建新数据库,请选择 “是”
  • 新数据库现在将出现在服务器资源管理器
  • 如果使用的是 Visual Studio 2012
    • 在服务器资源管理器中右键单击该数据库,然后选择 “新建查询
    • 将以下 SQL 复制到新的查询中,然后右键单击该查询,然后选择 “执行
  • 如果使用的是 Visual Studio 2010
    • 选择数据> Transact-sql 编辑器-> 新建查询连接 …
    • 输入 。\SQLEXPRESS作为服务器名称,然后单击 “确定”
    • 从 “查询编辑器” 顶部的下拉菜单中选择 “ STESample “ 数据库
    • 将以下 SQL 复制到新的查询中,然后右键单击该查询,然后选择 “执行 SQL “。
  1. CREATE TABLE [dbo].[Blogs] (
  2. [BlogId] INT IDENTITY (1, 1) NOT NULL,
  3. [Name] NVARCHAR (200) NULL,
  4. [Url] NVARCHAR (200) NULL,
  5. CONSTRAINT [PK_dbo.Blogs] PRIMARY KEY CLUSTERED ([BlogId] ASC)
  6. );
  7. CREATE TABLE [dbo].[Posts] (
  8. [PostId] INT IDENTITY (1, 1) NOT NULL,
  9. [Title] NVARCHAR (200) NULL,
  10. [Content] NTEXT NULL,
  11. [BlogId] INT NOT NULL,
  12. CONSTRAINT [PK_dbo.Posts] PRIMARY KEY CLUSTERED ([PostId] ASC),
  13. CONSTRAINT [FK_dbo.Posts_dbo.Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [dbo].[Blogs] ([BlogId]) ON DELETE CASCADE
  14. );
  15. SET IDENTITY_INSERT [dbo].[Blogs] ON
  16. INSERT INTO [dbo].[Blogs] ([BlogId], [Name], [Url]) VALUES (1, N'ADO.NET Blog', N'blogs.msdn.com/adonet')
  17. SET IDENTITY_INSERT [dbo].[Blogs] OFF
  18. INSERT INTO [dbo].[Posts] ([Title], [Content], [BlogId]) VALUES (N'Intro to EF', N'Interesting stuff...', 1)
  19. INSERT INTO [dbo].[Posts] ([Title], [Content], [BlogId]) VALUES (N'What is New', N'More interesting stuff...', 1)

创建模型Create the Model

首先,我们需要一个项目来放置模型。

  • 文件->> 项目 .。。
  • 从左窗格中选择 “ Visual C#然后选择” 类库 “
  • 输入STESample作为名称,然后单击 “确定”

现在,我们将在 EF 设计器中创建一个简单的模型来访问数据库:

  • 项目-> “添加新项 …”
  • 从左窗格中选择 “数据“,然后ADO.NET 实体数据模型
  • 输入BloggingModel作为名称,然后单击 “确定”
  • 选择 “从数据库生成“,然后单击 “下一步
  • 输入在上一部分中创建的数据库的连接信息
  • 输入 “bloggingcontext” 作为连接字符串的名称,然后单击 “下一步
  • 选中 ““ 旁边的框,然后单击 “完成

交换到粘贴代码生成Swap to STE Code Generation

现在,我们需要禁用默认代码生成并交换到自跟踪实体。

如果使用的是 Visual Studio 2012If you are using Visual Studio 2012

  • 展开解决方案资源管理器中的BloggingModel ,删除BloggingModel.ttBloggingModel.Context.tt 这将禁用默认代码生成
  • 右键单击 EF 设计器图面上的空白区域,然后选择 “添加代码生成项 …
  • 从左窗格中选择 “联机“,然后搜索粘贴生成器
  • 选择粘贴生成器 For C# 模板,输入STETemplate作为名称,然后单击 “添加
  • STETemplate.ttSTETemplate.Context.tt文件添加到了 BloggingModel 文件下

如果使用的是 Visual Studio 2010If you are using Visual Studio 2010

  • 右键单击 EF 设计器图面上的空白区域,然后选择 “添加代码生成项 …
  • 从左窗格中选择 “代码“,然后ADO.NET 自跟踪实体生成器
  • 输入STETemplate作为名称,然后单击 “添加
  • STETemplate.ttSTETemplate.Context.tt文件会直接添加到你的项目

将实体类型移到单独的项目中Move Entity Types into Separate Project

若要使用自跟踪实体,客户端应用程序需要访问模型中生成的实体类。 由于我们不想将整个模型公开给客户端应用程序,因此我们要将实体类移到单独的项目中。

第一步是停止生成现有项目中的实体类:

  • 解决方案资源管理器中右键单击 “ STETemplate.tt “,然后选择 “属性
  • 在 “属性“ 窗口中,清除 “ CustomTool “ 属性中的TextTemplatingFileGenerator
  • 解决方案资源管理器中展开 “ STETemplate.tt “,并删除其下嵌套的所有文件

接下来,我们将添加一个新项目,并在其中生成实体类

  • 文件-> 添加> 项目 .。。

  • 从左窗格中选择 “ Visual C#然后选择” 类库 “

  • 输入STESample作为名称,然后单击 “确定”

  • 项目-> 添加现有项 .。。

  • 导航到STESample项目文件夹

  • 选择查看所有文件(**)

  • 选择STETemplate.tt文件

  • 单击 “添加“ 按钮旁边的下拉箭头,然后选择 “添加为链接“。

    添加链接模板

我们还将确保在上下文相同的命名空间中生成实体类。 这只是减少了我们需要在应用程序中添加的 using 语句的数量。

  • 解决方案资源管理器中右键单击链接的STETemplate.tt ,然后选择 “属性
  • 在 “属性“ 窗口中,将自定义工具命名空间设置为STESample

粘贴模板生成的代码将需要引用 system.exception才能进行编译。 在可序列化实体类型上使用的 WCF DataContractDataMember特性需要此库。

  • 右键单击解决方案资源管理器中的STESample项目,然后选择 “添加引用 …
    • 在 Visual Studio 2012 中,选中 “ system.web “ 旁边的框,然后单击 “确定”
    • 在 Visual Studio 2010 中,选择 “ System.web “ 并单击 “确定”

最后,具有中的上下文的项目将需要对实体类型的引用。

  • 解决方案资源管理器中右键单击 “ STESample “ 项目,然后选择 “添加引用 …
    • 在 Visual Studio 2012 中,从左窗格中选择 “解决方案“,选中 “ STESample “ 旁边的框,然后单击 “确定”
    • 在 Visual Studio 2010 中,选择 “项目“ 选项卡,选择 “ STESample “,然后单击 “确定”

备注

将实体类型移动到单独的项目的另一种方法是移动模板文件,而不是将其链接到其默认位置。 如果执行此操作,则需要更新模板中的inputFile变量,以提供 edmx 文件的相对路径(在本示例中,为 ..\BloggingModel)。

创建 WCF 服务Create a WCF Service

现在是时候添加 WCF 服务来公开数据,接下来我们将创建该项目。

  • 文件-> 添加> 项目 .。。
  • 从左窗格中选择 “ Visual C# ,然后选择” WCF 服务应用程序
  • 输入STESample作为名称,然后单击 “确定”
  • 添加对system.web程序集的引用
  • 添加对STESampleSTESample项目的引用

需要将 EF 连接字符串复制到此项目中,以便在运行时找到它。

  • 打开 STESample 项目的app.config文件并复制connectionStrings元素
  • STESample项目中,将connectionStrings元素粘贴为web.config文件的configuration元素的子元素

现在是时候实现实际服务了。

  • 打开IService1.cs并将内容替换为以下代码
  1. using System.Collections.Generic;
  2. using System.ServiceModel;
  3. namespace STESample.Service
  4. {
  5. [ServiceContract]
  6. public interface IService1
  7. {
  8. [OperationContract]
  9. List<Blog> GetBlogs();
  10. [OperationContract]
  11. void UpdateBlog(Blog blog);
  12. }
  13. }
  • 打开Service1并将内容替换为以下代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Linq;
  5. namespace STESample.Service
  6. {
  7. public class Service1 : IService1
  8. {
  9. /// <summary>
  10. /// Gets all the Blogs and related Posts.
  11. /// </summary>
  12. public List<Blog> GetBlogs()
  13. {
  14. using (BloggingContext context = new BloggingContext())
  15. {
  16. return context.Blogs.Include("Posts").ToList();
  17. }
  18. }
  19. /// <summary>
  20. /// Updates Blog and its related Posts.
  21. /// </summary>
  22. public void UpdateBlog(Blog blog)
  23. {
  24. using (BloggingContext context = new BloggingContext())
  25. {
  26. try
  27. {
  28. // TODO: Perform validation on the updated order before applying the changes.
  29. // The ApplyChanges method examines the change tracking information
  30. // contained in the graph of self-tracking entities to infer the set of operations
  31. // that need to be performed to reflect the changes in the database.
  32. context.Blogs.ApplyChanges(blog);
  33. context.SaveChanges();
  34. }
  35. catch (UpdateException)
  36. {
  37. // To avoid propagating exception messages that contain sensitive data to the client tier
  38. // calls to ApplyChanges and SaveChanges should be wrapped in exception handling code.
  39. throw new InvalidOperationException("Failed to update. Try your request again.");
  40. }
  41. }
  42. }
  43. }
  44. }

从控制台应用程序使用服务Consume the Service from a Console Application

让我们创建一个使用我们的服务的控制台应用程序。

  • 文件->> 项目 .。。
  • 从左窗格中选择 “ Visual C# ,然后选择”控制台应用程序
  • 输入STESample作为名称,然后单击 “确定”
  • 添加对STESample项目的引用

我们需要对 WCF 服务的服务引用

  • 解决方案资源管理器中右键单击 “ ConsoleTest “ 项目,然后选择 “添加服务引用 …
  • 单击发现
  • 输入BloggingService作为命名空间,然后单击 “确定”

现在,我们可以编写一些代码来使用该服务。

  • 打开Program.cs并将内容替换为以下代码。
  1. using STESample.ConsoleTest.BloggingService;
  2. using System;
  3. using System.Linq;
  4. namespace STESample.ConsoleTest
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. // Print out the data before we change anything
  11. Console.WriteLine("Initial Data:");
  12. DisplayBlogsAndPosts();
  13. // Add a new Blog and some Posts
  14. AddBlogAndPost();
  15. Console.WriteLine("After Adding:");
  16. DisplayBlogsAndPosts();
  17. // Modify the Blog and one of its Posts
  18. UpdateBlogAndPost();
  19. Console.WriteLine("After Update:");
  20. DisplayBlogsAndPosts();
  21. // Delete the Blog and its Posts
  22. DeleteBlogAndPost();
  23. Console.WriteLine("After Delete:");
  24. DisplayBlogsAndPosts();
  25. Console.WriteLine("Press any key to exit...");
  26. Console.ReadKey();
  27. }
  28. static void DisplayBlogsAndPosts()
  29. {
  30. using (var service = new Service1Client())
  31. {
  32. // Get all Blogs (and Posts) from the service
  33. // and print them to the console
  34. var blogs = service.GetBlogs();
  35. foreach (var blog in blogs)
  36. {
  37. Console.WriteLine(blog.Name);
  38. foreach (var post in blog.Posts)
  39. {
  40. Console.WriteLine(" - {0}", post.Title);
  41. }
  42. }
  43. }
  44. Console.WriteLine();
  45. Console.WriteLine();
  46. }
  47. static void AddBlogAndPost()
  48. {
  49. using (var service = new Service1Client())
  50. {
  51. // Create a new Blog with a couple of Posts
  52. var newBlog = new Blog
  53. {
  54. Name = "The New Blog",
  55. Posts =
  56. {
  57. new Post { Title = "Welcome to the new blog"},
  58. new Post { Title = "What's new on the new blog"}
  59. }
  60. };
  61. // Save the changes using the service
  62. service.UpdateBlog(newBlog);
  63. }
  64. }
  65. static void UpdateBlogAndPost()
  66. {
  67. using (var service = new Service1Client())
  68. {
  69. // Get all the Blogs
  70. var blogs = service.GetBlogs();
  71. // Use LINQ to Objects to find The New Blog
  72. var blog = blogs.First(b => b.Name == "The New Blog");
  73. // Update the Blogs name
  74. blog.Name = "The Not-So-New Blog";
  75. // Update one of the related posts
  76. blog.Posts.First().Content = "Some interesting content...";
  77. // Save the changes using the service
  78. service.UpdateBlog(blog);
  79. }
  80. }
  81. static void DeleteBlogAndPost()
  82. {
  83. using (var service = new Service1Client())
  84. {
  85. // Get all the Blogs
  86. var blogs = service.GetBlogs();
  87. // Use LINQ to Objects to find The Not-So-New Blog
  88. var blog = blogs.First(b => b.Name == "The Not-So-New Blog");
  89. // Mark all related Posts for deletion
  90. // We need to call ToList because each Post will be removed from the
  91. // Posts collection when we call MarkAsDeleted
  92. foreach (var post in blog.Posts.ToList())
  93. {
  94. post.MarkAsDeleted();
  95. }
  96. // Mark the Blog for deletion
  97. blog.MarkAsDeleted();
  98. // Save the changes using the service
  99. service.UpdateBlog(blog);
  100. }
  101. }
  102. }
  103. }

现在可以运行应用程序来查看其实际运行情况。

  • 解决方案资源管理器中右键单击 “ ConsoleTest “ 项目,然后选择 “调试-> 启动新实例

当应用程序执行时,将看到以下输出。

  1. Initial Data:
  2. ADO.NET Blog
  3. - Intro to EF
  4. - What is New
  5. After Adding:
  6. ADO.NET Blog
  7. - Intro to EF
  8. - What is New
  9. The New Blog
  10. - Welcome to the new blog
  11. - What's new on the new blog
  12. After Update:
  13. ADO.NET Blog
  14. - Intro to EF
  15. - What is New
  16. The Not-So-New Blog
  17. - Welcome to the new blog
  18. - What's new on the new blog
  19. After Delete:
  20. ADO.NET Blog
  21. - Intro to EF
  22. - What is New
  23. Press any key to exit...

从 WPF 应用程序使用服务Consume the Service from a WPF Application

让我们创建一个使用我们的服务的 WPF 应用程序。

  • 文件->> 项目 .。。
  • 从左窗格中选择 “ Visual C# ,然后选择” WPF 应用程序
  • 输入STESample作为名称,然后单击 “确定”
  • 添加对STESample项目的引用

我们需要对 WCF 服务的服务引用

  • 解决方案资源管理器中右键单击 “ WPFTest “ 项目,然后选择 “添加服务引用 …
  • 单击发现
  • 输入BloggingService作为命名空间,然后单击 “确定”

现在,我们可以编写一些代码来使用该服务。

  • 打开mainwindow.xaml并将内容替换为以下代码。
  1. <Window
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. xmlns:STESample="clr-namespace:STESample;assembly=STESample.Entities"
  7. mc:Ignorable="d" x:Class="STESample.WPFTest.MainWindow"
  8. Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
  9. <Window.Resources>
  10. <CollectionViewSource
  11. x:Key="blogViewSource"
  12. d:DesignSource="{d:DesignInstance {x:Type STESample:Blog}, CreateList=True}"/>
  13. <CollectionViewSource
  14. x:Key="blogPostsViewSource"
  15. Source="{Binding Posts, Source={StaticResource blogViewSource}}"/>
  16. </Window.Resources>
  17. <Grid DataContext="{StaticResource blogViewSource}">
  18. <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
  19. ItemsSource="{Binding}" Margin="10,10,10,179">
  20. <DataGrid.Columns>
  21. <DataGridTextColumn Binding="{Binding BlogId}" Header="Id" Width="Auto" IsReadOnly="True" />
  22. <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="Auto"/>
  23. <DataGridTextColumn Binding="{Binding Url}" Header="Url" Width="Auto"/>
  24. </DataGrid.Columns>
  25. </DataGrid>
  26. <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
  27. ItemsSource="{Binding Source={StaticResource blogPostsViewSource}}" Margin="10,145,10,38">
  28. <DataGrid.Columns>
  29. <DataGridTextColumn Binding="{Binding PostId}" Header="Id" Width="Auto" IsReadOnly="True"/>
  30. <DataGridTextColumn Binding="{Binding Title}" Header="Title" Width="Auto"/>
  31. <DataGridTextColumn Binding="{Binding Content}" Header="Content" Width="Auto"/>
  32. </DataGrid.Columns>
  33. </DataGrid>
  34. <Button Width="68" Height="23" HorizontalAlignment="Right" VerticalAlignment="Bottom"
  35. Margin="0,0,10,10" Click="buttonSave_Click">Save</Button>
  36. </Grid>
  37. </Window>
  • 打开 Mainwindow.xaml (MainWindow.xaml.cs)的隐藏代码,并将内容替换为以下代码
  1. using STESample.WPFTest.BloggingService;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Windows;
  5. using System.Windows.Data;
  6. namespace STESample.WPFTest
  7. {
  8. public partial class MainWindow : Window
  9. {
  10. public MainWindow()
  11. {
  12. InitializeComponent();
  13. }
  14. private void Window_Loaded(object sender, RoutedEventArgs e)
  15. {
  16. using (var service = new Service1Client())
  17. {
  18. // Find the view source for Blogs and populate it with all Blogs (and related Posts)
  19. // from the Service. The default editing functionality of WPF will allow the objects
  20. // to be manipulated on the screen.
  21. var blogsViewSource = (CollectionViewSource)this.FindResource("blogViewSource");
  22. blogsViewSource.Source = service.GetBlogs().ToList();
  23. }
  24. }
  25. private void buttonSave_Click(object sender, RoutedEventArgs e)
  26. {
  27. using (var service = new Service1Client())
  28. {
  29. // Get the blogs that are bound to the screen
  30. var blogsViewSource = (CollectionViewSource)this.FindResource("blogViewSource");
  31. var blogs = (List<Blog>)blogsViewSource.Source;
  32. // Save all Blogs and related Posts
  33. foreach (var blog in blogs)
  34. {
  35. service.UpdateBlog(blog);
  36. }
  37. // Re-query for data to get database-generated keys etc.
  38. blogsViewSource.Source = service.GetBlogs().ToList();
  39. }
  40. }
  41. }
  42. }

现在可以运行应用程序来查看其实际运行情况。

  • 解决方案资源管理器中右键单击 “ WPFTest “ 项目,然后选择 “调试-> 启动新实例
  • 你可以使用屏幕操作数据,并使用 “保存“ 按钮通过服务保存数据

WPF 主窗口