Desenvolvimento - SQL
Tratando a concorrência de dados com LINQ TO SQL
Neste artigo iremos introduzir o estudo da concorrência de dados no LINQ TO SQL, algo pouco difundido e que é vital para qualquer aplicação que manipule dados.
por Gerson Afonso DiasNeste artigo iremos introduzir o estudo da concorrência de dados no LINQ TO SQL, algo pouco difundido e que é vital para qualquer aplicação que manipule dados.
Introdução:
O LINQ To SQL tem uma arquitetura que permite trabalharmos de maneira desconectada aos bancos de dados. Após uma consulta qualquer, a conexão é encerrada, o aplicativo edita seus dados e envia novamente para o banco. Mas o que acontecerá se neste meio tempo os dados forem modificados por outro usuário?
A Concorrência Otimista:
O LINQ trabalha com concorrência otimista. Neste tipo de concorrência infere-se que determinada mudança na base de dados pouco irá influenciar em outras operações que estão sendo feitas com estes dados. Neste cenário, a cada vez que chamamos o método SubmitChanges() da classe DataContext, o código SQL criado pelo LINQ irá verificar se os dados que estão na base atualmente são os mesmos que foram obtidos inicialmente. Veja o código SQL criado pelo LINQ em um update:
UPDATE [dbo].[Cliente]
SET [Cidade] = @p2
WHERE ([Nome] = @p0) AND ([Cidade] = @p1)
Note que os parâmetros @p0 e @p1 carregam os valores que foram obtidos quando o usuário requisitou estes dados. Caso estes valores estejam diferentes, o comando UPDATE falhará e será disparado na aplicação a exceção ChangeConflictException() que possibilitará ao desenvolvedor escolher a melhor prática para o tratamento em cada caso.
Conflict Mode:
Quando executamos a instrução SubmitChanges() podemos escolher como este comando irá se comportar em caso de conflitos de dados, através do enum ConflictMode:
ConflictMode.ContinueOnConflict: Todas as alterações serão feitas e os conflitos serão colocados em uma coleção no fim do processo;
ConflictMode.FailOnFirstConflict: Ao detectar que um registro apresentou conflito, a exceção será lançada.
É de se observar que estas opções trariam o inconveniente de todos os registros que não deram erros seriam modificados, causando, talvez, inconsistência na base de dados. O controle de transações é tema para outro artigo, porém, por padrão o LINQ executa seus comandos em transações, e, em caso de erro, há o RollBack. Portanto, nenhum dado seria modificado.
Resolução de Conflitos:
O enum RefreshMode nos permite definir como os objetos serão atualizados na base de dados:
RefreshMode.KeepChanges: Caso tenha havido modificações na base de dados estas alterações serão mantidas e a sua descartada
RefreshMode.KeepCurrentValues: As alterações na base feitas por outros usuários serão descartadas e a sua alteração prevalecerá
RefreshMode.OverwriteCurrentValues: Sobrescreve suas alterações com os valores presentes no banco de dados.
Coleção ChangeConflicts:
A coleção ChangeConflicts guarda objetos do tipo ObjectChangesConflict e representa todas as tabelas onde houve conflitos. Cada instancia de ObjectChangesConflict guardas as colunas onde houve conflitos e, por sua vez, a propriedade MemberConflicts guarda as linhas que apresentaram o conflito. Veja um exemplo:
try
{
//db é um DataContext
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict tabela in db.ChangeConflicts)
{
foreach (MemberChangeConflict coluna in tabela.MemberConflicts)
{
var valorOriginal = coluna.OriginalValue;
var valorAtual = coluna.CurrentValue;
var valorNaBase = coluna.DatabaseValue;
}
}
}
Com esta estrutura podemos analisar como resolver os conflitos em nossas bases:
try
{
//db é um DataContext
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
db.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
}
O método ResolveAll() irá definir o mesmo tipo de RefreshMode para todos os conflitos apresentados.
try
{
//db é um DataContext
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict tabela in db.ChangeConflicts)
{
//Posso definir um tipo de tratamento para cada tabela
MetaTable table = db.Mapping.GetTable(tabela.Object.GetType());
if (table.TableName == "Clientes")
{
tabela.Resolve(RefreshMode.OverwriteCurrentValues);
}
else
{
tabela.Resolve(RefreshMode.KeepCurrentValues);
}
}
}
Neste exemplo estamos entrando na coleção ObjectChangeConflict, que representa uma tabela, para dar diferentes tratamentos para as entidades.
try
{
//db é um DataContext
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict tabela in db.ChangeConflicts)
{
foreach (MemberChangeConflict coluna in tabela.MemberConflicts)
{
//Posso dar tratamentos diferentes para cada coluna
coluna.Resolve(RefreshMode.KeepChanges);
}
}
}
Entrando mais a fundo no objeto ObjectChangeConflict podemos dar tratamentos diferentes para cada coluna da tabela.
Conclusão:
O LINQ nos trás toda a possibilidade de tratar problemas de concorrência de dados de forma simples, e com o nível de detalhamento que for necessário para a situação. Para completar o tema é necessário ainda entrarmos no tema Transações e Concorrência Pessimista, o faremos em um próximo artigo.
Abraços!
Bibliografia:
· Revista Programar, 16ª edição – Concorrência em LINQ para SQL
(http://www.revista-programar.info/)
· Optimistic Concurrency Overview (LINQ to SQL) -
- Diferenças entre SEQUENCES x IDENTITY no Microsoft SQL Server 2012SQL
- Utilizando FILETABLE no SQL Server 2012SQL Server
- Utilizando SEQUENCES no Microsoft SQL Server 2012SQL
- Exportação de dados do SQL Server para o Oracle com assistente de importação do SQL ServerSQL
- Tunning Index com o DTASQL