public override int Execute(IEnumerable <ModificationCommandBatch> commandBatches, IRelationalConnection connection) { // Convert the list of batches to a list to prevent it from being re-generated each time that we iterate over the enumerator. var batchesList = commandBatches.ToList(); var tracer = TracerProviderExtension.GetTracer(); using var span = tracer.StartActiveSpan(TracerProviderExtension.SPAN_NAME_SAVECHANGES); try { base.Execute(batchesList, connection); foreach (var batch in batchesList) { if (batch is SpannerModificationCommandBatch spannerModificationCommandBatch) { spannerModificationCommandBatch.PropagateResults(); } } span.SetStatus(Status.Ok); } catch (Exception ex) { span.SetStatus(Status.Error.WithDescription(ex.Message)); throw; } finally { span.End(); } return(SpannerCount(batchesList)); }
public override async Task <int> ExecuteAsync(IEnumerable <ModificationCommandBatch> commandBatches, IRelationalConnection connection, CancellationToken cancellationToken = default) { // Convert the list of batches to a list to prevent it from being re-generated each time that we iterate over the enumerator. var batchesList = commandBatches.ToList(); var tracer = TracerProviderExtension.GetTracer(); using var span = tracer.StartActiveSpan(TracerProviderExtension.SPAN_NAME_SAVECHANGES); try { await base.ExecuteAsync(batchesList, connection, cancellationToken); // Results that need to be propagated after an update are executed after the batch has been saved. // This ensures that when implict transactions are being used the updated value is fetched after the // transaction has been committed. This makes it possible to use mutations for implicit transactions // and still automatically propagate computed columns. foreach (var batch in batchesList) { if (batch is SpannerModificationCommandBatch spannerModificationCommandBatch) { await spannerModificationCommandBatch.PropagateResultsAsync(cancellationToken); } } span.SetStatus(Status.Ok); } catch (Exception ex) { span.SetStatus(Status.Error.WithDescription(ex.Message)); throw; } finally { span.End(); } return(SpannerCount(batchesList)); }
/// <summary> /// Commits the database transaction asynchronously. /// </summary> /// <param name="cancellationToken">A cancellation token used for this task.</param> /// <returns>Returns the UTC timestamp when the data was written to the database.</returns> public async Task <DateTime> CommitAsync(CancellationToken cancellationToken = default) { var tracer = TracerProviderExtension.GetTracer(); using var span = tracer.StartActiveSpan(TracerProviderExtension.SPAN_NAME_COMMIT); while (true) { try { _commitTimestamp = await SpannerTransaction.CommitAsync(cancellationToken).ConfigureAwait(false); // Propagate the commit timestamp to all columns that were automatically updated during this transaction. // Note that this transaction could both be a manual transaction started by the application, as well as // an implicit transaction started by Entity Framework. foreach (var modificationCommand in _commitTimestampModificationCommands) { foreach (var columnModification in modificationCommand.ColumnModifications) { if (columnModification is SpannerPendingCommitTimestampColumnModification pendingCommitTimestampColumnModification) { var property = pendingCommitTimestampColumnModification.Property.PropertyInfo; if (property != null) { var entry = pendingCommitTimestampColumnModification.Entry; var originalState = entry.EntityState; var entity = entry.ToEntityEntry().Entity; property.SetValue(entity, _commitTimestamp); entry.EntityState = originalState; } } } } span.SetStatus(OpenTelemetry.Trace.Status.Ok); span.End(); return((DateTime)_commitTimestamp); } catch (SpannerException e) when(e.ErrorCode == ErrorCode.Aborted) { span.SetAttribute(TracerProviderExtension.ATTRIBUTE_NAME_RETRYING, e.Message); await RetryAsync(e, cancellationToken).ConfigureAwait(false); } catch (Exception e) { span.SetStatus(OpenTelemetry.Trace.Status.Error.WithDescription(e.Message)); span.End(); throw; } } }