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: , ,

Comentários


abril 4. 2009 09:28
Márcio Fábio Althmann
Olá primeiro parabéns pelo MVP Smile
Eu estou utilizando o EF em um projeto de teste aqui e tive esse mesmo problema, por sorte achei o mesmo artigo que disse no artigo e resolvi o problema, será que teremos uma modificação para facilitar as coisas na próxima versão do EF Laughing

Abraços.

http://www.sharpcode.com.br/http://www.sharpcode.com.br/


abril 5. 2009 17:42
Rafael Izidoro
parabéns pelo MVP!!!

http://raf4.net/http://raf4.net/


abril 6. 2009 14:07
Paulo Quicoli
É por essa e outras que fico com meu querido NHibernate Smile

http://www.pauloquicoli.spaces.live.com/http://www.pauloquicoli.spaces.live.com/


Brazil Alessandro
abril 6. 2009 19:18
Alessandro
Giggio,
você sabe se a MS tem alguma previsão para que as próximas versões do EF sejam 'persistence ignorance' para trabalharmos com POCOS da mesma forma que se faz com NH?

[]´s

obs: não esquece do nosso forum não hem... (forum.giggio.net).. postei 2 perguntinhas lá.. hehehe... valeu


no site


abril 7. 2009 09:42
Felipe Fujiy
Um problema que sempre tenho com o EF é pra atualizar o modelo.

Se por exemplo apago uma entidade e quero adicionar de novo não consigo, tenho que editar o XML apagando todas as referencias dela(que ficaram de lixo) pra depois ela aparecer na lista de Add novamente.

Ou quando mudo os campos de uma tabela, nem sempre atualiza corretamente. Nisso acho que o NHibernate se sai melhor, tem mais controle.


http://blog.fujiy.net/http://blog.fujiy.net/


Brazil Lano
abril 14. 2009 12:48
Lano
Olá Giovanni, mais um parabéns aí pelo MVP.
Depois de alguns anos desenvolvendo minha própria lógica de acesso a dados, estou no meu primeiro projeto com EF. A transição foi fácil, porém estou emperrado no seguinte problema:

Sempre que tenho que inserir dados numa tabela que possua relacionamento com outra, é me retornado o seguinte erro:

"Um objeto de entidade não pode ser referenciado por várias instâncias de IEntityChangeTracker.”

Exemplo: Tenho a entidade Permissoes, que se relaciona com a Entidade Usuarios.

Se eu tentar fazer a anexação do objeto, é disparado outra excessão:

"anexação não é uma operação válida quando o objeto de origem associado a essa extremidade relacionada está em estado adicionado, excluído ou desanexado. Objetos carregados usando a opção de mesclagem NoTracking sempre estão desanexados. "

Já estou a três dias pesquisando como resolver, o meu prazo no projeto tá correndo, e eu já estou pra partir para uma solução alternativa ao EF.
Preciso de uma força e desde já agradeço a disponibilidade

no site


abril 14. 2009 15:59
Giovanni Bassi
Lano, esse é o tipo de coisa que faz a gente perder tempo com EF...
Sugestão: se você estiver no começo do projeto, muda para NHibernate. Pelo menos até a MS lançar a versão 2 do EF, que deve estar melhor.

http://unplugged.giggio.net/http://unplugged.giggio.net/


abril 23. 2009 08:14
Prodis
Infelizmente o EF foi uma grande decepção. Nós avaliamos a utilização dele para alguns projetos e desistimos.
A ênfase no modelo de dados ao invés do modelo de objetos é frustante.
Sendo assim, vamos aguardar a versão 2.0 do EF para ver se a Microsoft acorda para o mundo do desenvolvimento ágil.
NHibernate é a melhor opção para quem quer POCOs e se livrar de entidades recheadas de código de infra-estrutura.

http://prodis.pro.br/http://prodis.pro.br/


Brazil Rodrigo
maio 13. 2009 11:47
Rodrigo
Olá pessoal, alguém sabe como eu resolvo a questão da chave primária composta, pois iremos construir o mapeamento em um banco antigo mosso ?

no site


Brazil Rodrigo
maio 13. 2009 11:48
Rodrigo
continuando...

  como ficaria o CRUD?

no site


julho 23. 2009 12:10
Patek Phillipe
Tried to autotranslate you site not understand the writing any hope deutsch version?

http://luxurywatchstore.biz/http://luxurywatchstore.biz/

Comentar


(Vai mostrar seu Gravatar)

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading



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