Em um artigo anterior, apresentei vários problemas que você pode encontrar ao usar o LINQ to SQL para operações de atualização. Na verdade, este não é um problema que encontrei sozinho. Quando procurei respostas na Internet, descobri que muitas pessoas publicaram artigos semelhantes sobre este assunto. Mas o que não me agrada é que, embora tenham levantado o problema, não realizaram uma análise detalhada. Eles apenas deram soluções (como adicionar colunas RowVersion, remover associações, etc.), mas não explicaram por que deveriam fazer isso. . Esta também é a intenção original ao escrever o artigo anterior. Espero descobrir a solução para o problema passo a passo através da análise do código-fonte LINQ to SQL. Este artigo discutirá esses métodos um por um.
Opção 1: Reatribuição Na estrutura de código aberto Ezsocio de TerryLee, Anytao, Ding Xue e outros, a reatribuição é adotada em alguns lugares. Dentro do método Update, obtenha as entidades do banco de dados com base na chave primária e, a seguir, atribua valores às suas propriedades, um por um, com as entidades nos parâmetros.
public void UpdateProfile(Perfil p)
{
usando (RepositoryContext db = novo RepositoryContext())
{
var perfil = db.GetTable<Profile>().First<Profile>(u => u.ID == p.ID);
perfil.Aniversário = p.Aniversário;
perfil.Gênero = p.Gênero;
perfil.Cidade natal = p.Cidade natal;
perfil.MSN = p.MSN;
perfil.NickName = p.NickName;
perfil.PhoneNumber = p.PhoneNumber;
perfil.QQ = p.QQ;
perfil.Estado = p.Estado;
perfil.TrueName = p.TrueName;
perfil.StateRefreshTime = p.StateRefreshTime;
perfil.Avatar = p.Avatar;
perfil.Website = p.Website;
db.SubmitChanges();
}
}
O irmão Yang Guo também forneceu um método de reflexão para esta solução, a fim de obter cópia automática de valores de atributos.
Mas eu pessoalmente acho que este é um esquema que evita a realidade e evita a realidade. Ele não usa a API fornecida pelo LINQ to SQL para operações de atualização, mas adota uma estratégia indireta. Na verdade, isso é um compromisso. Será porque o método Attach "não é fácil de usar", então não o usamos? hehe.
Opção 2: Desabilitar o rastreamento de objetos Nesse sentido, lea propôs que a atualização correta pode ser obtida definindo a propriedade ObjectTrackingEnabled de DataContext como falsa.
Produto público GetProduct (int id)
{
NorthwindDataContext db = new NorthwindDataContext();
db.ObjectTrackingEnabled = falso;
retornar db.Products.SingleOrDefault(p => p.ProductID == id);
}
Nenhuma outra alteração de código.
Por que ele pode ser atualizado normalmente após desativar o rastreamento de objetos? Vamos encontrar a resposta no código-fonte.
bool público ObjectTrackingEnabled
{
pegar
{
this.CheckDispose();
retorne this.objectTrackingEnabled;
}
definir
{
this.CheckDispose();
se (this.Services.HasCachedObjects)
{
lançar System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = valor;
if (!this.objectTrackingEnabled)
{
this.deferredLoadingEnabled = falso;
}
this.services.ResetServices();
}
}
Acontece que quando ObjectTrackingEnabled é definido como falso, DeferredLoadingEnabled será definido como falso ao mesmo tempo. Desta forma, ao executar a consulta, nenhum dado que necessite de consulta atrasada será carregado para a entidade, portanto nenhuma exceção será lançada durante o Attach (veja a análise no artigo anterior).
No MSDN também obtemos as seguintes informações úteis: Definir a propriedade ObjectTrackingEnable como false pode melhorar o desempenho da recuperação porque reduz o número de itens a serem rastreados. Este é um recurso muito tentador.
Porém, ao desabilitar o rastreamento de objetos, atenção especial deve ser dada a dois pontos: (1) Deve ser desabilitado antes de executar a consulta. (2) Após serem desabilitados, os métodos Attach e SubmitChanges não poderão mais ser chamados. Caso contrário, uma exceção será lançada.
Opção 3: Remover a associação Um método idiota foi introduzido no artigo anterior, que consiste em definir manualmente a Categoria associada ao Produto como nula no método GetProduct. Podemos extrair esta parte do código e colocá-la em um método Detach. Como este Detach é um método de entidade, classes parciais podem ser usadas:
classe parcial pública Produto
{
público vazio Detach()
{
this._Category = default(EntityRef<Categoria>);
}
}
Categoria de classe parcial pública
{
público vazio Detach()
{
foreach (var produto neste.Produtos)
{
produto.Detach();
}
}
}Mas este método de definir Detach para cada entidade é muito complicado. À medida que o número de entidades aumenta, os relacionamentos tornam-se cada vez mais complexos e é fácil aparecerem atributos ausentes. Zhang Yi propôs um método muito elegante para abstrair essa lógica usando reflexão:
desanexação de vazio privado (entidade TEntity)
{
foreach (FieldInfo fi em entidade.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fi.FieldType.ToString().Contains("EntityRef"))
{
var valor = fi.GetValue(entidade);
if (valor! = nulo)
{
fi.SetValue(entidade,nulo);
}
}
if (fi.FieldType.ToString().Contains("EntitySet"))
{
var valor = fi.GetValue(entidade);
if (valor! = nulo)
{
MethodInfo mi = value.GetType().GetMethod("Limpar");
if (mi! = nulo)
{
mi.Invoke (valor, nulo);
}
fi.SetValue(entidade, valor);
}
}
}
}
Algumas pessoas também pensam que os eventos PropertyChanging e PropertyChanged devem ser definidos como nulos durante Detach, mas a ideia geral é a mesma.
Opção 4: Use delegação Este é o método fornecido por ZC29 nos comentários do meu último artigo. Pessoalmente, acho que vale a pena aprender.
public void UpdateProductWithDelegate(Expressão<Func<Produto, bool>> predicado, Ação<Produto> ação)
{
NorthwindDataContext db = new NorthwindDataContext();
var produto = db.Products.SingleOrDefault(predicado);
ação(produto);
db.SubmitChanges();
}
//Código do cliente
Repositório ProductRepository = new ProductRepository();
repositório.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.ProductName = "Alterado";
});
Use expressões Lambda para incorporar a lógica GetProduct em UpdateProduct e use delegados para adiar a execução da lógica de atualização. Isso coloca habilmente a pesquisa e a atualização em um DataContext, ignorando assim o Attach. No entanto, a API deste método é um pouco complexa e requer níveis muito elevados de programadores clientes. Além disso, a lógica Get deve ser executada novamente em Update. Embora a perda de desempenho seja mínima, sempre parece dar às pessoas a sensação de que não está DRY o suficiente.
Opção 5: Use a instrução UPDATE No código-fonte do Ezsocio, encontrei o método RepositoryBase.UpdateEntity. A emenda das instruções SQL é feita dentro do método e apenas as colunas alteradas serão atualizadas. Como o ITable não é mais usado aqui e é necessário suporte completo ao framework, nenhum comentário adicional será feito. Consulte o código-fonte do Ezsocio para obter detalhes.
Resumo Este artigo lista várias soluções que encontrei na Internet nos últimos dias. Todas têm prós e contras. Qual delas é melhor ou pior depende da opinião de diferentes pessoas. No próximo artigo, compararei o desempenho desses métodos para encontrar a solução ideal.