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 Dias



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.

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) -

http://msdn.microsoft.com/en-us/library/bb399373.aspx

Gerson Afonso Dias

Gerson Afonso Dias