Pergunta

Estou usando o "técnica de esboço" para atualizar meus POCOs (usados ​​em um contexto desanexado, ASP.NET MVC).

Este é o código que tenho atualmente no meu controlador (que funciona):

[HttpPost]
public ActionResult Edit(Review review)
{
   Review originalReview = _userContentService.FindById(review.PostId) as Review;
   var ctx = _unitOfWork as MySqlServerObjectContext;
   ctx.ApplyCurrentValues("MyEntities.Posts", review);
   _unitOfWork.Commit();
   // ..snip - MVC stuff..
}

Como você pode ver, há cheiro de código em todos os lugares.:)

Alguns pontos:

  1. Eu uso injeção de dependência (baseada em interface) para basicamente tudo
  2. Eu uso o padrão Unidade de Trabalho para abstrair ObjectContext e fornecer persistência em vários repositórios
  3. Atualmente meu IUnitOfWork interface tem apenas 1 método: void Commit();
  4. Controlador tem IUserContentService e IUnitOfWork injetar por DI
  5. IUserContentService chamadas Find em Repositórios, que usam o ObjectContext.

Estas são duas coisas que não gosto no meu código acima:

  1. Eu não quero lançar o IUnitOfWork como MySqlServerObjectContext.
  2. Eu não quero que o Controlador tenha que se preocupar com ApplyCurrentValues

Basicamente, quero que meu código fique assim:

[HttpPost]
public ActionResult Edit(Review review)
{
   _userContentService.Update(review);
   _unitOfWork.Commit();
   // ..snip - MVC stuff..
}

Alguma idéia de como posso fazer isso?(ou algo semelhante).

Já tenho inteligência para descobrir o nome do conjunto de entidades com base no tipo (combinação de genéricos, pluralização), então não se preocupe muito com isso.

Mas estou me perguntando qual é o melhor lugar para colocar ApplyCurrentValues é?Não parece apropriado colocá-lo no IUnitOfWork interface, pois esta é uma preocupação de persistência (EF).Pela mesma razão não pertence ao Serviço.Se eu colocar no meu MySqlServerObjectContext class (faz sentido), de onde eu chamaria isso, já que nada tem acesso direto a essa classe - ela é injetada via DI quando algo solicita IUnitOfWork.

Alguma ideia?

EDITAR

Tenho uma solução abaixo usando a técnica stub, mas o problema é que se eu tivesse recuperado a entidade que estou atualizando antes, ela lançaria uma exceção, informando que já existe uma entidade com essa chave.

O que faz sentido, embora eu não tenha certeza de como resolver isso?

Preciso "verificar se a entidade já está anexada, caso contrário, anexe-a?"

Algum especialista em EF4 pode ajudar?

EDITAR

Deixa pra lá - encontrei a solução, veja a resposta abaixo.

Foi útil?

Solução

Descobri - não foi fácil, então tentarei explicar da melhor maneira possível.(para quem se importa)

Código relevante do controlador:

// _userContentService is IUserContentService
_userContentService.Update(review);

Então, meu controlador chama um método chamado Update sobre IUserContentService, passando pelo fortemente tipado Review objeto.

Código relevante do serviço de conteúdo do usuário

public void Update(Post post)
{
   // _userContentRepository is IPostRepository
   _userContentRepository.UpdateModel(post);
}

Então, meu serviço chama um método chamado UpdateModel sobre IPostRepository, passando pelo fortemente tipado Review objeto.

Agora, aqui está a parte complicada.

na verdade eu tenho nenhum repositório específico.eu tenho um repositório genérico chamado GenericRepository<T> : IRepository<T>, que trata todos os diferentes repositórios.

Então, quando algo solicita um IPostRepository (o que meu serviço estava fazendo), DI daria uma GenericRepository<Post>.

Mas agora, eu dou uma PostRepository:

public class PostRepository : GenericRepository<Post>, IPostRepository
{
   public void UpdateModel(Post post)
   {
      var originalPost = CurrentEntitySet.SingleOrDefault(p => p.PostId == post.PostId);
      Context.ApplyCurrentValues(GetEntityName<Post>(), post);
   }
}

E porque a classe deriva de Repositório Genérico, ele herda toda a lógica do repositório principal (Localizar, Adicionar, etc).

No começo, tentei colocar isso Modelo de atualização código no Repositório Genérico classe em si (e então eu não precisaria desse repositório específico), mas o problema é que a lógica para recuperar a entidade existente é baseada em uma chave de entidade específica, que o GenericRepository<T> não saberia.

Mas o resultado final é o costura está escondido nas profundezas da camada de dados, e acabo com um controlador realmente limpo.

EDITAR

Esta "técnica de esboço" também funciona:

public void UpdateModel(Post post)
{
   var stub = new Review {PostId = post.PostId};
   CurrentEntitySet.Attach(stub);
   Context.ApplyCurrentValues(GetEntityName<Post>(), post);
}

Mas o problema é porque Publicar é abstrato, não consigo instanciar e, portanto, teria que verificar o tipo de Post e criar stubs para cada um tipo derivado.Não é realmente uma opção.

EDITAR 2 (ÚLTIMA VEZ)

Ok, fiz a "técnica de stub" funcionar com classes abstratas, então agora o problema de simultaneidade está resolvido.

Eu adicionei um parâmetro de tipo genérico ao meu Modelo de atualização método e o especial restrição new().

Implementação:

public void UpdateModel<T>(T post) where T : Post, new()
{
   var stub = new T { PostId = post.PostId };
   CurrentEntitySet.Attach(stub);
   Context.ApplyCurrentValues(GetEntityName<Post>, post);
}

Interface:

void UpdateModel<T>(T post) where T : Post, new();

Isso evita que eu tenha que descobrir o tipo de T manualmente, evita problemas de simultaneidade e também evita uma viagem extra ao banco de dados.

Muito legal.

EDIT 3 (pensei que a última vez fosse a última vez)

A "técnica de stub" acima funciona, mas se eu recuperar o objeto de antemão, ela lança uma exceção informando que uma entidade com essa chave já existe no OSM.

Alguém pode aconselhar como lidar com isso?

EDITAR 4 (OK - é isso!)

Encontrei a solução, graças a esta resposta do SO: É possível verificar se um objeto já está anexado a um contexto de dados no Entity Framework?

Tentei "verificar se a entidade está anexada" usando o seguinte código:

ObjectStateEntry entry;
CurrentContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);

Mas sempre voltou nulo, mesmo quando explorei o OSM pude ver minha entidade lá com a mesma chave.

Mas este código funciona:

CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), entity), out entry)

Talvez por estar usando Pure POCO's, o OSM teve dificuldade em descobrir a chave da entidade, quem sabe.

Ah, e mais uma coisa que adicionei - para não precisar adicionar um repositório específico para cada entidade, criei um atributo chamado "[Chave da Entidade]" (atributo de propriedade pública).

Todos os POCOs devem ter 1 propriedade pública decorada com esse atributo, ou lançarei uma exceção no meu módulo de repositório.

Portanto, meu repositório genérico procura essa propriedade para criar/configurar o stub.

Sim - ele usa reflexão, mas é uma reflexão inteligente (baseada em atributos) e já estou usando reflexão para pluralização de nomes de conjuntos de entidades de T.

De qualquer forma, problema resolvido - tudo funcionando bem agora!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top