Partilhar via


Passo a passo das entidades de auto-acompanhamento

Importante

Não recomendamos usar o modelo de entidades de rastreamento automático. Ele continuará disponível apenas para dar suporte aos aplicativos existentes. Se o seu aplicativo exigir o trabalho com grafos de entidades desconectados, considere outras alternativas como Entidades Rastreáveis, que são uma tecnologia semelhante às Entidades de Rastreamento Automático desenvolvidas mais ativamente pela comunidade, ou escreva um código personalizado usando as APIs de controle de alterações de baixo nível.

Este passo a passo demonstra o cenário em que um serviço WCF (Windows Communication Foundation) expõe uma operação que retorna um grafo de entidade. Em seguida, um aplicativo cliente manipula esse grafo e envia as modificações para uma operação de serviço que valida e salva as atualizações em um banco de dados usando o Entity Framework.

Antes de concluir este passo a passo, leia a página Entidades de Auto-Acompanhamento.

Este passo a passo conclui as seguintes ações:

  • Cria um banco de dados para acessar.
  • Cria uma biblioteca de classes que contém o modelo.
  • Alterna para o modelo gerador de entidade de auto-rastreamento.
  • Move as classes de entidade para um projeto separado.
  • Cria um serviço WCF que expõe operações para consultar e salvar entidades.
  • Cria aplicativos cliente (Console e WPF) que consomem o serviço.

Usaremos o Banco de Dados Primeiro neste passo a passo, mas as mesmas técnicas se aplicam igualmente ao Model First.

Pré-Requisitos

Para concluir este passo a passo, você precisará de uma versão recente do Visual Studio.

Criar um banco de dados

O servidor de banco de dados instalado com o Visual Studio é diferente dependendo da versão do Visual Studio que você instalou:

  • Se você estiver usando o Visual Studio 2012, criará um banco de dados LocalDB.
  • Se você estiver usando o Visual Studio 2010, criará um banco de dados SQL Express.

Vamos em frente e gerar o banco de dados.

  • Abra o Visual Studio
  • Exibir ->Gerenciador de Servidores
  • Clique com o botão direito do mouse em Conexões de Dados –> Adicionar conexão...
  • Se você não tiver se conectado a um banco de dados do Gerenciador de Servidores antes de precisar selecionar o Microsoft SQL Server como a fonte de dados
  • Conecte-se ao LocalDB ou ao SQL Express, dependendo de qual você instalou
  • Insira STESample como o nome do banco de dados
  • Selecione OK e você será perguntado se deseja criar um novo banco de dados, selecione Sim
  • Agora, o novo banco de dados será exibido no Gerenciador de Servidores
  • Se você estiver usando o Visual Studio 2012
    • Clique com o botão direito do mouse no banco de dados no Gerenciador de Servidores e selecione Nova Consulta
    • Copie o SQL a seguir para a nova consulta e clique com o botão direito do mouse na consulta e selecione Executar
  • Se você estiver usando o Visual Studio 2010
    • Selecionar Dados –> Editor do SQL transact –> Nova conexão de consulta...
    • Insira .\SQLEXPRESS como o nome do servidor e clique em OK
    • Selecione o banco de dados STESample na lista suspensa na parte superior do editor de consultas
    • Copie o SQL a seguir para a nova consulta e clique com o botão direito do mouse na consulta e selecione Executar 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)

Criar o modelo

Primeiro, precisamos de um projeto para colocar o modelo.

  • Arquivo –> Novo –> Projeto...
  • Selecione Visual C# no painel esquerdo e, em seguida, Biblioteca de Classes
  • Insira STESample como o nome e clique em OK

Agora, criaremos um modelo simples no Designer EF para acessar nosso banco de dados:

  • Projeto –> Adicionar Novo Item...
  • Selecione Dados no painel esquerdo e, em seguida, ADO.NET Modelo de Dados de Entidade
  • Insira BloggingModel como o nome e clique em OK
  • Selecione Gerar do banco de dados e clique em Avançar
  • Insira as informações de conexão do banco de dados que você criou na seção anterior
  • Insira BloggingContext como o nome da cadeia de conexão e clique em Avançar
  • Marque a caixa ao lado de Tabelas e clique em Concluir

Alternar para geração de código STE

Agora, precisamos desabilitar a geração de código padrão e alternar para entidades de autoatendimento.

Se você estiver usando o Visual Studio 2012

  • Expanda BloggingModel.edmx no Gerenciador de Soluções e exclua o BloggingModel.tt e BloggingModel.Context.ttIsso desabilitará a geração de código padrão
  • Clique com o botão direito do mouse em uma área vazia na superfície do Designer EF e selecione Adicionar Item de Geração de Código...
  • Selecione Online no painel esquerdo e pesquise pelo Gerador STE
  • Selecione o gerador STE para o modelo C#, insira STETemplate como o nome e clique em Adicionar
  • Os arquivos STETemplate.tt e STETemplate.Context.tt são adicionados aninhados no arquivo BloggingModel.edmx

Se você estiver usando o Visual Studio 2010

  • Clique com o botão direito do mouse em uma área vazia na superfície do Designer EF e selecione Adicionar Item de Geração de Código...
  • Selecione Código no painel esquerdo e, em seguida, ADO.NET Gerador de Entidade de Auto acompanhamento
  • Insira STETemplate como o nome e clique em Adicionar
  • Os arquivos STETemplate.tt e STETemplate.Context.tt são adicionados diretamente ao seu projeto

Mover tipos de entidade para um projeto separado

Para usar entidades de autoatendimento, nosso aplicativo cliente precisa de acesso às classes de entidade geradas a partir do nosso modelo. Como não queremos expor todo o modelo ao aplicativo cliente, vamos mover as classes de entidade para um projeto separado.

A primeira etapa é parar de gerar classes de entidade no projeto existente:

  • Clique com o botão direito do mouse em STETemplate.tt no Gerenciador de Soluções e selecione Propriedades
  • Na janela Propriedades, desmarque TextTemplatingFileGenerator da propriedade CustomTool
  • Expanda STETemplate.tt no Gerenciador de Soluções e exclua todos os arquivos aninhados sob ele

Em seguida, vamos adicionar um novo projeto e gerar as classes de entidade nele

  • Arquivo –> Adicionar –> Projeto...

  • Selecione Visual C# no painel esquerdo e, em seguida, Biblioteca de Classes

  • Insira STESample.Entities como o nome e clique em OK

  • Projeto –> Adicionar item existente...

  • Navegue até a pasta do projeto STESample

  • Selecione para exibir Todos os Arquivos (*.*)

  • Selecione o arquivo STETemplate.tt

  • Clique na seta suspensa ao lado do botão Adicionar e selecione Adicionar como Link

    Add Linked Template

Também vamos garantir que as classes de entidade sejam geradas no mesmo namespace que o contexto. Isso apenas reduz o número de instruções de uso que precisamos adicionar em todo o nosso aplicativo.

  • Clique com o botão direito do mouse no STETemplate.tt vinculado no Gerenciador de Soluções e selecione Propriedades
  • Na janela Propriedades, defina o Namespace da Ferramenta Personalizada como STESample

O código gerado pelo modelo STE precisará de uma referência a System.Runtime.Serialization para compilar. Essa biblioteca é necessária para os atributos WCF DataContract e DataMember que são usados nos tipos de entidade serializáveis.

  • Clique com o botão direito do mouse no projeto STESample.Entities no Gerenciador de Soluções e selecione Adicionar Referência...
    • No Visual Studio 2012 - marque a caixa ao lado de System.Runtime.Serialization e clique em OK
    • No Visual Studio 2010 - selecione System.Runtime.Serialization e clique em OK

Por fim, o projeto com nosso contexto nele precisará de uma referência aos tipos de entidade.

  • Clique com o botão direito do mouse no projeto STESample no Gerenciador de Soluções e selecione Adicionar Referência...
    • No Visual Studio 2012 - selecione Solução no painel esquerdo, marque a caixa ao lado de STESample.Entities e clique em OK
    • No Visual Studio 2010 - selecione a guia Projetos, selecione STESample.Entities e clique em OK

Observação

Outra opção para mover os tipos de entidade para um projeto separado é mover o arquivo de modelo, em vez de vinculá-lo de seu local padrão. Se você fizer isso, precisará atualizar a variável inputFile no modelo para fornecer o caminho relativo para o arquivo edmx (neste exemplo que seria ..\BloggingModel.edmx).

Criar um serviço WCF

Agora é hora de adicionar um Serviço WCF para expor nossos dados, vamos começar criando o projeto.

  • Arquivo –> Adicionar –> Projeto...
  • Selecione Visual C# no painel esquerdo e, em seguida, Aplicativo de Serviço WCF
  • Insira STESample.Service como o nome e clique em OK
  • Adicionar uma referência ao assembly System.Data.Entity
  • Adicionar uma referência aos projetos STESample e STESample.Entities

Precisamos copiar a cadeia de conexão EF para este projeto para que ela seja encontrada em runtime.

  • Abra o arquivo App.Config para o projeto **STESample **e copie o elemento connectionStrings
  • Cole o elemento connectionStrings como um elemento filho do elemento de configuração do arquivo Web.Config no projeto STESample.Service

Agora é hora de implementar o serviço real.

  • Abra IService1.cs e substitua o conteúdo pelo código a seguir
    using System.Collections.Generic;
    using System.ServiceModel;

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

            [OperationContract]
            void UpdateBlog(Blog blog);
        }
    }
  • Abra Service1.svc e substitua o conteúdo pelo código a seguir
    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.");
                    }
                }
            }        
        }
    }

Consumir o serviço de um aplicativo de console

Vamos criar um aplicativo de console que usa nosso serviço.

  • Arquivo –> Novo –> Projeto...
  • Selecione Visual C# no painel esquerdo e, em seguida, Aplicativo de Console
  • Insira STESample.ConsoleTest como o nome e clique em OK
  • Adicionar uma referência ao projeto STESample.Entities

Precisamos de uma referência de serviço ao nosso serviço WCF

  • Clique com o botão direito do mouse no projeto STESample.ConsoleTest no Gerenciador de Soluções e selecione Adicionar Referência de Serviço...
  • Clique em Descobrir
  • Insira BloggingService como o namespace e clique em OK

Agora podemos escrever algum código para consumir o serviço.

  • Abra Program.cs e substitua o conteúdo pelo código a seguir.
    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);
                }
            }
        }
    }

Agora, você pode executar o aplicativo para vê-lo em ação.

  • Clique com o botão direito do mouse no projeto STESample.ConsoleTest no Gerenciador de Soluções e selecione Depurar –> Iniciar nova instância

Você verá a saída a seguir quando o aplicativo for executado.

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...

Consumir o serviço de um aplicativo WPF

Vamos criar um aplicativo WPF que usa nosso serviço.

  • Arquivo –> Novo –> Projeto...
  • Selecione Visual C# no painel esquerdo e, em seguida, Aplicativo WPF
  • Insira STESample.WPFTest como o nome e clique em OK
  • Adicionar uma referência ao projeto STESample.Entities

Precisamos de uma referência de serviço ao nosso serviço WCF

  • Clique com o botão direito do mouse no projeto STESample.WPFTest no Gerenciador de Soluções e selecione Adicionar Referência de Serviço...
  • Clique em Descobrir
  • Insira BloggingService como o namespace e clique em OK

Agora podemos escrever algum código para consumir o serviço.

  • Abra MainWindow.xaml e substitua o conteúdo pelo código a seguir.
    <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>
  • Abra o code behind para MainWindow (MainWindow.xaml.cs) e substitua o conteúdo pelo código a seguir
    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();
                }
            }
        }
    }

Agora, você pode executar o aplicativo para vê-lo em ação.

  • Clique com o botão direito do mouse no projeto STESample.WPFTest no Gerenciador de Soluções e selecione Depurar –> Iniciar nova instância
  • Você pode manipular os dados usando a tela e salvá-los por meio do serviço usando o botão Salvar

WPF Main window