private async Task CommitEditsInternal(DbConnection connection, Func <Task> successHandler, Func <Exception, Task> errorHandler) { try { // @TODO: Add support for transactional commits // Trust the RowEdit to sort itself appropriately var editOperations = EditCache.Values.ToList(); editOperations.Sort(); foreach (var editOperation in editOperations) { // Get the command from the edit operation and execute it using (DbCommand editCommand = editOperation.GetCommand(connection)) using (DbDataReader reader = await editCommand.ExecuteReaderAsync()) { // Apply the changes of the command to the result set await editOperation.ApplyChanges(reader); } // If we succeeded in applying the changes, then remove this from the cache // @TODO: Prevent edit sessions from being modified while a commit is in progress RowEditBase re; EditCache.TryRemove(editOperation.RowId, out re); } await successHandler(); } catch (Exception e) { await errorHandler(e); } }
/// <summary> /// Creates a new row update and adds it to the update cache /// </summary> /// <exception cref="InvalidOperationException">If inserting into cache fails</exception> /// <returns>The internal ID of the newly created row</returns> public EditCreateRowResult CreateRow() { ThrowIfNotInitialized(); // Create a new row ID (atomically, since this could be accesses concurrently) long newRowId = NextRowId++; // Create a new row create update and add to the update cache RowCreate newRow = new RowCreate(newRowId, associatedResultSet, objectMetadata); if (!EditCache.TryAdd(newRowId, newRow)) { // Revert the next row ID NextRowId--; throw new InvalidOperationException(SR.EditDataFailedAddRow); } EditCreateRowResult output = new EditCreateRowResult { NewRowId = newRow.RowId, DefaultValues = newRow.DefaultValues }; return(output); }
/// <summary> /// Removes a pending row update from the update cache. /// </summary> /// <exception cref="ArgumentOutOfRangeException"> /// If a pending row update with the given row ID does not exist. /// </exception> /// <param name="rowId">The internal ID of the row to reset</param> public void RevertRow(long rowId) { ThrowIfNotInitialized(); // Attempt to remove the row with the given ID RowEditBase removedEdit; if (!EditCache.TryRemove(rowId, out removedEdit)) { throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataUpdateNotPending); } }
/// <summary> /// Removes the edit from the edit cache if the row is no longer dirty /// </summary> /// <param name="rowId">ID of the update to cleanup</param> /// <param name="editCellResult">Result with row dirty flag</param> private void CleanupEditIfRowClean(long rowId, EditCellResult editCellResult) { // If the row is still dirty, don't do anything if (editCellResult.IsRowDirty) { return; } // Make an attempt to remove the clean row edit. If this fails, it'll be handled on commit attempt. RowEditBase removedRow; EditCache.TryRemove(rowId, out removedRow); }
/// <summary> /// Retrieves a subset of rows with the pending updates applied. If more rows than exist /// are requested, only the rows that exist will be returned. /// </summary> /// <param name="startIndex">Index to start returning rows from</param> /// <param name="rowCount">The number of rows to return.</param> /// <returns>An array of rows with pending edits applied</returns> public async Task <EditRow[]> GetRows(long startIndex, int rowCount) { ThrowIfNotInitialized(); // Get the cached rows from the result set ResultSetSubset cachedRows = startIndex < associatedResultSet.RowCount ? await associatedResultSet.GetSubset(startIndex, rowCount) : new ResultSetSubset { RowCount = 0, Rows = new DbCellValue[][] { } }; // Convert the rows into EditRows and apply the changes we have List <EditRow> editRows = new List <EditRow>(); for (int i = 0; i < cachedRows.RowCount; i++) { long rowId = i + startIndex; RowEditBase edr; if (EditCache.TryGetValue(rowId, out edr)) { // Ask the edit object to generate an edit row editRows.Add(edr.GetEditRow(cachedRows.Rows[i])); } else { // Package up the existing row into a clean edit row EditRow er = new EditRow { Id = rowId, Cells = cachedRows.Rows[i].Select(cell => new EditCell(cell, false)).ToArray(), State = EditRow.EditRowState.Clean }; editRows.Add(er); } } // If the requested range of rows was at the end of the original cell set and we have // added new rows, we need to reflect those changes if (rowCount > cachedRows.RowCount) { long endIndex = startIndex + cachedRows.RowCount; var newRows = EditCache.Where(edit => edit.Key >= endIndex).Take(rowCount - cachedRows.RowCount); editRows.AddRange(newRows.Select(newRow => newRow.Value.GetEditRow(null))); } return(editRows.ToArray()); }
/// <summary> /// Creates a new row update and adds it to the update cache /// </summary> /// <exception cref="InvalidOperationException">If inserting into cache fails</exception> /// <returns>The internal ID of the newly created row</returns> public EditCreateRowResult CreateRow() { ThrowIfNotInitialized(); // Create a new row ID (atomically, since this could be accesses concurrently) long newRowId = NextRowId++; // Create a new row create update and add to the update cache RowCreate newRow = new RowCreate(newRowId, associatedResultSet, objectMetadata); if (!EditCache.TryAdd(newRowId, newRow)) { // Revert the next row ID NextRowId--; throw new InvalidOperationException(SR.EditDataFailedAddRow); } // Set the default values of the row if we know them string[] defaultValues = new string[objectMetadata.Columns.Length]; for (int i = 0; i < objectMetadata.Columns.Length; i++) { EditColumnMetadata col = objectMetadata.Columns[i]; // If the column is calculated, return the calculated placeholder as the display value if (col.IsCalculated.HasTrue()) { defaultValues[i] = SR.EditDataComputedColumnPlaceholder; } else { if (col.DefaultValue != null) { newRow.SetCell(i, col.DefaultValue); } defaultValues[i] = col.DefaultValue; } } EditCreateRowResult output = new EditCreateRowResult { NewRowId = newRowId, DefaultValues = defaultValues }; return(output); }
/// <summary> /// Creates a delete row update and adds it to the update cache /// </summary> /// <exception cref="InvalidOperationException"> /// If row requested to delete already has a pending change in the cache /// </exception> /// <param name="rowId">The internal ID of the row to delete</param> public void DeleteRow(long rowId) { ThrowIfNotInitialized(); // Sanity check the row ID if (rowId >= NextRowId || rowId < 0) { throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataRowOutOfRange); } // Create a new row delete update and add to cache RowDelete deleteRow = new RowDelete(rowId, associatedResultSet, objectMetadata); if (!EditCache.TryAdd(rowId, deleteRow)) { throw new InvalidOperationException(SR.EditDataUpdatePending); } }
/// <summary> /// Reverts a cell in a pending edit /// </summary> /// <param name="rowId">Internal ID of the row to have its edits reverted</param> /// <param name="columnId">Ordinal ID of the column to revert</param> /// <returns>String version of the old value for the cell</returns> public EditRevertCellResult RevertCell(long rowId, int columnId) { ThrowIfNotInitialized(); // Attempt to get the row edit with the given ID RowEditBase pendingEdit; if (!EditCache.TryGetValue(rowId, out pendingEdit)) { throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataUpdateNotPending); } // Update the row EditRevertCellResult revertResult = pendingEdit.RevertCell(columnId); CleanupEditIfRowClean(rowId, revertResult); // Have the edit base revert the cell return(revertResult); }
/// <summary> /// Performs an update to a specific cell in a row. If the row has not already been /// initialized with a record in the update cache, one is created. /// </summary> /// <exception cref="InvalidOperationException">If adding a new update row fails</exception> /// <exception cref="ArgumentOutOfRangeException"> /// If the row that is requested to be edited is beyond the rows in the results and the /// rows that are being added. /// </exception> /// <param name="rowId">The internal ID of the row to edit</param> /// <param name="columnId">The ordinal of the column to edit in the row</param> /// <param name="newValue">The new string value of the cell to update</param> public EditUpdateCellResult UpdateCell(long rowId, int columnId, string newValue) { ThrowIfNotInitialized(); // Sanity check to make sure that the row ID is in the range of possible values if (rowId >= NextRowId || rowId < 0) { throw new ArgumentOutOfRangeException(nameof(rowId), SR.EditDataRowOutOfRange); } // Attempt to get the row that is being edited, create a new update object if one // doesn't exist // NOTE: This *must* be done as a lambda. RowUpdate creation requires that the row // exist in the result set. We only want a new RowUpdate to be created if the edit // doesn't already exist in the cache RowEditBase editRow = EditCache.GetOrAdd(rowId, key => new RowUpdate(rowId, associatedResultSet, objectMetadata)); // Update the row EditUpdateCellResult result = editRow.SetCell(columnId, newValue); CleanupEditIfRowClean(rowId, result); return(result); }