Skip to content

Commit

Permalink
Adding DefaultTransactionalBehavior setting to DbContext and ObjectCo…
Browse files Browse the repository at this point in the history
…ntext.

This fixes issue dotnet#1612
  • Loading branch information
AndriySvyryd committed Nov 11, 2014
1 parent 9e43f8d commit bc369fe
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 55 deletions.
24 changes: 19 additions & 5 deletions src/EntityFramework/Core/Objects/ObjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3603,10 +3603,12 @@ public virtual ObjectResult<TElement> ExecuteFunction<TElement>(
executionOptions = new ExecutionOptions(executionOptions.MergeOption, !executionStrategy.RetriesOnFailure);
}

var startLocalTransaction = !executionOptions.UserSpecifiedStreaming.Value
&& _options.EnsureTransactionsForFunctionsAndCommands;
return executionStrategy.Execute(
() => ExecuteInTransaction(
() => CreateFunctionObjectResult<TElement>(entityCommand, functionImport.EntitySets, expectedEdmTypes, executionOptions),
executionStrategy, startLocalTransaction: !executionOptions.UserSpecifiedStreaming.Value,
executionStrategy, startLocalTransaction: startLocalTransaction,
releaseConnectionOnSuccess: !executionOptions.UserSpecifiedStreaming.Value));
}

Expand Down Expand Up @@ -3634,7 +3636,8 @@ public virtual int ExecuteFunction(string functionName, params ObjectParameter[]
return executionStrategy.Execute(
() => ExecuteInTransaction(
() => ExecuteFunctionCommand(entityCommand), executionStrategy,
startLocalTransaction: true, releaseConnectionOnSuccess: true));
startLocalTransaction: _options.EnsureTransactionsForFunctionsAndCommands,
releaseConnectionOnSuccess: true));
}

private static int ExecuteFunctionCommand(EntityCommand entityCommand)
Expand Down Expand Up @@ -4067,7 +4070,10 @@ public virtual T CreateObject<T>()
/// <returns>The number of rows affected.</returns>
public virtual int ExecuteStoreCommand(string commandText, params object[] parameters)
{
return ExecuteStoreCommand(TransactionalBehavior.EnsureTransaction, commandText, parameters);
return ExecuteStoreCommand(
_options.EnsureTransactionsForFunctionsAndCommands ? TransactionalBehavior.EnsureTransaction : TransactionalBehavior.DoNotEnsureTransaction,
commandText,
parameters);
}

/// <summary>
Expand Down Expand Up @@ -4122,7 +4128,11 @@ public virtual int ExecuteStoreCommand(TransactionalBehavior transactionalBehavi
/// </returns>
public Task<int> ExecuteStoreCommandAsync(string commandText, params object[] parameters)
{
return ExecuteStoreCommandAsync(TransactionalBehavior.EnsureTransaction, commandText, CancellationToken.None, parameters);
return ExecuteStoreCommandAsync(
_options.EnsureTransactionsForFunctionsAndCommands ? TransactionalBehavior.EnsureTransaction : TransactionalBehavior.DoNotEnsureTransaction,
commandText,
CancellationToken.None,
parameters);
}

/// <summary>
Expand Down Expand Up @@ -4178,7 +4188,11 @@ public Task<int> ExecuteStoreCommandAsync(TransactionalBehavior transactionalBeh
public virtual Task<int> ExecuteStoreCommandAsync(
string commandText, CancellationToken cancellationToken, params object[] parameters)
{
return ExecuteStoreCommandAsync(TransactionalBehavior.EnsureTransaction, commandText, cancellationToken, parameters);
return ExecuteStoreCommandAsync(
_options.EnsureTransactionsForFunctionsAndCommands ? TransactionalBehavior.EnsureTransaction : TransactionalBehavior.DoNotEnsureTransaction,
commandText,
cancellationToken,
parameters);
}

/// <summary>
Expand Down
14 changes: 14 additions & 0 deletions src/EntityFramework/Core/Objects/ObjectContextOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,22 @@ public sealed class ObjectContextOptions
internal ObjectContextOptions()
{
ProxyCreationEnabled = true;
EnsureTransactionsForFunctionsAndCommands = true;
}

/// <summary>
/// Gets or sets the value that determines whether SQL functions and commands should be always executed in a transaction.
/// </summary>
/// <remarks>
/// This flag determines whether a new transaction will be started when methods such as <see cref="ObjectContext.ExecuteFunction"/>
/// and <see cref="ObjectContext.ExecuteStoreCommand(string,object[])"/> are executed outside of a transaction.
/// Note that this does not change the behavior of <see cref="ObjectContext.SaveChanges()"/>.
/// </remarks>
/// <value>
/// The default transactional behavior.
/// </value>
public bool EnsureTransactionsForFunctionsAndCommands { get; set; }

/// <summary>Gets or sets a Boolean value that determines whether related objects are loaded automatically when a navigation property is accessed.</summary>
/// <returns>true if lazy loading is enabled; otherwise, false.</returns>
public bool LazyLoadingEnabled { get; set; }
Expand Down
13 changes: 10 additions & 3 deletions src/EntityFramework/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,10 @@ public DbRawSqlQuery SqlQuery(Type elementType, string sql, params object[] para
/// <returns> The result returned by the database after executing the command. </returns>
public int ExecuteSqlCommand(string sql, params object[] parameters)
{
return ExecuteSqlCommand(TransactionalBehavior.EnsureTransaction, sql, parameters);
return ExecuteSqlCommand(
_internalContext.EnsureTransactionsForFunctionsAndCommands ? TransactionalBehavior.EnsureTransaction : TransactionalBehavior.DoNotEnsureTransaction,
sql,
parameters);
}

/// <summary>
Expand Down Expand Up @@ -627,7 +630,7 @@ public int ExecuteSqlCommand(TransactionalBehavior transactionalBehavior, string
/// </returns>
public Task<int> ExecuteSqlCommandAsync(string sql, params object[] parameters)
{
return ExecuteSqlCommandAsync(TransactionalBehavior.EnsureTransaction, sql, CancellationToken.None, parameters);
return ExecuteSqlCommandAsync(sql, CancellationToken.None, parameters);
}

/// <summary>
Expand Down Expand Up @@ -680,7 +683,11 @@ public Task<int> ExecuteSqlCommandAsync(TransactionalBehavior transactionalBehav
/// </returns>
public Task<int> ExecuteSqlCommandAsync(string sql, CancellationToken cancellationToken, params object[] parameters)
{
return ExecuteSqlCommandAsync(TransactionalBehavior.EnsureTransaction, sql, cancellationToken, parameters);
return ExecuteSqlCommandAsync(
_internalContext.EnsureTransactionsForFunctionsAndCommands ? TransactionalBehavior.EnsureTransaction : TransactionalBehavior.DoNotEnsureTransaction,
sql,
cancellationToken,
parameters);
}

/// <summary>
Expand Down
17 changes: 17 additions & 0 deletions src/EntityFramework/Infrastructure/DbContextConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ public override int GetHashCode()
#endregion

#region Configuration options

/// <summary>
/// Gets or sets the value that determines whether SQL functions and commands should be always executed in a transaction.
/// </summary>
/// <remarks>
/// This flag determines whether a new transaction will be started when methods such as <see cref="Database.ExecuteSqlCommand(string,object[])"/>
/// are executed outside of a transaction.
/// Note that this does not change the behavior of <see cref="DbContext.SaveChanges()"/>.
/// </remarks>
/// <value>
/// The default transactional behavior.
/// </value>
public bool EnsureTransactionsForFunctionsAndCommands
{
get { return _internalContext.EnsureTransactionsForFunctionsAndCommands; }
set { _internalContext.EnsureTransactionsForFunctionsAndCommands = value; }
}

/// <summary>
/// Gets or sets a value indicating whether lazy loading of relationships exposed as
Expand Down
8 changes: 7 additions & 1 deletion src/EntityFramework/Internal/EagerInternalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,13 @@ public override void OverrideConnection(IInternalConnection connection)

#endregion

#region Lazy Loading
#region Context options

public override bool EnsureTransactionsForFunctionsAndCommands
{
get { return ObjectContextInUse.ContextOptions.EnsureTransactionsForFunctionsAndCommands; }
set { ObjectContextInUse.ContextOptions.EnsureTransactionsForFunctionsAndCommands = value; }
}

// <summary>
// Gets or sets a value indicating whether lazy loading is enabled. This is just a wrapper
Expand Down
5 changes: 5 additions & 0 deletions src/EntityFramework/Internal/InternalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,11 @@ private Action CreateInitializationAction<TContext>(IDatabaseInitializer<TContex

#region Context options

// <summary>
// Gets or sets the value that determines whether SQL functions and commands should be always executed in a transaction.
// </summary>
public abstract bool EnsureTransactionsForFunctionsAndCommands { get; set; }

// <summary>
// Gets or sets a value indicating whether lazy loading is enabled.
// </summary>
Expand Down
27 changes: 26 additions & 1 deletion src/EntityFramework/Internal/LazyInternalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ private static readonly ConcurrentDictionary<Tuple<DbCompiledModel, string>, Ret
// Set to true if the context was created with an existing DbCompiledModel instance.
private readonly bool _createdWithExistingModel;

// This flag is used to keep the user's selected default transactional behavior option before the ObjectContext is initialized.
private bool _initialEnsureTransactionsForFunctionsAndCommands = true;

// This flag is used to keep the user's selected lazy loading option before the ObjectContext is initialized.
private bool _initialLazyLoadingFlag = true;

Expand Down Expand Up @@ -449,6 +452,7 @@ var model
}
}

_objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands = _initialEnsureTransactionsForFunctionsAndCommands;
_objectContext.ContextOptions.LazyLoadingEnabled = _initialLazyLoadingFlag;
_objectContext.ContextOptions.ProxyCreationEnabled = _initialProxyCreationFlag;
_objectContext.ContextOptions.UseCSharpNullComparisonBehavior = !_useDatabaseNullSemanticsFlag;
Expand Down Expand Up @@ -659,7 +663,28 @@ public override IDatabaseInitializer<DbContext> DefaultInitializer

#endregion

#region Lazy Loading
#region Context options

public override bool EnsureTransactionsForFunctionsAndCommands
{
get
{
var objectContext = ObjectContextInUse;
return objectContext != null ? objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands : _initialEnsureTransactionsForFunctionsAndCommands;
}
set
{
var objectContext = ObjectContextInUse;
if (objectContext != null)
{
objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands = value;
}
else
{
_initialEnsureTransactionsForFunctionsAndCommands = value;
}
}
}

// <summary>
// Gets or sets a value indicating whether lazy loading is enabled.
Expand Down
79 changes: 79 additions & 0 deletions test/EntityFramework/FunctionalTests/Objects/TransactionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2120,6 +2120,45 @@ public void ExecuteSqlCommand_by_default_uses_transaction()
}
}

[Fact]
public void ExecuteSqlCommand_with_no_TransactionalBehavior_uses_transaction()
{
try
{
using (var ctx = CreateTransactionDbContext())
{
ctx.Database.ExecuteSqlCommand("[dbo].[TransactionLogEntry_Insert]");
var transactionCount = ctx.LogEntries.Single().TransactionCount;

Assert.Equal(1, transactionCount);
}
}
finally
{
ResetTables();
}
}

[Fact]
public void ExecuteSqlCommand_with_EnsureTransactionsForFunctionsAndCommands_set_to_false_does_not_use_transaction()
{
try
{
using (var ctx = CreateTransactionDbContext())
{
ctx.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
ctx.Database.ExecuteSqlCommand("[dbo].[TransactionLogEntry_Insert]");
var transactionCount = ctx.LogEntries.Single().TransactionCount;

Assert.Equal(0, transactionCount);
}
}
finally
{
ResetTables();
}
}

[Fact]
public void ExecuteSqlCommand_with_TransactionalBehavior_EnsureTransaction_uses_transaction()
{
Expand Down Expand Up @@ -2214,6 +2253,46 @@ public void
}

#if !NET40

[Fact]
public void ExecuteSqlCommandAsync_with_no_TransactionalBehavior_uses_transaction()
{
try
{
using (var ctx = CreateTransactionDbContext())
{
ctx.Database.ExecuteSqlCommandAsync("[dbo].[TransactionLogEntry_Insert]").Wait();
var transactionCount = ctx.LogEntries.Single().TransactionCount;

Assert.Equal(1, transactionCount);
}
}
finally
{
ResetTables();
}
}

[Fact]
public void ExecuteSqlCommandAsync_with_EnsureTransactionsForFunctionsAndCommands_set__to_false_does_not_use_transaction()
{
try
{
using (var ctx = CreateTransactionDbContext())
{
ctx.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
ctx.Database.ExecuteSqlCommandAsync("[dbo].[TransactionLogEntry_Insert]").Wait();
var transactionCount = ctx.LogEntries.Single().TransactionCount;

Assert.Equal(0, transactionCount);
}
}
finally
{
ResetTables();
}
}

[Fact]
public void ExecuteSqlCommandAsync_by_default_uses_transaction()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3377,6 +3377,54 @@ private void TestDetectChangesWithSaveChangesAndValidation(bool validateOnSaveEn

#endregion


#region Transactional behavior tests

[Fact]
public void EnsureTransactionsForFunctionsAndCommands_is_true_by_default()
{
using (var context = new DbContext("TransactionalBehavior"))
{
Assert.True(context.Configuration.EnsureTransactionsForFunctionsAndCommands);
}

using (var context = new ObjectContext(new EntityConnection(SimpleModelEntityConnectionString)))
{
Assert.True(context.ContextOptions.EnsureTransactionsForFunctionsAndCommands);
}
}

[Fact]
public void EnsureTransactionsForFunctionsAndCommands_is_passed_through_to_implicit_ObjectContext()
{
using (var context = new DbContext("TransactionalBehavior"))
{
context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

var objectContext = ((IObjectContextAdapter)context).ObjectContext;
Assert.False(objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands);

objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands = true;
Assert.True(context.Configuration.EnsureTransactionsForFunctionsAndCommands);
}
}

[Fact]
public void EnsureTransactionsForFunctionsAndCommands_is_passed_through_to_existing_ObjectContext()
{
var objectContext = new ObjectContext(new EntityConnection(SimpleModelEntityConnectionString));
using (var context = new DbContext(objectContext, dbContextOwnsObjectContext: false))
{
context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
Assert.False(objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands);

objectContext.ContextOptions.EnsureTransactionsForFunctionsAndCommands = true;
Assert.True(context.Configuration.EnsureTransactionsForFunctionsAndCommands);
}
}

#endregion

#region Test EntityConnection-Store Connection state correlation when opening EntityConnection implicitly through context

[ExtendedFact(SkipForSqlAzure = true, Justification = "Streaming queries are not reliable on SQL Azure")]
Expand Down
Loading

0 comments on commit bc369fe

Please sign in to comment.