/// <summary> /// Execute a CRUD operation /// </summary> /// <param name="operation">The operation to execute (as Azure TableBatchOperation)</param> public async void ExecuteNonQuery(TableBatchOperation operation) { // TBOs must be paged by partition key, and no more than a 100 items per batch page if (operation.Count > 0) { TableBatchOperation batchPage = new(); // all entities in a batch must have the same partition key: foreach (IEnumerable <TableOperation> operations in operation.GroupBy(o => o.Entity.PartitionKey)) { // order elements in a partition by row key so that we reduce tablescans foreach (TableOperation op in operations.OrderBy(o => o.Entity.RowKey)) { batchPage.Add(op); if (batchPage.Count == 100) { await Connection.ExecuteBatchAsync(batchPage); batchPage.Clear(); } } } // get the remaining if (batchPage.Count > 0) { await Connection.ExecuteBatchAsync(batchPage); } } }
/// <summary> /// Execute a DML operation /// </summary> /// <typeparam name="T">Class type of the business object</typeparam> /// <param name="batchOperation">TableBatchOperation to execute</param> public void ExecuteNonQuery <T>(TableBatchOperation batchOperation) where T : class { if (batchOperation.Count > 0) { TableBatchOperation batchPage = new TableBatchOperation(); // all entities in a batch must have the same partition key: foreach (IEnumerable <TableOperation> operations in batchOperation.GroupBy(o => o.Entity.PartitionKey)) { // order elements in a partition by row key so that we reduce tablescans foreach (TableOperation operation in operations.OrderBy(o => o.Entity.RowKey)) { batchPage.Add(operation); if (batchPage.Count == 100) { _currentTableReference.ExecuteBatch(batchPage); batchPage.Clear(); } } } // get the remaining if (batchPage.Count > 0) { _currentTableReference.ExecuteBatch(batchPage); } } }
/// <summary> /// Queue a new operation /// </summary> /// <param name="batch">TableBatchOperation to queue</param> /// <param name="tableName">Name of the table to run it against</param> public void Add(TableBatchOperation batch, string tableName) { if (_isDraining) { // no items can be added during a drain throw new Exception("Cannot queue items during a drain."); } if ((batch == null) || (batch.Count == 0)) { throw new ArgumentNullException("Empty batch"); } if (string.IsNullOrWhiteSpace(tableName)) { throw new ArgumentNullException("tableName"); } // examine the batch, split into unique operations of a max of 100 elements each foreach (IGrouping <string, TableOperation> operationByPartitionKeys in batch.GroupBy(b => b.Entity.PartitionKey)) { string partitionKey = operationByPartitionKeys.Key; Dictionary <TableOperationType, TableBatchOperationWrapper> operationsByType = new Dictionary <TableOperationType, TableBatchOperationWrapper>() { { TableOperationType.Delete, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) }, { TableOperationType.Insert, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) }, { TableOperationType.InsertOrMerge, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) }, { TableOperationType.InsertOrReplace, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) }, { TableOperationType.Merge, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) }, { TableOperationType.Replace, new TableBatchOperationWrapper(new TableBatchOperation(), tableName) } }; foreach (TableOperation operation in operationByPartitionKeys.OrderBy(o => o.Entity.RowKey).ThenBy(o => o.OperationType)) { if ((operation.OperationType == TableOperationType.Invalid) || (operation.OperationType == TableOperationType.Retrieve)) { throw new ArgumentOutOfRangeException("Unsupported operation for queue!"); } operationsByType[operation.OperationType].Batch.Add(operation); ItemAdded?.Invoke(tableName, Enum.GetName(typeof(TableOperationType), operation.OperationType) ?? "Unknown", partitionKey, operation.Entity.RowKey); if (operationsByType[operation.OperationType].Batch.Count == 100) { _queue.Enqueue(operationsByType[operation.OperationType]); operationsByType[operation.OperationType] = new TableBatchOperationWrapper(new TableBatchOperation(), tableName); } } // flush each op/group to the queue, because next iteration of the loop changes the partition key foreach (TableOperationType type in operationsByType.Keys) { if (operationsByType[type].Batch.Count > 0) { _queue.Enqueue(operationsByType[type]); } } } }