public static SaveResult save(PreTransactionAction preTransactionAction, preConcDelegate pcDelegate, PreConcArguments pcArguments, preSaveDelegate psDelegate, PreSaveArguments psArguments, PostSaveAction postSaveAction, bool activateOpcaoCancelar) { Concorrencia conc = new Concorrencia(); IDbTransaction tran = null; SaveResult successfulSave = SaveResult.successful; GisaDataSetHelper.HoldOpen ho = null; long startTicks = 0; bool savedWithoutDeadlock = false; // Variavel que indica qual a tabela que está a ser gravada (tem o debugging como fim) string currentTable = string.Empty; DataSet gBackup = null; ArrayList changedRowsArrayList = null; // Variável que vai manter a informação das linhas "added" cujos Ids são gerados automaticamente // antes e depois de serem gravadas (antes de essas linhas serem gravadas os seus IDs são // negativos e depois são-lhe atribuidos valores positivos Hashtable trackNewIds = new Hashtable(); try { if (preTransactionAction != null) { preTransactionAction.ExecuteAction(); if (preTransactionAction.args.cancelAction) { MessageBox.Show(preTransactionAction.args.message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Warning); GisaDataSetHelper.GetInstance().RejectChanges(); return SaveResult.nothingToSave; } } } catch (Exception e) { Trace.WriteLine(e.ToString()); return SaveResult.unsuccessful; } finally { } while (! savedWithoutDeadlock) { try { if (pcDelegate != null) { ho = new GisaDataSetHelper.HoldOpen(GisaDataSetHelper.GetConnection()); tran = ho.Connection.BeginTransaction(GisaDataSetHelper.GetTransactionIsolationLevel()); pcArguments.tran = tran; gBackup = new DataSet(); gBackup.CaseSensitive = true; pcArguments.gisaBackup = gBackup; pcArguments.continueSave = true; pcDelegate(pcArguments); //no caso de se pretender apagar uma relacao hierarquica ha a possibilidade de //não ser necessário prosseguir com a gravação dos dados por razoes de logica //(ver o delegate verifyIfCanDeleteRH) if (! pcArguments.continueSave) { tran.Commit(); savedWithoutDeadlock = true; successfulSave = SaveResult.unsuccessful; break; } } //verifica logo à cabeça se houve de facto alguma alteração ao dataset if (! (GisaDataSetHelper.GetInstance().HasChanges())) { return SaveResult.nothingToSave; } //obter um arraylist com estruturas que indicam para cada tabela quais as linhas que foram alteradas changedRowsArrayList = getCurrentDatasetChanges(conc); if (changedRowsArrayList == null) { return SaveResult.nothingToSave; } while (true) { if (ho == null) { ho = new GisaDataSetHelper.HoldOpen(GisaDataSetHelper.GetConnection()); } if (tran == null) { tran = ho.Connection.BeginTransaction(GisaDataSetHelper.GetTransactionIsolationLevel()); } //manter 2 datasets com linhas provenientes da bd //isto para permitir a detecção de novos e possiveis conflitos qd o utilizador perante 1 problema de concorrencia pretende manter as suas alterações //a estratégia passa por verificar se houve alterações no 1º (e principal) dataset com as linhas da bd if (originalRowsDB1 == null) { //guardar as linhas neste dataset //FIXME: changedRowsArrayList esta a chegar com tabelas repetidas (!) originalRowsDB1 = conc.getOriginalRowsDB(changedRowsArrayList, tran); } else if (originalRowsDB1 != null && originalRowsDB2 == null) { //o dataset principal (originalRowsDB1) ja esta preenchido e o utilizador pretende manter as suas alterações //o dataset principal (originalRowsDB1) ja esta preenchido e o utilizador pretende manter as suas alterações // as linhas da BD vao parar ao 2º dataset originalRowsDB2 = conc.getOriginalRowsDB(changedRowsArrayList, tran); if (originalRowsDB2 == null) { originalRowsDB1 = null; } } else if (originalRowsDB1 != null && originalRowsDB2 != null) { //os 2 datasets estao preenchidos (o que quer dizer que estamos perante a 2ª situação de concorrencia consecutiva (no mínimo) na mesma mudança de contexto) //nesta situação o dataset principal passa a ter as alterações contidas no 2º para que neste último passem a constar os novos dados provenientes da BD //se não existirem novas linhas, quer dizer que as alterações feitas em memória coincidem com aquelas existentes na BD //verificar se existem novas linhas if (! (conc.getOriginalRowsDB(changedRowsArrayList, tran) == null)) { originalRowsDB1 = originalRowsDB2.Copy(); originalRowsDB2 = conc.getOriginalRowsDB(changedRowsArrayList, tran); } else { originalRowsDB1 = null; originalRowsDB2 = null; } } //se não existirem conflitos de concorrencia if (! ((originalRowsDB1 != null && originalRowsDB2 == null && conc.temLinhas(originalRowsDB1)) || (originalRowsDB1 != null && originalRowsDB2 != null && conc.wasModified(originalRowsDB1, originalRowsDB2, changedRowsArrayList)))) { //não há concorrência quando o originalRowsDB1 não tem linhas e originalRowsDB2 está vazio ou quando os dois datasets são diferentes break; } else { //caso existam conflitos, é apresentado ao utilizador uma mensagem a indicar os pontos de conflito e de que forma os pretende resolver //ToDo: verificar a necessidade de fazer rollback tran.Rollback(); ho.Dispose(); ho = null; tran = null; frm.DetalhesUser = Concorrencia.StrConcorrenciaUser.ToString(); frm.DetalhesBD = Concorrencia.StrConcorrenciaBD.ToString(); frm.btnCancel.Enabled = activateOpcaoCancelar; switch (frm.ShowDialog()) { case DialogResult.Yes: //mantem-se dentro do ciclo de forma a voltar a verificar se existe concorrencia ou nao //ao mesmo tempo que este tratamento de conflitos e executado outro utilizador pode já ter feito novas alterações //é necessario limpar as variaveis que mantem as mensagens sobre a concorrencia para o caso de neste situação ainda existirem novos situações de conflito Concorrencia.StrConcorrenciaBD.Remove(0, Concorrencia.StrConcorrenciaBD.Length); Concorrencia.StrConcorrenciaUser.Remove(0, Concorrencia.StrConcorrenciaUser.Length); break; case DialogResult.No: //gravar em memoria as linhas obtidas da base de dados e sai do metodo if (originalRowsDB2 != null) { //se no dataset originalRowsDB2 existirem linhas, logo é este que vai ser gravado conc.MergeDatasets(originalRowsDB2, GisaDataSetHelper.GetInstance(), DataSetTablesOrderedA); } else { conc.MergeDatasets(originalRowsDB1, GisaDataSetHelper.GetInstance(), DataSetTablesOrderedA); } conc.ClearRowsChangedToModified(); cleanConcurrencyVariables(); return successfulSave; case DialogResult.Cancel: cleanConcurrencyVariables(); successfulSave = SaveResult.cancel; return successfulSave; } } } startTicks = DateTime.Now.Ticks; if (Concorrencia.StrConcorrenciaLinhasNaoGravadas.Length > 0) { MessageBox.Show("A informação referente aos campos seguintes não pode ser gravada " + Environment.NewLine + "por ter sido, entretanto, eliminada: " + System.Environment.NewLine + Concorrencia.StrConcorrenciaLinhasNaoGravadas.ToString(), "Gravação de dados."); } // Chamar qualquer tarefa de "pré-gravação" que possa ter sido definida if (psDelegate != null) { psArguments.tran = tran; psDelegate(psArguments); } // forma de manter a informação referente à actualização dos Ids das linhas quando // estas são adicionadas na base de dados, isto é, saber qual o valor (negativo) // do ID antes da linha ser gravada e o valor (positivo) atribuído pela base de dados // depois do save trackNewIds.Clear(); conc.startTrackingIdsAddedRows(GisaDataSetHelper.GetInstance(), changedRowsArrayList, ref trackNewIds); // garantir que filhos das linhas a eliminar que não estão carregados ficam também eles eliminados //ToDo: em getChildRowsFromDB utilizar dataset de trabalho para obter as relações entre tabelas. Passar ao metodo o origRowsDB para que as linhas obtidas lhe sejam directamente adicionadas ArrayList rows = new ArrayList(); ArrayList afectedTables = new ArrayList(); GisaDataSetHelper.ManageDatasetConstraints(false); foreach (Concorrencia.changedRows changedRow in changedRowsArrayList) { currentTable = changedRow.tab; if (changedRow.rowsDel.Count > 0) { cascadeManageChildDeletedRows(changedRow.tab, changedRow.rowsDel, conc, tran); } // alterar o estado das Rowstate.Deleted para Rowstate.Unchanged e passa-las a isDeleted=True DataRow delRow = null; if (changedRow.rowsDel.Count > 0) { while (changedRow.rowsDel.Count > 0) { if (changedRow.tab.Equals("ControloAutDataDeDescricao") || changedRow.tab.Equals("FRDBaseDataDeDescricao")) { delRow = (DataRow)(changedRow.rowsDel[0]); delRow.RejectChanges(); changedRow.rowsDel.RemoveAt(0); } else { delRow = (DataRow)(changedRow.rowsDel[0]); delRow.RejectChanges(); delRow["isDeleted"] = 1; changedRow.rowsMod.Add(delRow); changedRow.rowsDel.RemoveAt(0); } } } rows.Clear(); rows.AddRange(changedRow.rowsAdd); rows.AddRange(changedRow.rowsMod); PersistencyHelperRule.Current.saveRows(GisaDataSetHelper.GetInstance().Tables[changedRow.tab], (DataRow[])(rows.ToArray(typeof(DataRow))), tran); } if (postSaveAction != null) { postSaveAction.args.tran = tran; postSaveAction.ExecuteAction(); } conc.ClearRowsChangedToModified(); GisaDataSetHelper.ManageDatasetConstraints(true); tran.Commit(); savedWithoutDeadlock = true; Debug.WriteLine("Save: " + new TimeSpan(DateTime.Now.Ticks - startTicks).ToString()); Trace.WriteLine("Save completed."); } catch (Exception ex) { Trace.WriteLine(ex); //GisaDataSetHelper.GetInstance().RejectChanges() if (DBAbstractDataLayer.DataAccessRules.ExceptionHelper.isDeadlockException(ex)) { Trace.WriteLine(">>> Deadlock (save)."); conc.prepareRollBackDataSet(ref trackNewIds); tran.Rollback(); tran = null; if (conc.mGisaBackup != null && gBackup != null) { conc.MergeDatasets(gBackup, conc.gisabackup, DataSetTablesOrderedA); } if (conc.mGisaBackup != null) { conc.MergeDatasets(conc.mGisaBackup, GisaDataSetHelper.GetInstance(), DataSetTablesOrderedA, trackNewIds); } conc.deleteUnusedRows(GisaDataSetHelper.GetInstance(), ref trackNewIds); gBackup = null; } else if (DBAbstractDataLayer.DataAccessRules.ExceptionHelper.isTimeoutException(ex)) { Trace.WriteLine(">>> Timeout (save)."); conc.prepareRollBackDataSet(ref trackNewIds); tran = null; if (conc.mGisaBackup != null && gBackup != null) { conc.MergeDatasets(gBackup, conc.gisabackup, DataSetTablesOrderedA); } if (conc.mGisaBackup != null) { conc.MergeDatasets(conc.mGisaBackup, GisaDataSetHelper.GetInstance(), DataSetTablesOrderedA, trackNewIds); } conc.deleteUnusedRows(GisaDataSetHelper.GetInstance(), ref trackNewIds); gBackup = null; } else { #if DEBUG Trace.WriteLine(currentTable); #endif Trace.WriteLine("Save failed."); Trace.WriteLine(ex); Debug.Assert(false, "Save failed."); if (tran != null) { tran.Rollback(); } tran = null; MessageBox.Show("Ocorreu um erro inesperado durante a gravação " + Environment.NewLine + "da informação e por esse motivo a aplicação irá " + Environment.NewLine + "fechar. Por favor contacte o administrador do sistema", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error); throw; } } finally { if (tran != null) { tran.Dispose(); } cleanConcurrencyVariables(); if (savedWithoutDeadlock) { conc.mGisaBackup = null; } if (ho != null) { ho.Dispose(); ho = null; } } } return successfulSave; }