/*=========================*/ #endregion #region Internal Methods /*=========================*/ /// <summary> /// Fills the item with data retrieved from the data source using the select command. /// </summary> protected void Bind() { // Cannot bind when the table is not managed inside the item if (_owner != null) { throw new InvalidOperationException("Cannot independently bind an item that is part of a collection"); } if (DataManager.ProxyMode) { ProxyRequestAction action = ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()); action.OnComplete = delegate() { DataTable table = ProxyClient.Result[action].GetData <DataTable>("_table"); _table = table; _row = table.Rows[0]; // TODO: deal with any field backups and shit }; } else { // Initialize the table if (_table == null) { _table = new DataTable("_table"); } else { _table.Rows.Clear(); } SqlDataAdapter adapter = DataManager.CreateAdapter(this); ConnectionKey ckey = null; try { ckey = DataManager.Current.OpenConnection(this); // Fill the table adapter.Fill(this._table); } finally { DataManager.Current.CloseConnection(ckey); } if (_table.Rows.Count < 1) { throw new DataException("Binding operation did not return data"); } _row = _table.Rows[0]; if (ProxyServer.InProgress) { ProxyServer.Current.Result[ProxyServer.Current.CurrentAction].AddData("_table", _table); } } }
/// <summary> /// Invoked when the item needs to be deleted from the data source in independent mode. /// </summary> /// /// <remarks> /// <para> /// The base implementation of this method uses a DataAdapter with the item's DeleteCommand /// in order to delete the record from the database. After deleting, the item /// and it's fields remain fully accessible from memory only and so can be resaved. /// </para> /// <para> /// Derived classes can override this method to add additional /// checks or operations before and after the delete, or to change the delete process altogether. /// </para> /// </remarks> protected virtual void OnDelete() { // Cannot update when the table is not managed inside the item if (_owner != null) { throw new InvalidOperationException("Cannot independently delete an item that is part of a collection." + " Use the Remove or RemoveAt methods to delete the item from the collection and then use Save to save the data to the database."); } if (this.DataState == DataRowState.Deleted || this.DataState == DataRowState.Detached ) { throw new InvalidOperationException("Cannot delete an item with the DataState of " + this.DataState.ToString()); } if (DataManager.ProxyMode) { ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()).OnComplete = delegate() { _row.SetAdded(); }; } else { // Preserve the items so we can re-insert them (deleting clears the row) object[] dataBeforeDelete = new object[_row.ItemArray.Length]; _row.ItemArray.CopyTo(dataBeforeDelete, 0); // Delete the row _row.Delete(); // Create an adapter SqlDataAdapter adapter = DataManager.CreateAdapter(this); ConnectionKey ckey = null; using (adapter) { try { ckey = DataManager.Current.OpenConnection(this); adapter.Update(_table); } finally { DataManager.Current.CloseConnection(ckey); } } // Re-add the row so it can be accessed again (only when not part of a collection) if (_owner == null) { _table.Rows.Add(_row); _row.ItemArray = dataBeforeDelete; } } }
/*=========================*/ #endregion #region Internal Methods /*=========================*/ /// <summary> /// Populates the collection with DataItem objects by binding to the data source. /// </summary> /// /// <remarks> /// Calling this method clears the inner table before re-populating the list, which means /// items contained in the collection before the binding call are disassociated from the collection /// (their <c>DataState</c> changes to <c>DataRowState.Detached</c>). Changes to detached items /// will not be saved when the collection is saved (unless they are re-added). /// </remarks> protected void Bind() { // Clear the table _table.Rows.Clear(); // Clear items _innerList.Clear(); if (_hash != null) { _hash.Clear(); } if (!DataManager.ProxyMode) { // Attach an event handler to the table so we can create DataItems for each row added DataRowChangeEventHandler addedHandler = new DataRowChangeEventHandler(_table_RowChanged); _table.RowChanged += addedHandler; SqlDataAdapter adapter = DataManager.CreateAdapter(this); ConnectionKey ckey = null; try { ckey = DataManager.Current.OpenConnection(this); // Fill the table adapter.Fill(this._table); } finally { DataManager.Current.CloseConnection(ckey); } // Detach the event so it doesn't get raised in future changes _table.RowChanged -= addedHandler; if (ProxyServer.InProgress) { ProxyServer.Current.Result[ProxyServer.Current.CurrentAction].AddData("_table", _table); } } else { ProxyRequestAction action = ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()); action.OnComplete = delegate() { DataTable table = ProxyClient.Result[action].GetData <DataTable>("_table"); _table = table; // Create new items for each row using the existing handler foreach (DataRow row in _table.Rows) { _table_RowChanged(_table, new DataRowChangeEventArgs(row, DataRowAction.Add)); } }; } }
/// <summary> /// Invoked when the item needs to be saved to the data source in independent mode. /// </summary> /// /// <remarks> /// The base implementation of this method uses a DataAdapter with the item's UpdateCommand /// in order to save it's fields to the database. Derived classes can override this method to add additional /// checks or operations before and after the save, or to change the saving method altogether. /// </remarks> protected virtual void OnSave() { // Cannot update when the table is not managed inside the item if (_owner != null) { throw new InvalidOperationException("Cannot independently save an item that is part of a collection"); } // Rather than just allowing the adapter to skip the row, throw and exception to notify the consumer if (this.DataState != DataRowState.Added && this.DataState != DataRowState.Modified && this.DataState != DataRowState.Unchanged ) { throw new InvalidOperationException("Cannot save an item with the DataState of " + this.DataState.ToString()); } if (DataManager.ProxyMode) { ProxyRequestAction action = ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()); action.OnComplete = delegate() { DataTable table = ProxyClient.Result[action].GetData <DataTable>("_table"); _table = table; _row = table.Rows[0]; // TODO: deal with any field backups and shit }; } else { // Create an adapter SqlDataAdapter adapter = DataManager.CreateAdapter(this); ConnectionKey ckey = null; using (adapter) { try { ckey = DataManager.Current.OpenConnection(this); adapter.Update(_table); } finally { DataManager.Current.CloseConnection(ckey); } } if (ProxyServer.InProgress) { ProxyServer.Current.Result[ProxyServer.Current.CurrentAction].AddData("_table", _table); } } }
/// <summary> /// Ignores any keys and forces the connection to close. /// </summary> public void ForceCloseConnection() { _connectionKey = null; if (_connection != null && _connection.State == ConnectionState.Open) { if (_transaction != null) { CommitTransaction(); } _connection.Close(); } _transaction = null; _connection = null; }
/// <summary> /// Manages connection open based on sequence. /// </summary> /// /// <param name="dataObj">If not null, associates the connection with the object's data commands.</param> /// /// <returns> /// When opening a new connection, an object that can be used to close the connection. /// Null if the connection is already open. /// </returns> public ConnectionKey OpenConnection(IDataBoundObject dataObj, SqlConnection externalConnection) { //Check for a valid connection string if (externalConnection == null && (_connectionString == "" || _connectionString == null)) { return(null); } // This is the key used to close the connection. ConnectionKey key = null; // If a connection doesn't exist, open a new one // (second condition is just in case, should never happen because we nullify the connection when closing it) if (_connection == null || _connection.State == ConnectionState.Closed) { if (externalConnection != null) { _connection = externalConnection; } else { _connection = new SqlConnection(_connectionString); key = new ConnectionKey(); } } // Associate the data bound object's commands with the current connection if (dataObj != null) { SetActiveConnection(dataObj); } if (key != null) { _connectionKey = key; _connection.Open(); } return(key); }
/// <summary> /// Deletes the rows in the table, updates the database and then re-adds the rows. /// </summary> /// <remarks> /// Used to disassociate a collection from a parent object but allowing the values to be reused. /// (used in <see cref="PT.Data.QueryKeywordGroupCollection"/>). /// </remarks> protected void DeleteFromDB() { if (DataManager.ProxyMode) { throw new NotSupportedException("DeleteFromDB currently not supported in proxy mode"); } // Delete all rows foreach (DataRow row in _table.Rows) { row.Delete(); } // Update to DB SqlDataAdapter adapter = DataManager.CreateAdapter(this); ConnectionKey ckey = null; using (adapter) { try { ckey = DataManager.Current.OpenConnection(this); adapter.Update(_table); } finally { DataManager.Current.CloseConnection(ckey); } } // Restore all item rows that are still in the list (not items that were removed foreach (DataItem item in this._innerList) { _table.Rows.Add(item.Row); item.RestoreFieldValues(); } }
/// <summary> /// Closes the open connection if <pararef name="key">key</pararef> matches the active key. /// </summary> /// /// <param name="key"> /// The key returned by <see cref="OpenConnection"/>. If key is null or does not match the /// current valid key, the method does not do anything. /// </param> /// public void CloseConnection(ConnectionKey key) { if ( key != null && _connectionKey == key && _connection != null ) { if (_connection.State == ConnectionState.Open) { if (_transaction != null) { CommitTransaction(); } _connection.Close(); } _transaction = null; _connection = null; _connectionKey = null; } }
/// <summary> /// Manages connection open based on sequence. /// </summary> /// /// <param name="dataObj">If not null, associates the connection with the object's data commands.</param> /// /// <returns> /// When opening a new connection, an object that can be used to close the connection. /// Null if the connection is already open. /// </returns> public ConnectionKey OpenConnection(SqlConnection externalConnection) { //Check for a valid connection string if (externalConnection == null && (_connectionString == "" || _connectionString == null)) { return(null); } // This is the key used to close the connection. ConnectionKey key = null; // If a connection doesn't exist, open a new one // (second condition is just in case, should never happen because we nullify the connection when closing it) if (_connection == null || _connection.State == ConnectionState.Closed) { if (externalConnection != null) { _connection = externalConnection; } else { _connection = new SqlConnection(_connectionString); key = new ConnectionKey(); } } if (key != null) { _connectionKey = key; _connection.Open(); } return(key); }
/// <summary> /// Runs the stand-alone SELECT command to retrieve fields that were not present when the item was /// created (in either independent or collection-owned mode). /// </summary> public virtual void RetrieveMissingFields() { if (DataManager.ProxyMode) { ProxyRequestAction action = ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()); action.OnComplete = delegate() { DataTable table = ProxyClient.Result[action].GetData <DataTable>("_table"); _table = table; _missingFields.Clear(); // When no owner just swap tables, if (_owner == null) { _row = table.Rows[0]; } else { _retrievedFieldRow = table.Rows[0]; } }; } else { if (SelectCommand == null) { throw new InvalidOperationException("There is no defined SELECT command so missing fields cannot be retrieved."); } // Only do something if there are missing fields to complete if (_missingFields.Count > 0 && _selectCmd != null) { ConnectionKey key = null; // Gets parameter values foreach (SqlParameter param in SelectCommand.Parameters) { if (param.SourceColumn != null && param.SourceColumn != String.Empty) { // Map to the row or to the backup hash according to state param.Value = _row.RowState == DataRowState.Deleted || _row.RowState == DataRowState.Detached ? _backupFieldValues[param.SourceColumn] : _row[param.SourceColumn]; } else { param.Value = DBNull.Value; } } try { key = DataManager.Current.OpenConnection(this); SqlDataReader reader = SelectCommand.ExecuteReader(); using (reader) { if (!reader.HasRows) { throw new DataItemInconsistencyException("Error retrieving item data - item no longer exists"); } else { // Read the first row reader.Read(); DataRow rowToUse; // When object is part of the collection, initialize the internal table to hold retrieved fields if (_owner != null && _table == null) { _table = new DataTable("_table"); OnInitializeTable(); _retrievedFieldRow = _table.NewRow(); _table.Rows.Add(_retrievedFieldRow); rowToUse = _retrievedFieldRow; } else { rowToUse = _row; } foreach (string missingField in _missingFields) { try { if ((_row.RowState == DataRowState.Deleted || _row.RowState == DataRowState.Detached) && rowToUse == _row) { // Update the backup hash when state is inaccessible _backupFieldValues[missingField] = reader[missingField]; } else { rowToUse[missingField] = reader[missingField]; } } catch (Exception ex) { // If a field assigment failed, the select command throw new DataItemInconsistencyException("Error setting item data - retrieved data does not match item's internal structure", ex); } } // Done getting the missing fields, so clear them _missingFields.Clear(); } } } finally { DataManager.Current.CloseConnection(key); } } if (ProxyServer.InProgress) { ProxyServer.Current.Result[ProxyServer.Current.CurrentAction].AddData("_table", _table); } } }
/// <summary> /// Handles saving the collection's items to the data source. /// </summary> /// /// <remarks> /// The base implementation of this method uses a DataAdapter with the collection's associated commands /// in order to save the contained records to the database. Derived classes can override this method to add additional /// checks or operations before and after the save, or to change the saving method altogether. /// </remarks> protected virtual void OnSave() { // Don't perform the save if there are no changes if (_table.GetChanges() == null) { return; } if (!DataManager.ProxyMode) { // Create an adapter SqlDataAdapter adapter = DataManager.CreateAdapter(this); // Vars for proxy operations DataTable updatedRowsTable = null; SqlRowUpdatedEventHandler updatedHander = null; if (ProxyServer.InProgress) { updatedRowsTable = _table.Clone(); updatedHander = delegate(object sender, SqlRowUpdatedEventArgs e) { if (e.StatementType != StatementType.Insert && e.StatementType != StatementType.Update) { return; } // Store and row that has been inserted or updated updatedRowsTable.ImportRow(e.Row); }; adapter.RowUpdated += updatedHander; } ConnectionKey ckey = null; using (adapter) { try { ckey = DataManager.Current.OpenConnection(this); adapter.Update(_table); } finally { DataManager.Current.CloseConnection(ckey); } } // Wrap up the proxy operation if (ProxyServer.InProgress) { adapter.RowUpdated -= updatedHander; ProxyServer.Current.Result[ProxyServer.Current.CurrentAction].AddData("updated", updatedRowsTable); } } else { ProxyRequestAction action = ProxyClient.Request.AddAction(this, MethodInfo.GetCurrentMethod()); action.OnComplete = delegate() { DataTable updated = ProxyClient.Result[action].GetData <DataTable>("updated"); bool usingInnerID = updated.Columns.Contains(Const.InnerIDColumn); if (!usingInnerID) { throw new Exception("not using inner ID!!!"); } // Go over each row and find matching rows for (int i = 0; i < updated.Rows.Count; i++) { bool merged = false; DataRow updatedRow = updated.Rows[i]; object testID = usingInnerID ? updatedRow[Const.InnerIDColumn] : null; if (testID is int) { int innerID = (int)testID; DataRow[] rs = _table.Select(String.Format("{0} = {1}", Const.InnerIDColumn, innerID)); if (rs.Length > 0) { // If a row with a matching innerID is found, merge the values and accept changes DataRow row = rs[0]; row.ItemArray = updatedRow.ItemArray; row.AcceptChanges(); merged = true; } } // TODO: make sure we shouldn't be clearing the table before adding unidentified rows if (!merged) { // Insert the new row at the same index DataRow newRow = _table.NewRow(); newRow.ItemArray = updatedRow.ItemArray; _table.Rows.InsertAt(newRow, i); DataItem newItem = NewItem(newRow); _innerList.Insert(i, newItem); if (_hash != null) { _hash.Add(GetPrimaryKeyValue(newItem), newItem); } } } }; } }