Skip to content

Commit

Permalink
Allow users to always use transactions with SaveChanges
Browse files Browse the repository at this point in the history
Obsoletes AutoTransactionsEnabled, replacing it with a new 3-valued
enum.

Closes dotnet#27574
  • Loading branch information
roji committed Aug 15, 2022
1 parent 35d6fa1 commit a2a08df
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 136 deletions.
12 changes: 8 additions & 4 deletions src/EFCore.Relational/Update/Internal/BatchExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ public virtual int Execute(IEnumerable<ModificationCommandBatch> commandBatches,
try
{
var transactionEnlistManager = connection as ITransactionEnlistmentManager;
var autoTransactionBehavior = CurrentContext.Context.Database.AutoTransactionBehavior;
if (transaction == null
&& transactionEnlistManager?.EnlistedTransaction is null
&& transactionEnlistManager?.CurrentAmbientTransaction is null
&& CurrentContext.Context.Database.AutoTransactionsEnabled
// Don't start a transaction if we have a single batch which doesn't require a transaction (single command), for perf.
&& (batch.AreMoreBatchesExpected || batch.RequiresTransaction))
&& ((autoTransactionBehavior == AutoTransactionBehavior.WhenNeeded
&& (batch.AreMoreBatchesExpected || batch.RequiresTransaction))
|| autoTransactionBehavior == AutoTransactionBehavior.Always))
{
transaction = connection.BeginTransaction();
beganTransaction = true;
Expand Down Expand Up @@ -174,12 +176,14 @@ public virtual async Task<int> ExecuteAsync(
try
{
var transactionEnlistManager = connection as ITransactionEnlistmentManager;
var autoTransactionBehavior = CurrentContext.Context.Database.AutoTransactionBehavior;
if (transaction == null
&& transactionEnlistManager?.EnlistedTransaction is null
&& transactionEnlistManager?.CurrentAmbientTransaction is null
&& CurrentContext.Context.Database.AutoTransactionsEnabled
// Don't start a transaction if we have a single batch which doesn't require a transaction (single command), for perf.
&& (batch.AreMoreBatchesExpected || batch.RequiresTransaction))
&& ((autoTransactionBehavior == AutoTransactionBehavior.WhenNeeded
&& (batch.AreMoreBatchesExpected || batch.RequiresTransaction))
|| autoTransactionBehavior == AutoTransactionBehavior.Always))
{
transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
beganTransaction = true;
Expand Down
29 changes: 29 additions & 0 deletions src/EFCore/AutoTransactionBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// Indicates whether or not a transaction will be created automatically by <see cref="DbContext.SaveChanges()" /> if a user transaction
/// wasn't provided via 'BeginTransaction' nor 'UseTransaction'.
/// </summary>
public enum AutoTransactionBehavior
{
/// <summary>
/// Transactions are automatically created as needed. For example, most single SQL statements are implicitly executed within a
/// transaction, and so do not require an explicit one to be created, reducing database round trips. This is the default setting.
/// </summary>
WhenNeeded,

/// <summary>
/// Transactions are always created automatically, as long as a user transaction wasn't provided. This setting may create
/// transactions even when they're not needed, adding additional database round trips which may degrade performance.
/// </summary>
Always,

/// <summary>
/// Transactions are never created automatically. Use this options with caution, since the database could be left in an
/// inconsistent state if failure occurs.
/// </summary>
Never
}
4 changes: 2 additions & 2 deletions src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ protected virtual async Task<int> SaveChangesAsync(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
=> !Context.Database.AutoTransactionsEnabled
=> Context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Never
? SaveChanges(this, acceptAllChangesOnSuccess)
: Dependencies.ExecutionStrategy.Execute(
(StateManager: this, AcceptAllChangesOnSuccess: acceptAllChangesOnSuccess),
Expand Down Expand Up @@ -1291,7 +1291,7 @@ private static int SaveChanges(StateManager stateManager, bool acceptAllChangesO
public virtual Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default)
=> !Context.Database.AutoTransactionsEnabled
=> Context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Never
? SaveChangesAsync(this, acceptAllChangesOnSuccess, cancellationToken)
: Dependencies.ExecutionStrategy.ExecuteAsync(
(StateManager: this, AcceptAllChangesOnSuccess: acceptAllChangesOnSuccess),
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ private void SetLeaseInternal(DbContextLease lease)
|| _configurationSnapshot.HasDatabaseConfiguration)
{
var database = Database;
database.AutoTransactionsEnabled = _configurationSnapshot.AutoTransactionsEnabled;
database.AutoTransactionBehavior = _configurationSnapshot.AutoTransactionBehavior;
database.AutoSavepointsEnabled = _configurationSnapshot.AutoSavepointsEnabled;
}

Expand Down Expand Up @@ -917,7 +917,7 @@ void IDbContextPoolable.SnapshotConfiguration()
changeDetectorEvents != null,
_changeTracker?.AutoDetectChangesEnabled ?? true,
_changeTracker?.QueryTrackingBehavior ?? QueryTrackingBehavior.TrackAll,
_database?.AutoTransactionsEnabled ?? true,
_database?.AutoTransactionBehavior ?? AutoTransactionBehavior.WhenNeeded,
_database?.AutoSavepointsEnabled ?? true,
_changeTracker?.LazyLoadingEnabled ?? true,
_changeTracker?.CascadeDeleteTiming ?? CascadeTiming.Immediate,
Expand Down
53 changes: 44 additions & 9 deletions src/EFCore/Infrastructure/DatabaseFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,28 +376,63 @@ public virtual IDbContextTransaction? CurrentTransaction
=> Dependencies.TransactionManager.CurrentTransaction;

/// <summary>
/// Gets or sets a value indicating whether or not a transaction will be created
/// automatically by <see cref="DbContext.SaveChanges()" /> if none of the
/// 'BeginTransaction' or 'UseTransaction' methods have been called.
/// Gets or sets a value indicating whether or not a transaction will be created automatically by
/// <see cref="DbContext.SaveChanges()" /> if none of the 'BeginTransaction' or 'UseTransaction' methods have been called.
/// </summary>
/// <remarks>
/// <para>
/// Setting this value to <see langword="false" /> will also disable the <see cref="IExecutionStrategy" />
/// for <see cref="DbContext.SaveChanges()" />
/// Setting this value to <see langword="false" /> will also disable the <see cref="IExecutionStrategy" /> for
/// <see cref="DbContext.SaveChanges()" />
/// </para>
/// <para>
/// The default value is <see langword="true" />, meaning that <see cref="DbContext.SaveChanges()" /> will always use a
/// transaction when saving changes.
/// </para>
/// <para>
/// Setting this value to <see langword="false" /> should only be done with caution since the database
/// could be left in a corrupted state if <see cref="DbContext.SaveChanges()" /> fails.
/// Setting this value to <see langword="false" /> should only be done with caution, since the database could be left in an
/// inconsistent state if failure occurs.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-transactions">Transactions in EF Core</see> for more information and examples.
/// </para>
/// </remarks>
public virtual bool AutoTransactionsEnabled { get; set; } = true;
[Obsolete("Use EnableAutoTransactions instead")]
public virtual bool AutoTransactionsEnabled
{
get => AutoTransactionBehavior is AutoTransactionBehavior.Always or AutoTransactionBehavior.WhenNeeded;
set
{
if (value)
{
if (AutoTransactionBehavior == AutoTransactionBehavior.Never)
{
AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded;
}
}
else
{
AutoTransactionBehavior = AutoTransactionBehavior.Never;
}
}
}

/// <summary>
/// Gets or sets a value indicating whether or not a transaction will be created automatically by
/// <see cref="DbContext.SaveChanges()" /> if neither 'BeginTransaction' nor 'UseTransaction' has been called.
/// </summary>
/// <remarks>
/// <para>
/// The default setting is <see cref="AutoTransactionBehavior.WhenNeeded" />.
/// </para>
/// <para>
/// Setting this to <see cref="AutoTransactionBehavior.Never" /> with caution, since the database could be left in
/// an inconsistent state if failure occurs.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-transactions">Transactions in EF Core</see> for more information and examples.
/// </para>
/// </remarks>
public virtual AutoTransactionBehavior AutoTransactionBehavior { get; set; } = AutoTransactionBehavior.WhenNeeded;

/// <summary>
/// Whether a transaction savepoint will be created automatically by <see cref="DbContext.SaveChanges()" /> if it is called
Expand Down Expand Up @@ -483,7 +518,7 @@ DbContext IDatabaseFacadeDependenciesAccessor.Context
/// <inheritdoc />
void IResettableService.ResetState()
{
AutoTransactionsEnabled = true;
AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded;
AutoSavepointsEnabled = true;
}

Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Internal/DbContextPoolConfigurationSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public DbContextPoolConfigurationSnapshot(
bool hasChangeDetectorConfiguration,
bool autoDetectChangesEnabled,
QueryTrackingBehavior queryTrackingBehavior,
bool autoTransactionsEnabled,
AutoTransactionBehavior autoTransactionBehavior,
bool autoSavepointsEnabled,
bool lazyLoadingEnabled,
CascadeTiming cascadeDeleteTiming,
Expand All @@ -47,8 +47,8 @@ public DbContextPoolConfigurationSnapshot(
HasChangeDetectorConfiguration = hasChangeDetectorConfiguration;
AutoDetectChangesEnabled = autoDetectChangesEnabled;
QueryTrackingBehavior = queryTrackingBehavior;
AutoTransactionsEnabled = autoTransactionsEnabled;
AutoSavepointsEnabled = autoSavepointsEnabled;
AutoTransactionBehavior = autoTransactionBehavior;
LazyLoadingEnabled = lazyLoadingEnabled;
CascadeDeleteTiming = cascadeDeleteTiming;
DeleteOrphansTiming = deleteOrphansTiming;
Expand Down Expand Up @@ -143,7 +143,7 @@ public DbContextPoolConfigurationSnapshot(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public bool AutoTransactionsEnabled { get; }
public AutoTransactionBehavior AutoTransactionBehavior { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Loading

0 comments on commit a2a08df

Please sign in to comment.