Nesta segunda-feira mostrei que o mapeamento objeto relacional de uma aplicação inteira pode ser feito em 60 linhas de código e deixei no ar a pergunta que dá nome a este post:

- O DBA morreu?

Vou respondê-la agora:

- Não. Não morreu. Nem vai morrer. Aliás, é o contrário.

Porque não? Porque ele não morreu se agora o banco virou um mero repositório de dados? Respondo: não morreu porque o banco não é um mero repositório de dados. Apesar de hoje termos ORMs capazes de fazer todo o mapeamento automaticamente (ou mesmo que tenhamos que fazê-lo manualmente para depois o ORM gerar um banco de dados) isso só significa que temos produtividade neste contato com o banco. É o DBA, o especialista em banco de dados, quem vai ajudar a preparar o banco para uma aplicação, o que vai muito além de gerar algumas tabelas.

O que isso significa? Devemos então ignorar o automapeamento? Ou o mapeamento fluente? Temos que abandonar os ORMs?

Não, claro que não. Na maioria da aplicações (na minha experiência – e isso varia), em torno de 50% das suas operações de entrada e saída de dados são simples CRUDs, sem muita complexidade. É comum também que estas mesmas operações sejam pouco usadas pelos usuários, já que as telas que trazem valor para o negócio não são simples CRUDs, ou o sistema sequer seria necessário. É nestas telas complexas que nosso esforço deve se concentrar. E é nas telas de CRUD simples que o automapeamento e os ORMs realmente ajudam. No resto podemos usar um mapeamento manual, ou até eliminar o ORM por completo. Se precisamos de performance, se temos que espremer cada milisegundo, por exemplo, talvez um ORM não seja a solução ideal. Nesse cenário podemos isolar as partes da aplicação que tem essas características, e não usar um ORM. No resto, usamos um ORM com mapeamento automático. Ganhamos tempo e dinheiro para quem está pagando pela aplicação.

Além disso, um DBA sempre é importante no final das iterações do projeto, já que é lá que você vai precisar testar e otimizar a performance da aplicação, além de verificar sua adequação aos diversos padrões do projeto. Então, no final da iteração, você examina o modelo gerado pelo seu ORM junto do DBA e confirma se ele quer mudar alguma coisa, acrescentar índices, mudar colunas, tabelas, etc. Depois de pegar esse feedback o banco é refatorado, junto com o mapeamento, tudo é revisado, e a iteração segue para sua conclusão. O DBA atua confirmando se os desenvolvedores, que em geral não especialistas em banco de dados, não estão cometendo nenhum absurdo. É bom que ele faça isso, já que é ele quem vai conviver com o banco de dados depois.

Depois que paramos para pensar, parece óbvio, não é? No mundo de hoje precisamos de pessoas cada vez mais especializadas. Precisamos de especialistas em algorítmos, de conhecedores de paradigmas de linguagens diferentes, de arquitetos, entre outros tipos de especialistas, e os especialistas de banco de dados claramente não poderiam ficar de fora. E eles vão ficar mais importantes ainda, conforme as tecnologias de armazenagem evoluem. O que pode acontecer é que talvez precisemos de menos DBAs para sustentar as aplicações (algo que tem acontecido), mas eu não arriscaria minha vida nisso.


Postado na(s) categoria(s) Polêmicas , Mapeadores O/R pelo giovanni bassi em 8 de julho de 2009 às 13:08 | Tags:

No .Net Architects Day eu apresentei rapidamente um exemplo iniciante de um sistema de blog durante minha palestra de DDD. Eu havia criado algumas entidades, post e comentário, e tudo funcionava.

Aí resolvi criar outra entidade: usuário. Criei a entidade, criei o controlador as views, liguei tudo, e tudo continuava funcionando. Não criei repositórios específicos, nem mapeamento, nem nada a mais, só a entidade e as partes de interface gráfica. Estava usando NHibernate para resolver o "problema" da relação objeto e dados relacionais. De repente a tabela apareceu lá, os dados salvavam corretamente, o SQL gerado era otimizado e eu estava feliz.

Tudo funcionava. Tudo automático. Muitos me perguntaram como isso foi feito. A resposta é muito simples. Além do NHibernate usei também um pouco de NHibernate Fluente e sua possibilidade de trabalhar automapeamento baseado em convenções. Minha classe de configuração do NHibernate fazia mapeamento de três formas: a básica do NH (com XML), a fluente (do NH fluente), e a automática. O código da classe de autoconfiguração é muito curto, cerca de 60 linhas:

public class Autoconfiguracao
{
    private static ISessionFactory _factory;
    public static ISessionFactory Configurar(bool gerarBanco = true)
    {
        if (_factory != null)
            return _factory;

        var config = Fluently.Configure()
            .Database(
            FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005
            .ConnectionString(c =>
              c.Is(@"Data Source=.\sqlexpress;Initial Catalog=DNAD09Blog;Integrated Security=True"))
              .ShowSql()
        );

        config.Mappings(m =>
        {
            m.HbmMappings.AddFromAssemblyOf<Autoconfiguracao>();

            m.FluentMappings
                .AddFromAssemblyOf<Autoconfiguracao>();

            m.AutoMappings.Add(
                AutoPersistenceModel.MapEntitiesFromAssemblyOf<Postagem>()
                .WithSetup(s => s.IsBaseType = (type => type == typeof(EntidadeComId)))
                .ConventionDiscovery.Setup(c =>
                {
                    c.Add(PrimaryKey.Name.Is(x => x.EntityType.Name + "_Id"));
                    c.Add(DefaultLazy.AlwaysFalse());
                    //c.Add(ForeignKey.Format((prop, tipo) => prop.Name + "_Id"));
                    c.Add(Table.Is(x => x.EntityType.Name));
                }
                )
                .Where(t =>
                    t.Namespace == "Dnad09.Blog.Dominio.Entidades"
                    && t.IsInterface == false
                    && t.IsAbstract == false
                )
            );
        }
        );

        if (gerarBanco)
        {
            config.ExposeConfiguration(cfg =>
            {
                var schemaExport = new SchemaExport(cfg);
                schemaExport.Create(false, true);
            });
        }
        _factory = config.BuildSessionFactory();
        return _factory;
    }
}

 

Na parte de configuração, que vai até "config.Mappings" (linhas 17 a 42) todas as chamadas são baseadas em Lambdas. Em seguida se o flag gerar bancos é configurado então o banco é criado, e a factory do NH é retornada. Nela estão todas as configurações do NH, e a criação de sessões do NH, que é o que realmente importa, é muito facilitada. Para trocar o banco de dados para Oracle, por exemplo, bastaria trocar a chamada "Fluently.Configure" (linhas 9 a 15) para trabalhar com este banco de dados.

O mais interessante é a possibilidade de mapear automaticamente apenas a parte que interessa. Podemos mapear algumas classes automaticamente, aceitando as convenções, e em outras utilizar mapeamento fluente ou baseado em XML. Em um cliente recentemente fizemos isso através de atributos: se uma classe tinha um atributo de automapeamento que criamos, então usávamos ela no automapeamento. Senão, teríamos que trabalhar o mapeamento de forma fluente ou com XML.

A produtividade neste tipo de cenário é gigantesca. O foco é o domínio, ou seja, o coração de um software de negócios. A persistência dos dados não é sequer uma preocupação.

Essa abordagem toda é muito legal, mas levantou questões interessantes no .Net Architects Day, que foram inclusive discutidas em outras palestras, como a dada pelo Juliano, que foi mais focada em ORM. Entre elas, a mais polêmica foi sem dúvida a seguinte:

- O DBA morreu?

Minha resposta desta pergunta nesta quarta-feira de manhã. Aguardem.


Postado na(s) categoria(s) Mapeadores O/R pelo giovanni bassi em 6 de julho de 2009 às 07:43 | Tags: ,

A Microsoft anunciou, em um post do time de ADO, que não vai mais desenvolver seu provider de ADO.Net para Oracle. A última versão é a que sai agora no .Net 4.0, e todas as classes estarão marcadas como obsoletas. No .Net vNext, todo o provider não vai mais existir no BCL.

A Oracle faz o próprio provider dela, o ODP.Net, e ele é gratuito e funciona bem. Em diversos pontos é melhor que o da Microsoft, em outros pior.

A Microsoft não anunciou, e pelo que vejo, nunca vai anunciar, um provider para Entity Framework para o Oracle.

O que isso tudo significa?

Na minha visão isso significa que a Microsoft aprendeu, já quando lançou o EF1, que não deve fazer o trabalho de outras empresas, incluindo aí o da concorrente Oracle. As empresas querem um provider para Oracle para o ADO.Net ou para o EF, que pressionem a Oracle para que entregue um.

Exigir da MS que faça um provider de Oracle para o ADO.Net é como exigir que ela faça um player de Flash para o IE, e não a Adobe, ou uma VM de Java para o Windows, e não a Sun (ou qualquer outra empresa que implemente as especificações do Java). No caso, ela até fazia uma JVM própria, mas parou com isso. Agora foi a vez do provider do Oracle.

O que eu acho disso tudo?

Acho mais do que certo. Primeiro porque não é do interesse de uma empresa ficar fazendo software para suportar aplicações de outras empresas. Segundo porque o time de ADO vai estar livre para investir mais no ADO. Terceiro porque libera a Microsoft de ter que ficar polindo um produto que pode ter bugs, e os bugs muitas vezes nem são seus, ou nem deviam estar sob responsabilidade dela para começar. Quarto porque a Microsoft dá claros sinais de fortalecimento ao ecossistema de parceiros quando faz esse tipo de coisa, já que algum parceiro poderá implementar o provider. E quinto, a Microsoft para de reinventar a roda, assumindo que o que há no mercado é bom o suficiente, e por isso ela não tem que ter a versão dela da tecnologia xyz.

(Esse quinto motivo é meio complicado. Muitas empresas não usam software se não for marcado a ferro com a marca Microsoft. É o caso de componentes de Mock, onde a Microsoft não possui uma alternativa. Muita empresa não usa porque a Microsoft não incentiva oficialmente – descontado o P&P, não fomenta o uso, já que não tem um produto para atender.)

 

Eu sei que algumas empresas vão sofrer, porque utilizam o provider da Microsoft. Então fica aqui a recomendação: se você usa, pare de usar já. Comece a migrar para o ODP.Net ou para outras alternativas, praticamente todas pagas (o lado bom é que algumas suportam EF). Quem começar a pensar nisso agora tem uns bons anos pela frente, já que a Microsoft ainda nem deve ter entrado em fase de concepção do .Net vNext.

Além do mais, código legado vai continuar funcionando e compilando. O único problema será quando você converter uma solução de .Net 4 ou inferior para o .Net vNext. Nesse caso, você vai ter que substituir o provider. Ainda que o ADO.Net abstraia todo o conceito de objetos concretos, eu pouquíssimas vezes vi alguém usando um DbCommand no lugar de um OracleCommand. Dá-lhe mão de obra para arrumar isso.

Aproveitando o problema que esse tipo de coisa vai gerar, deixo aqui algo para reflexão: se você sofrerá com isso, lembre-se que não teria tido problema se adotasse um ORM (se possível). Quem sabe não é a hora?


Postado na(s) categoria(s) Mapeadores O/R , .Net Framework pelo giovanni bassi em 24 de junho de 2009 às 01:20 | Tags:

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:

 EDMX do EF

Até aí nada de mais.

No solution explorer eu retiro o texto de "Custom Tool", vejam abaixo:

 Solution Explorer do EDMX

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.

Gerando a base a partir do modelo

Final do Wizard:

DDL gerado

Arquivo salvo, basta clicar no botão para rodar:

Arquivo SQL pronto 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=&quot;Data Source=.\sqlexpress;Initial Catalog=Blog;Integrated Security=True;MultipleActiveResultSets=True&quot;" 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:

Resultado do programa

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?


Postado na(s) categoria(s) Mapeadores O/R pelo giovanni bassi em 25 de maio de 2009 às 03:08 | Tags:

Acabo de ver que o suporte a POCOs no EF 4.0 ainda não está completo. Eu procurei muito sobre isso, e não achava em lugar nenhum. A documentação ofical do EF4 não menciona POCO em lugar nenhum também.

Vi no fórum de EF4 Beta um post onde o Noam Ben-Ami, da Microsoft, diz que o suporte a POCO ainda não está pronto no Beta 1. Lá ele aponta para dois blogs, um para entender a idéia do POCO, e outro, do MVP Matthieu Mezil, que criou um T4 para POCO para usar com o EF (que agora suporta T4 – cool huh?). AInda não testei, assim que testar eu conto pra vocês. Estou contando aqui porque sei que tinha muita gente ansiosa por ver isso, e essa falta de informação está aumentando a ansiedade.

Divirtam-se.


Postado na(s) categoria(s) Mapeadores O/R pelo giovanni bassi em 21 de maio de 2009 às 03:47 | Tags:

Eu sempre falo que não é a melhor situação do mundo trabalhar com EF em contextos de aplicações distribuidas, ou mesmo em uma onde você não guarde o contexto do EF por muito tempo, como no caso de uma aplicação web onde você guardou uma entidade do EF na sessão, mas não o contexto.

<parêntesis>
Aliás, isso é boa prática: nunca mantenha o contexto na memória muito tempo. As entidades ficam desatualizadas, e o EF faz cache, o que ocupa espaço e mata sua escalabilidade.
</parêntesis>

O Danny Simmons, do time de ADO.Net e que trabalha no EF, é sempre um cara transparente e legal de ler. E agora ele vem deixar claro que não é mesmo muito fácil usar EF nesses contextos de aplicação distribuida. Vale a pena dar uma olhada na solução que ele propõe para o problema.

Mas ele ressalva algo que eu já disse várias vezes: se sua entidade tem relacionamentos, esquece, vai dar problema. Melhor ir no banco, buscar a entidade, atualizá-la, e submeter o update. Não espere ser capaz de recriar a entidade do zero e submeter o update para uma entidade com relacionamentos. Isso vai ser resolvido na versão 2.0 do EF, que vai sair junto com o .Net 4.0, e que eu estou muito ansioso para ver. Nesses cenários ainda é melhor usar NHibernate (pelo menos até a próxima versão do EF, quando vou reavaliar isso).

Veja o blog post dele aqui.


Postado na(s) categoria(s) Arquitetura , Mapeadores O/R pelo giovanni bassi em 22 de abril de 2009 às 10:34 | Tags: ,

Todo mundo sabe que eu gosto do Entity Framework (EF), mas não morro de amores por ele.

Pois bem, estou preparando uma demonstração para o pessoal do curso de ASP.Net MVC que estou ministrando (amanhã é o segundo dia). Eles me pediram para demonstrar como usar EF com o ASP.Net MVC. E legal, funciona bem. Até você precisar atualizar uma entidade: aí você está com problemas.

O cenário do problema é o seguinte: você recebe uma entidade via Request.Form no ASP.Net, e quer reconstituir a entidade e salvá-la no banco. Se você estivesse usando NHibernate isso seria muito simples, o NH é persistence ignorant (ou seja, as entidades não conhecem o banco de dados, e não sabem se são novas entidades, se já vieram do banco, etc, etc), então uma entidade é sempre uma entidade, não importa se você a obteve via sessão (análogo ao contexto do EF) ou acabou de reconstituí-la via serialização HTTP. É uma entidade e isso é o que importa. No EF não é assim. Para atualizar uma entidade, ele assume que ela tem que ter vindo do banco. Não só isso, ela tem que ter vindo do banco pelo mesmo contexto que você está utilizando para atualizar. Isso causa um problemão. Veja esse código, que não funciona:

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Models.Categories cat)
        {
            try
            {
                _context.Attach(cat);
                _context.SaveChanges(); 
            }
            catch (Exception ex)
            {
                return View(cat);
            }
        } 

Nesse código estou usando o default model binder do ASP.Net MVC para montar a entidade para mim. Tudo maravilhoso, tudo lindo. Só que a entidade, que é uma entidade do EF, está independente do contexto. Se eu estivesse criando uma entidade nova e adicionando ela no banco, tudo seria perfeito. Aliás, é o que eu faço na ação create:

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(FormCollection collection)
        {
            try
            {
                var cat = new Models.Categories();
                UpdateModel(cat);
                _context.AddToCategories(cat);
                _context.SaveChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

Nesse caso não tem default model binder, estou usando outra abordagem, mas a idéia é a mesma, e se tivesse usando o model binder funcionaria. Não é a melhor abordagem mas funciona…

Então o problema é o seguinte: a entidade não conhece o contexto, e vice-versa. Ao chamar Attach, que é o método usado para anexar uma entidade externa ao contexto de volta para ele, você ganha de presente de páscoa antecipado uma bela exceção, que diz:

"An object with a null EntityKey value cannot be attached to an object context" 

Legal, não é? Então o problema é que eu não tenho uma EntityKey. Então vou criar uma. Você faz assim:

cat.EntityKey = _context.CreateEntityKey("Categories", cat);

Depois chama o Attach. Aí funciona. Mas não funciona tudo ainda. O contexto do EF é um cara muito controlador, e ele quer saber tudo que está acontecendo com suas entidades, então antes de salvar uma entidade ele pergunta a ela qual das suas propriedades mudaram, e adivinhem? A entidade neste caso acha que não mudou nada, fica com o status "unchanged" (sem modificações), então você precisa avisar a lesada que ela é uma entidade modificada. Aí entra um artigo bem legal do John Papa na MSDN Magazine que mostra um exemplo de como fazer isso.

Eu peguei o exemplo do Papa e dei uma melhorada. Criei um método de extensão que faz o trabalho pra mim que ficou assim:

        public static void Update(this ObjectContext context, string entitySetName, IEntityWithKey entity) 
        {
            entity.EntityKey = context.CreateEntityKey(entitySetName, entity);
            context.Attach(entity);
            var stateEntry = context.ObjectStateManager.GetObjectStateEntry(entity.EntityKey);
            var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(pn => pn.FieldType.Name);
            foreach (var propName in propertyNameList)
            {
                stateEntry.SetModifiedProperty(propName);
            }
        }

Para usar, nada mais fácil. Quase parece NHibernate de tão fácil. Chamo Update (meu método de extensão), depois sigo o padrão chamando SaveChanges:

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Models.Categories cat)
        {
            try
            {
                _context.Update("Categories", cat);
                _context.SaveChanges(); 
                return RedirectToAction("Index");
            }
            catch (Exception ex)
            {
                return View(cat);
            }
        }

Eu até podia chamar SaveChanges no método de extensão, mas quis manter o padrão de deixar o cliente do EF fazer isso. E com o Update dei uma boa idéia de atualização com entidade remota. Ficou legal.

Ah, existe uma outra opção, bem mais simples, mas que te custa uma viagem extra ao banco de dados:

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(int id, FormCollection collection)
        {
            try
            {
                var cat = (from cats in _context.Categories
                           where cats.CategoryID == id
                           select cats).FirstOrDefault();
                UpdateModel(cat);
                _context.SaveChanges(); 
                return RedirectToAction("Index");
            }
            catch (Exception ex)
            {
                return View(cat);
            }
        } 

Nessa caso você faz uma consulta ao BD, atualiza a entidade e salva. Duas viagens ao BD para fazer uma coisa. Caro! Essa é a maneira padrão que o ASP.Net MVC sugere. Apenas para constar… fuja desta última implementação!

Ah, estou postando isso porque sei que muita gente vai precisar, e isso inclui eu mesmo. Tenho certeza que cedo ou tarde vou precisar fazer isso de novo. No .Net Architects já chegamos a comentar um pouco sobre as possibilidades do EF, o André Dias até levantou a idéia do método de extensão, então fica aqui para todos.


Postado na(s) categoria(s) Mapeadores O/R pelo giovanni bassi em 3 de abril de 2009 às 23:21 | Tags: , ,

Tenho visto um monte de gente declarando a morte do Linq to SQL (L2S), especificamente: Oren Eini (a.k.a. Ayende Rahien), David Hayden (MVP), discussões no Stack Overflow, entre outros, tudo por causa de um post do time de ADO.Net onde fala-se sobre o roadmap do LINQ to Entities e LINQ to SQL. Segundo esse pessoal todo, você deve ler nas entrelinhas que o LINQ to SQL vai morrer, porque agora a Microsoft supostamente só quer saber do Entity Framework, ainda que o próprio time de ADO não tenha dito isso.

O negócio gerou tanta confusão, até porque o time de ADO não respondeu apropriadamente com um belo "Não morreu" em um post no fórum do MSDN, que um dos seus membros, o Damien Guard, respondeu em seu próprio blog que que o L2S não morreu. E também deixou claro que agora o L2S é de responsabilidade do time de Data Programmability (dono do Entity Framework) e não mais do time de C#, e que os dois frameworks vão continuar evoluindo. Mas realmente ficou claro que o EF receberá mais foco daqui para diante. Ele diz que o L2S vai continuar funcionando e ser suportado no .Net 4.0, e que o L2S vai continuar recebendo correções e evoluções para projetos mais complexos, e continuar sendo uma boa opção para projetos mais simples. O Scott Guthrie também respondeu ao Joe Rattz que o L2S vai continuar sendo suportado no futuro.

Minha visão sobre tudo isso? Não há porque a Microsoft manter dois mapeadores O/R, e o L2S é o mais fraco dos dois. Não vai morrer porque a Microsoft simplesmente não faz isso, porque reconhece o legado que existe, e não só neste caso, mas em praticamente todos (aliás, um dos grandes motivos do sucesso dela deriva disto). Mas o L2S também não é mais a menina dos olhos como era quando foi lançado. Foi uma tecnologia "ponte", se vocês me permitem a liberdade, entre datasets e um mapeador O/R mais robusto (que ainda não é a v1 do EF, mas deve vir a ser a v2 – mais sobre isso aqui no blog em breve). Diante disso, eu não sugiro grandes investimentos em L2S neste momento, pelo menos até a Microsoft colocar o dinheiro onde estão suas declarações e continuar investindo pesado no L2S. Quanto à minha opinião, eu acho que dois mapeadores, duas opções muito parecidas, oferecidas pelo mesmo fornecedor, que no caso é O fornecedor, traz mais complexidade e confusão do que benefícios.

Agora… o mais estranho é esse monte de criticos ao L2S e ao EF lamentarem sua (alegada) morte, sendo justamente eles os que mais reclamavam da tecnologia. Soa cínico até. Se não gostava, não vem falar agora que está triste porque teria morrido.


Postado na(s) categoria(s) Mapeadores O/R pelo giovanni bassi em 7 de novembro de 2008 às 12:10 | Tags: ,

Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Sou consultor independente especialista em .Net, focado em arquitetura e melhores práticas. Tenho dezenas de artigos publicados na .Net Magazine, revista da qual sou editor técnico. Ministro palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Busca

Selos

MVP

MCPD

MCSD

.Net Magazine

Abaixo ao if!

Calendário

«  março 2010  »
seteququsedo
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234
Ver detalhamento de posts no calendário

Blogs interessantes

    OPMLDownload OPML file

    Postagens recentes

    Comentários recentes

    Disclaimer / Aviso
    As opiniões colocadas neste blog são minhas e pessoais e não expressam necessariamente as opiniões de meus empregadores, pareceiros e amigos. Da mesma forma, os comentários feitos por leitores do blog não expressam a minha opinião.

    © Copyright 2010 .Net Unplugged
    Log in