public async Task ExplicitAndImplicitTransactionIsRetried(bool disableInternalRetries, bool useExplicitTransaction) { // Abort the next statement that is executed on the mock server. _fixture.SpannerMock.AbortNextStatement(); using var db = new MockServerSampleDbContextUsingMutations(ConnectionString); IDbContextTransaction transaction = null; if (useExplicitTransaction) { // Note that using explicit transactions in combination with mutations has a couple of side-effects: // 1. Read-your-writes does not work. // 2. Computed columns are not propagated to the current context. transaction = await db.Database.BeginTransactionAsync(); if (disableInternalRetries) { transaction.DisableInternalRetries(); } } db.Venues.Add(new Venues { Code = "C1", Name = "Concert Hall", }); // We can only disable internal retries when using explicit transactions. Otherwise internal retries // are always used. if (disableInternalRetries && useExplicitTransaction) { await db.SaveChangesAsync(); var e = await Assert.ThrowsAsync <SpannerException>(() => transaction.CommitAsync()); Assert.Equal(ErrorCode.Aborted, e.ErrorCode); } else { var updateCount = await db.SaveChangesAsync(); Assert.Equal(1L, updateCount); if (useExplicitTransaction) { await transaction.CommitAsync(); } Assert.Empty(_fixture.SpannerMock.Requests.Where(request => request is ExecuteBatchDmlRequest)); Assert.Collection( _fixture.SpannerMock.Requests.Where(request => request is CommitRequest).Select(request => (CommitRequest)request), // The commit request is sent twice to the server, as the statement is aborted during the first attempt. request => { Assert.Single(request.Mutations); Assert.Equal("Venues", request.Mutations.First().Insert.Table); Assert.NotNull(request.TransactionId); }, request => { Assert.Single(request.Mutations); Assert.Equal("Venues", request.Mutations.First().Insert.Table); Assert.NotNull(request.TransactionId); } ); } }