共用方式為


自我追蹤實體逐步解說

重要

我們不再建議使用自我追蹤實體範本。 繼續提供該範本只是為了支援現有應用程式。 如果您的應用程式需要使用已中斷連線的實體圖形,請考慮使用 Trackable Entities (可追蹤的實體) 之類的其他替代項目,這是一項類似於自我追蹤實體的技術,可由社群更積極地進行開發或使用低層級變更追蹤 API 來撰寫自訂程式碼。

本逐步解說示範 Windows Communication Foundation (WCF) 服務公開傳回實體圖形之作業的案例。 接下來,用戶端應用程式會操作該圖形,並將修改提交至服務作業,該作業會使用 Entity Framework 來驗證和儲存資料庫的更新。

完成本逐步解說之前,請確定您已閱讀 自我追蹤實體 頁面。

這個逐步解說會完成下列動作:

  • 建立要存取的資料庫。
  • 建立包含模型的類別庫。
  • 交換至自我追蹤實體產生器範本。
  • 將實體類別移至個別的專案。
  • 建立 WCF 服務,以公開作業來查詢和儲存實體。
  • 建立取用服務的用戶端應用程式 (主控台和 WPF)。

在本逐步解說中,我們將使用 Database First,但相同的技術同樣適用于 Model First。

必要條件

若要完成本逐步解說,您將需要最新版本的 Visual Studio。

建立資料庫

隨 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]
    CREATE TABLE [dbo].[Blogs] (
        [BlogId] INT IDENTITY (1, 1) NOT NULL,
        [Name] NVARCHAR (200) NULL,
        [Url]  NVARCHAR (200) NULL,
        CONSTRAINT [PK_dbo.Blogs] PRIMARY KEY CLUSTERED ([BlogId] ASC)
    );

    CREATE TABLE [dbo].[Posts] (
        [PostId] INT IDENTITY (1, 1) NOT NULL,
        [Title] NVARCHAR (200) NULL,
        [Content] NTEXT NULL,
        [BlogId] INT NOT NULL,
        CONSTRAINT [PK_dbo.Posts] PRIMARY KEY CLUSTERED ([PostId] ASC),
        CONSTRAINT [FK_dbo.Posts_dbo.Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [dbo].[Blogs] ([BlogId]) ON DELETE CASCADE
    );

    SET IDENTITY_INSERT [dbo].[Blogs] ON
    INSERT INTO [dbo].[Blogs] ([BlogId], [Name], [Url]) VALUES (1, N'ADO.NET Blog', N'blogs.msdn.com/adonet')
    SET IDENTITY_INSERT [dbo].[Blogs] OFF
    INSERT INTO [dbo].[Posts] ([Title], [Content], [BlogId]) VALUES (N'Intro to EF', N'Interesting stuff...', 1)
    INSERT INTO [dbo].[Posts] ([Title], [Content], [BlogId]) VALUES (N'What is New', N'More interesting stuff...', 1)

建立模型

首先,我們需要一個專案來放置模型。

  • 檔案 - > 新增 - > 專案...
  • 從左窗格中選取 [Visual C# ],然後 選取 [類別庫]
  • 輸入 STESample 作為名稱,然後按一下 [ 確定]

現在,我們將在 EF Designer 中建立簡單的模型,以存取我們的資料庫:

  • 專案 - > 新增專案...
  • 從左窗格中選取 [資料 ],然後 ADO.NET [實體資料模型]
  • 輸入 BloggingModel 作為名稱,然後按一下 [ 確定]
  • 選取 [從資料庫 產生],然後按 [ 下一步]
  • 輸入您在上一節中建立之資料庫的連線資訊
  • 輸入 BloggingCoNtext 作為連接字串的名稱,然後按 [下一步]
  • 核取 [資料表 ] 旁的 方塊,然後按一下 [ 完成]

交換至 STE 程式碼產生

現在我們需要停用預設程式碼產生,並交換至自我追蹤實體。

如果您使用 Visual Studio 2012

  • 展開 方案總管 中的 BloggingModel.edmx ,並刪除 BloggingModel.tt BloggingModel.CoNtext.tt 這會停用預設程式碼產生
  • 以滑鼠右鍵按一下 EF Designer 介面上的空白區域,然後選取 [ 新增程式碼產生專案...
  • 從左窗格中選取 [線上 ],然後搜尋 STE 產生器
  • 選取 C# 範本的 STE 產生器,輸入 STETemplate 作為名稱,然後按一下 [ 新增]
  • STETemplate.tt STETemplate.CoNtext.tt 檔案會以巢狀方式新增到 BloggingModel.edmx 檔案底下

如果您使用 Visual Studio 2010

  • 以滑鼠右鍵按一下 EF Designer 介面上的空白區域,然後選取 [ 新增程式碼產生專案...
  • 從左窗格中選取 [ 程式碼 ],然後 ADO.NET 自我追蹤實體產生器
  • 輸入 STETemplate 作為名稱,然後按一下 [ 新增]
  • STETemplate.tt STETemplate.CoNtext.tt 檔案會直接新增至您的專案

將實體類型移至個別專案

若要使用自我追蹤實體,用戶端應用程式需要存取從模型產生的實體類別。 因為我們不想將整個模型公開給用戶端應用程式,所以我們要將實體類別移至個別的專案。

第一個步驟是停止在現有專案中產生實體類別:

  • 以滑鼠右鍵按一下 方案總管 中的 STETemplate.tt ,然後選取 [ 屬性]
  • 在 [ 屬性] 視窗中,從 CustomTool 屬性清除 TextTemplatingFileGenerator
  • 展開 方案總管中的 STETemplate.tt ,並刪除其下巢狀的所有檔案

接下來,我們將新增專案,並在其中產生實體類別

  • 檔案 - > 新增 - > 專案...

  • 從左窗格中選取 [Visual C# ],然後 選取 [類別庫]

  • 輸入 STESample.Entities 作為名稱,然後按一下 [ 確定]

  • 專案 - > 新增現有的專案...

  • 流覽至 STESample 專案資料夾

  • 選取即可檢視 [所有檔案] (*.*)

  • 選取 STETemplate.tt 檔案

  • 按一下 [新增 ] 按鈕旁的 下拉式箭號,然後選取 [ 新增為連結]

    Add Linked Template

我們也將確定實體類別會在與內容相同的命名空間中產生。 這只會減少我們需要在整個應用程式中新增的 using 語句數目。

  • 以滑鼠右鍵按一下方案總管中 連結 的 STETemplate.tt ,然後選取 [ 屬性]
  • 在 [ 屬性] 視窗中,將 [自訂工具命名空間 ] 設定 STESample

STE 範本所產生的程式碼將需要 System.Runtime.Serialization 參考,才能編譯。 WCF DataContract DataMember 屬性需要此程式庫,這些屬性用於可序列化的實體類型。

  • 以滑鼠右鍵按一下 方案總管 中的 STESample.Entities 專案,然後選取 [ 新增參考...
    • 在 Visual Studio 2012 - 核取 System.Runtime.Serialization 的方塊,然後按一下 [ 確定]
    • 在 Visual Studio 2010 - 選取 [System.Runtime.Serialization] ,然後按一下 [ 確定]

最後,具有我們內容的專案將需要實體類型的參考。

  • 以滑鼠右鍵按一下 方案總管 中的 STESample 專案,然後選取 [ 新增參考...
    • 在 Visual Studio 2012 - 從左窗格中選取 [解決方案 ],核取 STESample.Entities 的方塊,然後按一下 [ 確定]
    • 在 Visual Studio 2010 - 選取 [ 專案 ] 索引標籤,選取 [STESample.Entities ],然後按一下 [ 確定]

注意

將實體類型移至個別專案的另一個選項是移動範本檔案,而不是從預設位置連結它。 如果您這樣做,您必須更新 範本中的 inputFile 變數,以提供 edmx 檔案的相對路徑(在此範例中為 。。\BloggingModel.edmx )。

建立 WCF 服務

現在是時候新增 WCF 服務來公開我們的資料,我們將從建立專案開始。

  • 檔案 - > 新增 - > 專案...
  • 從左窗格中選取 [Visual C# ],然後 選取 [WCF 服務應用程式]
  • 輸入 STESample.Service 作為名稱,然後按一下 [ 確定]
  • 新增 System.Data.Entity 元件的參考
  • 新增 STESample STESample.Entities 專案的參考

我們需要將 EF 連接字串複製到此專案,以便在執行時間找到它。

  • 開啟 **STESample ** 專案的 App.Config 檔案,並複製 connectionStrings 元素
  • connectionStrings 專案貼上為 STESample.Service 專案中 Web.Config 檔案 組態 專案的子專案

現在是時候實作實際服務了。

  • 開啟 IService1.cs ,並以下列程式碼取代內容
    using System.Collections.Generic;
    using System.ServiceModel;

    namespace STESample.Service
    {
        [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            List<Blog> GetBlogs();

            [OperationContract]
            void UpdateBlog(Blog blog);
        }
    }
  • 開啟 Service1.svc ,並以下列程式碼取代內容
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;

    namespace STESample.Service
    {
        public class Service1 : IService1
        {
            /// <summary>
            /// Gets all the Blogs and related Posts.
            /// </summary>
            public List<Blog> GetBlogs()
            {
                using (BloggingContext context = new BloggingContext())
                {
                    return context.Blogs.Include("Posts").ToList();
                }
            }

            /// <summary>
            /// Updates Blog and its related Posts.
            /// </summary>
            public void UpdateBlog(Blog blog)
            {
                using (BloggingContext context = new BloggingContext())
                {
                    try
                    {
                        // TODO: Perform validation on the updated order before applying the changes.

                        // The ApplyChanges method examines the change tracking information
                        // contained in the graph of self-tracking entities to infer the set of operations
                        // that need to be performed to reflect the changes in the database.
                        context.Blogs.ApplyChanges(blog);
                        context.SaveChanges();

                    }
                    catch (UpdateException)
                    {
                        // To avoid propagating exception messages that contain sensitive data to the client tier
                        // calls to ApplyChanges and SaveChanges should be wrapped in exception handling code.
                        throw new InvalidOperationException("Failed to update. Try your request again.");
                    }
                }
            }        
        }
    }

從主控台應用程式取用服務

讓我們建立使用我們的服務的主控台應用程式。

  • 檔案 - > 新增 - > 專案...
  • 從左窗格中選取 [Visual C# ],然後 選取 [主控台應用程式]
  • 輸入 STESample.ConsoleTest 作為名稱,然後按一下 [ 確定]
  • 新增 STESample.Entities 專案的參考

我們需要 WCF 服務的服務參考

  • 以滑鼠右鍵按一下 方案總管 中的 STESample.ConsoleTest 專案,然後選取 [ 新增服務參考...]。
  • 按一下 [ 探索]
  • 輸入 BloggingService 作為命名空間,然後按一下 [ 確定]

現在我們可以撰寫一些程式碼來取用服務。

  • 開啟 Program.cs ,並以下列程式碼取代內容。
    using STESample.ConsoleTest.BloggingService;
    using System;
    using System.Linq;

    namespace STESample.ConsoleTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Print out the data before we change anything
                Console.WriteLine("Initial Data:");
                DisplayBlogsAndPosts();

                // Add a new Blog and some Posts
                AddBlogAndPost();
                Console.WriteLine("After Adding:");
                DisplayBlogsAndPosts();

                // Modify the Blog and one of its Posts
                UpdateBlogAndPost();
                Console.WriteLine("After Update:");
                DisplayBlogsAndPosts();

                // Delete the Blog and its Posts
                DeleteBlogAndPost();
                Console.WriteLine("After Delete:");
                DisplayBlogsAndPosts();

                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            static void DisplayBlogsAndPosts()
            {
                using (var service = new Service1Client())
                {
                    // Get all Blogs (and Posts) from the service
                    // and print them to the console
                    var blogs = service.GetBlogs();
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(blog.Name);
                        foreach (var post in blog.Posts)
                        {
                            Console.WriteLine(" - {0}", post.Title);
                        }
                    }
                }

                Console.WriteLine();
                Console.WriteLine();
            }

            static void AddBlogAndPost()
            {
                using (var service = new Service1Client())
                {
                    // Create a new Blog with a couple of Posts
                    var newBlog = new Blog
                    {
                        Name = "The New Blog",
                        Posts =
                        {
                            new Post { Title = "Welcome to the new blog"},
                            new Post { Title = "What's new on the new blog"}
                        }
                    };

                    // Save the changes using the service
                    service.UpdateBlog(newBlog);
                }
            }

            static void UpdateBlogAndPost()
            {
                using (var service = new Service1Client())
                {
                    // Get all the Blogs
                    var blogs = service.GetBlogs();

                    // Use LINQ to Objects to find The New Blog
                    var blog = blogs.First(b => b.Name == "The New Blog");

                    // Update the Blogs name
                    blog.Name = "The Not-So-New Blog";

                    // Update one of the related posts
                    blog.Posts.First().Content = "Some interesting content...";

                    // Save the changes using the service
                    service.UpdateBlog(blog);
                }
            }

            static void DeleteBlogAndPost()
            {
                using (var service = new Service1Client())
                {
                    // Get all the Blogs
                    var blogs = service.GetBlogs();

                    // Use LINQ to Objects to find The Not-So-New Blog
                    var blog = blogs.First(b => b.Name == "The Not-So-New Blog");

                    // Mark all related Posts for deletion
                    // We need to call ToList because each Post will be removed from the
                    // Posts collection when we call MarkAsDeleted
                    foreach (var post in blog.Posts.ToList())
                    {
                        post.MarkAsDeleted();
                    }

                    // Mark the Blog for deletion
                    blog.MarkAsDeleted();

                    // Save the changes using the service
                    service.UpdateBlog(blog);
                }
            }
        }
    }

您現在可以執行應用程式來查看運作狀況。

  • 以滑鼠右鍵按一下 方案總管 中的 STESample.ConsoleTest 專案,然後選取 [偵錯 - > 啟動新實例]

當應用程式執行時,您會看到下列輸出。

Initial Data:
ADO.NET Blog
- Intro to EF
- What is New

After Adding:
ADO.NET Blog
- Intro to EF
- What is New
The New Blog
- Welcome to the new blog
- What's new on the new blog

After Update:
ADO.NET Blog
- Intro to EF
- What is New
The Not-So-New Blog
- Welcome to the new blog
- What's new on the new blog

After Delete:
ADO.NET Blog
- Intro to EF
- What is New

Press any key to exit...

從 WPF 應用程式取用服務

讓我們建立使用我們的服務的 WPF 應用程式。

  • 檔案 - > 新增 - > 專案...
  • 從左窗格中選取 [Visual C# ],然後 選取 [WPF 應用程式]
  • 輸入 STESample.WPFTest 作為名稱,然後按一下 [ 確定]
  • 新增 STESample.Entities 專案的參考

我們需要 WCF 服務的服務參考

  • 以滑鼠右鍵按一下 方案總管 中的 STESample.WPFTest 專案,然後選取 [ 新增服務參考...]。
  • 按一下 [ 探索]
  • 輸入 BloggingService 作為命名空間,然後按一下 [ 確定]

現在我們可以撰寫一些程式碼來取用服務。

  • 開啟 MainWindow.xaml ,並以下列程式碼取代內容。
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:STESample="clr-namespace:STESample;assembly=STESample.Entities"
        mc:Ignorable="d" x:Class="STESample.WPFTest.MainWindow"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">

        <Window.Resources>
            <CollectionViewSource
                x:Key="blogViewSource"
                d:DesignSource="{d:DesignInstance {x:Type STESample:Blog}, CreateList=True}"/>
            <CollectionViewSource
                x:Key="blogPostsViewSource"
                Source="{Binding Posts, Source={StaticResource blogViewSource}}"/>
        </Window.Resources>

        <Grid DataContext="{StaticResource blogViewSource}">
            <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
                      ItemsSource="{Binding}" Margin="10,10,10,179">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding BlogId}" Header="Id" Width="Auto" IsReadOnly="True" />
                    <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="Auto"/>
                    <DataGridTextColumn Binding="{Binding Url}" Header="Url" Width="Auto"/>
                </DataGrid.Columns>
            </DataGrid>
            <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True"
                      ItemsSource="{Binding Source={StaticResource blogPostsViewSource}}" Margin="10,145,10,38">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding PostId}" Header="Id" Width="Auto"  IsReadOnly="True"/>
                    <DataGridTextColumn Binding="{Binding Title}" Header="Title" Width="Auto"/>
                    <DataGridTextColumn Binding="{Binding Content}" Header="Content" Width="Auto"/>
                </DataGrid.Columns>
            </DataGrid>
            <Button Width="68" Height="23" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                    Margin="0,0,10,10" Click="buttonSave_Click">Save</Button>
        </Grid>
    </Window>
  • 開啟 MainWindow 的程式碼後置 ( MainWindow.xaml.cs ),並以下列程式碼取代內容
    using STESample.WPFTest.BloggingService;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Data;

    namespace STESample.WPFTest
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }

            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                using (var service = new Service1Client())
                {
                    // Find the view source for Blogs and populate it with all Blogs (and related Posts)
                    // from the Service. The default editing functionality of WPF will allow the objects
                    // to be manipulated on the screen.
                    var blogsViewSource = (CollectionViewSource)this.FindResource("blogViewSource");
                    blogsViewSource.Source = service.GetBlogs().ToList();
                }
            }

            private void buttonSave_Click(object sender, RoutedEventArgs e)
            {
                using (var service = new Service1Client())
                {
                    // Get the blogs that are bound to the screen
                    var blogsViewSource = (CollectionViewSource)this.FindResource("blogViewSource");
                    var blogs = (List<Blog>)blogsViewSource.Source;

                    // Save all Blogs and related Posts
                    foreach (var blog in blogs)
                    {
                        service.UpdateBlog(blog);
                    }

                    // Re-query for data to get database-generated keys etc.
                    blogsViewSource.Source = service.GetBlogs().ToList();
                }
            }
        }
    }

您現在可以執行應用程式來查看運作狀況。

  • 以滑鼠右鍵按一下 方案總管 中的 STESample.WPFTest 專案,然後選取 [ 偵錯 - > 啟動新實例]
  • 您可以使用畫面運算元據,並使用 [儲存] 按鈕透過服務 儲存資料

WPF Main window