/// <summary>
        /// Determines the result of the attempt to update the source database with changes to the
        /// data table.
        /// </summary>
        /// <param name="changedRows">The rows from the data table that were changed.</param>
        /// <param name="numberRowsUpdatedInSource">The number of rows that were updated in the
        /// source database.</param>
        /// <param name="errorMessage">Output parameter: Any error message as a result of
        /// attempting to update the source database.</param>
        /// <param name="result">DatabaseUpdateResult indicating whether the update attempt on the
        /// source database was a success, a failure, or a partial success (some rows were
        /// successfully updated but others were not).</param>
        private DatabaseUpdateResult GetUpdateResult(DataTable changedRows,
                                                     int numberRowsUpdatedInSource, out string errorMessage)
        {
            errorMessage = string.Empty;

            if (!changedRows.HasErrors)
            {
                return(DatabaseUpdateResult.Success);
            }

            DatabaseUpdateResult result = DatabaseUpdateResult.Failed;

            DataRow[]     errorRows = changedRows.GetErrors();
            StringBuilder sb        = new StringBuilder();

            sb.AppendLine("The following errors were encountered while attempting "
                          + "to update the data source:");
            foreach (DataRow row in errorRows)
            {
                sb.AppendLine(row.RowError);
            }
            errorMessage = sb.ToString();

            // Partial Success: Some of the row updates have succeeded, some have failed.
            if (numberRowsUpdatedInSource > 0 &&
                numberRowsUpdatedInSource < changedRows.Rows.Count)
            {
                result = DatabaseUpdateResult.PartialSuccess;
            }

            return(result);
        }
        /// <summary>
        /// Accepts the changes made to the data table after the source database has been updated.
        /// </summary>
        /// <param name="dataTable">Data table that was the source of the changes made to the
        /// database.</param>
        /// <param name="changedRows">The rows from the data table that were changed.</param>
        /// <param name="databaseUpdateResult">The result of attempting to update the database
        /// with the changes from the data table.</param>
        /// <remarks>This method will only be called if the update was successful or if the
        /// rollbackAllOnError parameter of the UpdateSource method is set to false.  If there
        /// is an error and rollbackAllOnError is set to true, all the changes would have been
        /// rolled back and this method would never be called.
        ///
        /// Changes will be accepted only for those rows in the data table where the update of
        /// the source database succeeded.  Rows where the database update failed will NOT have
        /// their changes either accepted or rejected.  ie the RowStates of the rows where the
        /// database update failed will be left as either Added, Modified, or Deleted, as
        /// appropriate.  It will be the responsibility of the client code to deal with these
        /// rows.  The rows which failed will also have their HasErrors properties set.</remarks>
        private void AcceptDataTableChanges(DataTable dataTable, DataTable changedRows,
                                            DatabaseUpdateResult databaseUpdateResult)
        {
            // Merge the changed rows back into the original data table.  For any rows that
            //	were added this will update the value of any identity column to match the value
            //	in the database.
            dataTable.Merge(changedRows);

            // After the merge any identity column values will have been updated so it is okay
            //	to AcceptChanges.  This will reset the RowState of any changed rows.
            if (databaseUpdateResult == DatabaseUpdateResult.Success)
            {
                dataTable.AcceptChanges();
            }
            else if (databaseUpdateResult == DatabaseUpdateResult.PartialSuccess)
            {
                // Cannot use foreach to iterate through the dataTable.Rows collection - fails
                //	on the next iteration of the loop after a deleted row has changes accepted
                //	(ie after deleted row is removed from the collection).
                DataRow[] rows = new DataRow[dataTable.Rows.Count];
                dataTable.Rows.CopyTo(rows, 0);
                foreach (DataRow row in rows)
                {
                    if (!row.HasErrors)
                    {
                        row.AcceptChanges();
                    }
                }
            }
            // If all rows failed (DatabaseUpdateResult.Failed) would not want any of the rows
            //	accepted, so do nothing.
        }
        /// <summary>
        /// Updates the data source with changes that have been made to a DataTable, using the
        /// specified data adapter.
        /// </summary>
        /// <param name="dataTable">DataTable containing changes that are to be merged back into
        /// the original data source.</param>
        /// <param name="dataAdapter">Data adapter that will be used to update the data source.
        /// </param>
        /// <param name="rollbackAllOnError">If an error occurs this determines whether all
        /// changes will be rolled back, or only those changes with errors.</param>
        /// <param name="timeout">Timeout, in seconds, for commands used to update database.</param>
        /// <param name="errorMessage">Output Parameter: Error message resulting from attempting
        /// to update the data source.  Will be an empty string if there is no error.</param>
        /// <returns>DatabaseUpdateResult enumeration value.</returns>
        /// <remarks>If the DataAdapter does not already have its InsertCommand, UpdateCommand
        /// or DeleteCommand properties set they will be automatically created.
        ///
        /// After a record in the data source is updated successfully the matching record in the
        /// DataTable will have its AcceptChanges method called, to commit the changes.
        ///
        /// If the rollbackAllOnError parameter is set and an error occurs then all changes,
        /// including those that have succeeded, will be rolled back in the data table and in
        /// the source database.
        ///
        /// If an error occurs when the rollbackAllOnError parameter is cleared then rows where
        /// the update has succeeded will be committed in the data table and in the source
        /// database.  Data table rows where the update failed will be left in their modified
        /// states (ie, with RowState of Added, Deleted or Modified, as appropriate) for the
        /// client code to handle.  These rows will have their RowError and HasErrors properties
        /// set.
        ///
        /// This UpdateSource method will not abort if there is any error while updating a
        /// row in the data source.  It will continue to process all the changes in the data table.
        ///
        /// The data adapter will not be disposed at the end of the method.  The client code is
        /// responsiible for diposing the data adapter.
        ///
        /// REQUIREMENTS:
        /// This method will fail if these requirements are not met.
        ///
        /// 1) That the table being updated has a primary key column.
        /// 2) That the name of the data table matches the name of the table in the source
        /// database that will be updated, and the column names in the data table and database
        /// table match.
        /// </remarks>
        public DatabaseUpdateResult UpdateSource(DataTable dataTable, SqlDataAdapter dataAdapter,
                                                 bool rollbackAllOnError, int timeout, out string errorMessage)
        {
            errorMessage = string.Empty;
            lock (_lockObject)
            {
                if (_connection == null)
                {
                    errorMessage = "Connection has not been set up.";
                    return(DatabaseUpdateResult.Failed);
                }

                if (dataTable == null)
                {
                    errorMessage = "No DataTable, containing the rows to update, was supplied.";
                    return(DatabaseUpdateResult.Failed);
                }

                if (dataAdapter == null)
                {
                    errorMessage = "No DataAdapter to perform the update on the data source.";
                    return(DatabaseUpdateResult.Failed);
                }

                if (dataAdapter.SelectCommand == null)
                {
                    string columnListText = GetColumnListText(dataTable, true, false);
                    if (columnListText == null)
                    {
                        errorMessage = "Unable to get a list of the columns in the data table.  "
                                       + "Cannot create a SelectCommand for the DataAdapter.";
                        return(DatabaseUpdateResult.Failed);
                    }
                    string selectQuery = string.Format("SELECT {0} FROM {1};",
                                                       columnListText, dataTable.TableName);
                    SqlCommand selectCommand = new SqlCommand(selectQuery, _connection);
                    dataAdapter.SelectCommand = selectCommand;
                }

                int initialSelectTimeout = dataAdapter.SelectCommand.CommandTimeout;
                int initialInsertTimeout = 0;
                int initialUpdateTimeout = 0;
                int initialDeleteTimeout = 0;
                dataAdapter.SelectCommand.CommandTimeout = timeout;

                try
                {
                    // Just get changed rows from DataTable - quicker to update the data source.
                    DataTable changedRows = dataTable.GetChanges();

                    if (changedRows == null || changedRows.Rows.Count == 0)
                    {
                        return(DatabaseUpdateResult.Success);
                    }

                    // Must set this to false otherwise AcceptChanges method of data table will be
                    //	automatically called for each row as the Update method executes.  If that
                    //	happens any newly added rows with identity values will not merge back into
                    //	the data table.  Once AcceptChanges has been called the value of the
                    //	identity column will reflect the value in the database, not the value in
                    //	the original data table.  When the Merge method is called the data adapter
                    //	will not be able to match the added row back to the equivalent row in
                    //	the original data table.  The row will be added as a new row to the data
                    //	table, instead of the original row having its identity value updated.
                    dataAdapter.AcceptChangesDuringUpdate = false;

                    dataAdapter.ContinueUpdateOnError = true;

                    if (_connection.State == ConnectionState.Closed)
                    {
                        _connection.Open();
                    }

                    // Connection must be open to create a transaction.
                    SqlTransaction transaction
                        = _connection.BeginTransaction(IsolationLevel.ReadCommitted);

                    // Build commands the data adapter uses to insert, delete and update records
                    //	in the source database, if they are not already specified.
                    SetDataAdapterCommands(dataTable, dataAdapter, transaction);
                    initialInsertTimeout = dataAdapter.InsertCommand.CommandTimeout;
                    initialUpdateTimeout = dataAdapter.UpdateCommand.CommandTimeout;
                    initialDeleteTimeout = dataAdapter.DeleteCommand.CommandTimeout;
                    dataAdapter.InsertCommand.CommandTimeout = timeout;
                    dataAdapter.UpdateCommand.CommandTimeout = timeout;
                    dataAdapter.DeleteCommand.CommandTimeout = timeout;

                    int numberRowsUpdated = 0;
                    try
                    {
                        numberRowsUpdated = dataAdapter.Update(changedRows);
                    }
                    catch (ArgumentNullException ex)
                    {
                        RollbackException(transaction);
                        errorMessage = "The supplied DataTable is invalid.  Exception message: "
                                       + ex.Message;
                        return(DatabaseUpdateResult.Failed);
                    }
                    catch (InvalidOperationException ex)
                    {
                        RollbackException(transaction);
                        errorMessage = "The table in the data source is invalid.  Exception message: "
                                       + ex.Message;
                        return(DatabaseUpdateResult.Failed);
                    }
                    catch (DBConcurrencyException ex)
                    {
                        RollbackException(transaction);
                        errorMessage = "Failed when executing a command on the data source: "
                                       + "No records were affected by the command.  Exception message: "
                                       + ex.Message;
                        return(DatabaseUpdateResult.Failed);
                    }

                    DatabaseUpdateResult result
                        = GetUpdateResult(changedRows, numberRowsUpdated, out errorMessage);

                    if (rollbackAllOnError && result != DatabaseUpdateResult.Success)
                    {
                        RollbackException(transaction);
                        dataTable.RejectChanges();
                        return(DatabaseUpdateResult.Failed);
                    }

                    transaction.Commit();

                    AcceptDataTableChanges(dataTable, changedRows, result);

                    return(result);
                }
                catch (SqlException ex)
                {
                    errorMessage = ex.Message;
                    if (ex.Number == (int)SqlExceptionNumbers.TimeoutExpired ||
                        ex.Number == (int)SqlExceptionNumbers.UnableToConnect)
                    {
                        return(DatabaseUpdateResult.TimedOut);
                    }

                    return(DatabaseUpdateResult.Failed);
                }
                catch (Exception ex)
                {
                    errorMessage = ex.Message;
                    return(DatabaseUpdateResult.Failed);
                }
                finally
                {
                    // Close connection.  Cannot use "using" block because do not want to
                    //	dispose of the connection as it may be used by other code.
                    if (_connection.State != ConnectionState.Closed)
                    {
                        _connection.Close();
                    }

                    // Reset command timeouts to initial values.
                    if (dataAdapter.SelectCommand != null)
                    {
                        dataAdapter.SelectCommand.CommandTimeout = initialSelectTimeout;
                    }
                    if (dataAdapter.InsertCommand != null)
                    {
                        dataAdapter.InsertCommand.CommandTimeout = initialInsertTimeout;
                    }
                    if (dataAdapter.UpdateCommand != null)
                    {
                        dataAdapter.UpdateCommand.CommandTimeout = initialUpdateTimeout;
                    }
                    if (dataAdapter.DeleteCommand != null)
                    {
                        dataAdapter.DeleteCommand.CommandTimeout = initialDeleteTimeout;
                    }
                }
            }
        }