Consegui ler a documentação sobre POCO no EF4 no MSDN e fazer meus primeiros testes. O que eu achei:
O suporte a POCO no EF4 é muito bom. Os caras criaram um ORM que permite modelar a entidade conceitual, que depois vai mapear para a classe em si. Depois você deve modelar o mapeamento dela com um banco de dados ou gerar um banco de dados novo, e criar sua própria classe para utilizar com o modelo conceitual. Ficou ótimo. Vou colocar aqui um exemplo simples.
No meu exemplo estou usando um projeto console simples. Não me interessam os padrões de projeto ou arquitetura, me interessa ver o EF4 funcionando. A primeira coisa é criar o arquivo do EF, o EDMX. Vou fazer aqui um início de um sistema de blogs, que é algo que todo mundo conhece.
Observação: Não existiu EF2 ou EF3. Existiu EF1 que é a que saiu com o SP1 do .Net Framework 3.5. Depois saiu o .Net Framework 4.0, e agora todo mundo fala em EF4, por causa do .Net Framework 4, não em EF2.
Primeiro crio o EDMX do zero (sem banco de dados) e crio duas entidades:
Até aí nada de mais.
No solution explorer eu retiro o texto de "Custom Tool", vejam abaixo:
Isso é importante para que o arquivo .cs não seja gerado junto ao modelo. Deste jeito temos apenas um modelo conceitual e não as classes geradas (classes do contexto do EF, das entidades, etc).
Preciso criar então as classes. Nada de mais, as classes são simples. Classe de Post:
using System.Collections.Generic; namespace BlogEF4 { public class Post { public virtual int Id { get; set; } public virtual string Body { get; set; } private IList<Comment> _comments = new List<Comment>(); public virtual IList<Comment> Comments { get { return _comments; } set { _comments = value; } } } }
A propriedade "Comments" não é automática porque eu quero inicializá-la. Se tornasse ela automática só poderia fazer isso no construtor, só que se houver uma classe proxy (mais sobre isso a frente), eu teria problemas. Tente fazer, e você também vai ter.
A classe de comentários é simples também:
namespace BlogEF4 { public class Comment { public virtual int Id { get; set; } public virtual string Text { get; set; } public virtual Post Post { get; set; } } }
Super simples, certo? Notaram os "virtuals" lá? Isso está lá para permitir que o EF crie "classes proxy" no lugar das classes reais. Isso não é obrigatório, mas é a única maneira de ter lazy loading no EF, e de ganhar uma boa performance no acompanhamento de mundaças do EF. Se você não fizer isso, para acompanhar as mudanças o EF vai tirar uma cópia dos dados e guardar. Desta forma ele cria uma classe que herda da sua, e coloca lá todo o código necessário para fazer o acompanhamento de mudanças. Imagino que, na prática, esta nova classe implemente as interfaces do EF, criando efetivemante uma IPOCO, e essa então faz todo o trabalho de acompanhamento. Para saber o que você precisa fazer para usar uma classe proxy, veja a documentação do EF no MSDN (a documentação também é beta).
Com isso, eu tenho que criar um contexto do EF. O contexto normalmente é criado pela ferramenta, mas como eu desabilitei o gerador de código, ele não está mais sendo criado. Mas o código é simples:
using System.Data.Objects; namespace BlogEF4 { class BlogContext : ObjectContext { public BlogContext() : base("name=BlogModelContainer", "BlogModelContainer") { } private IObjectSet<Post> _posts; public IObjectSet<Post> Posts { get { if (_posts == null) { _posts = CreateObjectSet<Post>(); } return _posts; } } } }
Ele herda de ObjectContext, que pede no construtor a string de conexão (que criaremos em seguida), e o nome do container, que deve estar criado no contexto (arquivo EDMX – você acha esse nome clicando no espaço em branco do Canvas e visualizando suas propriedades). A propriedade Posts é um IObjectSet, que herda de IQueryable, ou seja, continuamos com suporte total ao LINQ.
Falta só criar o banco e o novo EF permite trabalhar "Model First", o que também é uma grande melhoria. Você vai precisar criar a database antes, então faça isso. Depois disso basta clicar com o botão direito sobre o canvas do designer do EF e selecionar "Generate Database Script From Model…". Isso vai abrir um wizard que vai ter permitir selecionar o banco, e no fim vai gerar um script salvo no diretório do projeto, que você vai poder rodar direto pelo Visual Studio.
Final do Wizard:
Arquivo salvo, basta clicar no botão para rodar:
No final o arquivo EDMX vai ser atualizado com as informações do banco de dados, e o seu web.config/app.config vai ter a string de conexão adicionada:
<?xml version="1.0" encoding="utf-8"?> <configuration> <connectionStrings><add name="BlogModelContainer" connectionString="metadata=res://*/BlogModel.csdl|res://*/BlogModel.ssdl|res://*/BlogModel.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.\sqlexpress;Initial Catalog=Blog;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" /></connectionStrings> </configuration>
E é isso aí. Agora é só testar. Vejam minha classe de programa (console):
using System; using System.Linq; namespace BlogEF4 { class Program { static void Main(string[] args) { SaveEntities(); ShowEntities(); Console.ReadKey(); } private static void SaveEntities() { var context = new BlogContext(); context.ContextOptions.DeferredLoadingEnabled = true; var post = new Post { Body = "novo b" }; post.Comments.Add(new Comment { Text = "novo c", Post = post }); context.AddObject("PostSet", post); context.SaveChanges(); } private static void ShowEntities() { var context = new BlogContext(); context.ContextOptions.DeferredLoadingEnabled = true; var posts = context.Posts.ToList(); foreach (var post in posts) { Console.WriteLine("Post id: {0} Body: {1}", post.Id, post.Body); foreach (var comment in post.Comments) { Console.WriteLine("Comment id: {0} Comment Text: {1}", comment.Id, comment.Text); } } } } }
Notem que estou fazendo consulta e inclusão, e tudo funciona. Vejam o resultado:
O SQL que está rodando também é bem simples. Como tem lazy loading, são os seguintes (sniffei eles com SQL Profiller):
--Incluido posts: exec sp_executesql N'insert [dbo].[PostSet]([Body]) values (@0) select [Id] from [dbo].[PostSet] where @@ROWCOUNT > 0 and [Id] = scope_identity()',N'@0 nvarchar(max) ',@0=N'novo b' --Incluindo comments: exec sp_executesql N'insert [dbo].[CommentSet]([Text], [Post_Id]) values (@0, @1) select [Id] from [dbo].[CommentSet] where @@ROWCOUNT > 0 and [Id] = scope_identity()',N'@0 nvarchar(max) ,@1 int',@0=N'novo c',@1=5 --selecionando posts: SELECT [Extent1].[Id] AS [Id], [Extent1].[Body] AS [Body] FROM [dbo].[PostSet] AS [Extent1] --selecionando os comments de um post: exec sp_executesql N'SELECT 1 AS [C1], [Extent1].[Id] AS [Id], [Extent1].[Text] AS [Text], [Extent1].[Post_Id] AS [Post_Id] FROM [dbo].[CommentSet] AS [Extent1] WHERE [Extent1].[Post_Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
Sem segredos.
É bom notar que esse padrão de consulta que estou fazendo é errado. É o chamado SELECT N+1, onde vou fazer uma consulta a mais para cada post, para pegar os comentários. O ideal seria fazer isso sem lazy loading, já que vou ter N consultas para cada post, mais uma para obter os posts (por isso chama N+1).
Meus feelings sobre esse primeiro contato:
A Microsoft deu um GRANDE passo com essa nova versão. Só este suporte a POCOs já está muito legal. Vi também que há outras forma de trabalhar o carregamento dos dados, depois coloco aqui. O acompanhamento das mudanças dos dados também é feito de maneira interessante. Com T4 seremos capazes de gerar o código das classes, algo que ainda não vi. De qualquer forma, não gosto de código gerado, e já disse isso aqui antes, então não é algo que valorizarei de qualquer forma. Código gerado é bom só na interface gráfica, e ainda assim, com cuidado. Poder trabalhar model first, gerando o banco, também é bem legal. Seria legal ser capaz de gerar o banco programaticamente. Isso é especialmente útil em PoCs e protótipos, mas de qualquer forma é muito bom que agora podemos trabalhar o modelo antes.
A única coisa que não gostei é que, se quero ter lazy loading, sou obrigado a deixar todas as minhas propriedades públicas. Não consigo aplicar um padrão onde eu esconderia a coleção em uma propriedade protected ou private, e daria acesso a ela apenas como um IEnumerable, ou seja, ela não poderia ser alterada, permitindo alterações apenas através de métodos controlados, como um "AddComment". Eu só posso fazer isso se abrir mão de lazy loading, e isso geralmente não é uma opção. Isso é especialmente útil em cenários de relações bidirecionais. Veja um exemplo no MSDN onde a pessoa fez exatamente isso (sem lazy loading). Um maneira de lidar com isso seria trabalhar com repositórios checando as relações, mas isso é ruim. Ou ainda trabalhar com interfaces para as entidades, mas isso geralmente implica em um overhead de manutenção (apesar de também facilitar testes). Preciso analizar o conjunto melhor com relação a isso.
O que vocês acharam?
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.