Skip to content
This repository was archived by the owner on Nov 29, 2017. It is now read-only.

Commit e703498

Browse files
committed
Adding ExecutionStrategy support to ObjectContext.SaveChanges()
Adding ExecutionStrategy support to SqlProviderServices Removing a redundant check in ObjectContext.EnsureConnection() Splitting a precondition in EntityConnection.Open into two preconditions Reducing duplicate setup code in ObjectContextTests
1 parent 7e7b5cd commit e703498

File tree

13 files changed

+505
-260
lines changed

13 files changed

+505
-260
lines changed

src/EntityFramework.SqlServer/SqlProviderServices.cs

+23-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace System.Data.Entity.SqlServer
44
{
55
using System.Collections.Generic;
66
using System.Data.Common;
7+
using System.Data.Entity.Config;
78
using System.Data.Entity.Core;
89
using System.Data.Entity.Core.Common;
910
using System.Data.Entity.Core.Common.CommandTrees;
@@ -34,7 +35,7 @@ private SqlProviderServices()
3435
}
3536

3637
/// <summary>
37-
/// Singleton object;
38+
/// Singleton object
3839
/// </summary>
3940
private static readonly SqlProviderServices _providerInstance = new SqlProviderServices();
4041

@@ -1136,12 +1137,24 @@ private static SqlCommand CreateCommand(SqlConnection sqlConnection, string comm
11361137

11371138
private static void UsingConnection(SqlConnection sqlConnection, Action<SqlConnection> act)
11381139
{
1139-
// remember the connection string so that we can reset it credentials are wiped
1140+
// remember the connection string so that we can reset if credentials are wiped
11401141
var holdConnectionString = sqlConnection.ConnectionString;
11411142
var openingConnection = sqlConnection.State == ConnectionState.Closed;
11421143
if (openingConnection)
11431144
{
1144-
sqlConnection.Open();
1145+
DbConfiguration.GetService<IExecutionStrategy>(new ExecutionStrategyKey("System.Data.SqlClient", sqlConnection.DataSource))
1146+
.Execute(
1147+
() =>
1148+
{
1149+
// If Open() fails the original credentials need to be restored before retrying
1150+
if (sqlConnection.State == ConnectionState.Closed
1151+
&& !sqlConnection.ConnectionString.Equals(holdConnectionString, StringComparison.Ordinal))
1152+
{
1153+
sqlConnection.ConnectionString = holdConnectionString;
1154+
}
1155+
1156+
sqlConnection.Open();
1157+
});
11451158
}
11461159
try
11471160
{
@@ -1153,8 +1166,14 @@ private static void UsingConnection(SqlConnection sqlConnection, Action<SqlConne
11531166
{
11541167
// if we opened the connection, we should close it
11551168
sqlConnection.Close();
1169+
1170+
// Can only change the connection string if the connection is closed
1171+
if (!sqlConnection.ConnectionString.Equals(holdConnectionString, StringComparison.Ordinal))
1172+
{
1173+
Debug.Assert(true);
1174+
sqlConnection.ConnectionString = holdConnectionString;
1175+
}
11561176
}
1157-
sqlConnection.ConnectionString = holdConnectionString;
11581177
}
11591178
}
11601179

src/EntityFramework/Core/EntityClient/EntityCommand.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ private void CheckIfReadyToPrepare()
898898
if (_connection.StoreProviderFactory == null
899899
|| _connection.StoreConnection == null)
900900
{
901-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
901+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
902902
}
903903

904904
// Make sure the connection is not closed or broken

src/EntityFramework/Core/EntityClient/EntityConnection.cs

+24-14
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,12 @@ public override string ServerVersion
368368
{
369369
if (_storeConnection == null)
370370
{
371-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
371+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
372372
}
373373

374374
if (State != ConnectionState.Open)
375375
{
376-
throw new InvalidOperationException(Strings.EntityClient_ConnectionNotOpen);
376+
throw Error.EntityClient_ConnectionNotOpen();
377377
}
378378

379379
try
@@ -544,12 +544,17 @@ public override void Open()
544544
{
545545
if (_storeConnection == null)
546546
{
547-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
547+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
548548
}
549549

550-
if (State != ConnectionState.Closed)
550+
if (State == ConnectionState.Broken)
551551
{
552-
throw new InvalidOperationException(Strings.EntityClient_CannotReopenConnection);
552+
throw Error.EntityClient_CannotOpenBrokenConnection();
553+
}
554+
555+
if (State == ConnectionState.Open)
556+
{
557+
throw Error.EntityClient_CannotReopenConnection();
553558
}
554559

555560
var closeStoreConnectionOnFailure = false;
@@ -581,7 +586,7 @@ public override void Open()
581586
if (_storeConnection == null
582587
|| _storeConnection.State != ConnectionState.Open)
583588
{
584-
throw new InvalidOperationException(Strings.EntityClient_ConnectionNotOpen);
589+
throw Error.EntityClient_ConnectionNotOpen();
585590
}
586591

587592
InitializeMetadata(_storeConnection, _storeConnection, closeStoreConnectionOnFailure);
@@ -599,12 +604,17 @@ public override async Task OpenAsync(CancellationToken cancellationToken)
599604
{
600605
if (_storeConnection == null)
601606
{
602-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
607+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
608+
}
609+
610+
if (State == ConnectionState.Broken)
611+
{
612+
throw Error.EntityClient_CannotOpenBrokenConnection();
603613
}
604614

605-
if (State != ConnectionState.Closed)
615+
if (State == ConnectionState.Open)
606616
{
607-
throw new InvalidOperationException(Strings.EntityClient_CannotReopenConnection);
617+
throw Error.EntityClient_CannotReopenConnection();
608618
}
609619

610620
var closeStoreConnectionOnFailure = false;
@@ -637,7 +647,7 @@ await executionStrategy.ExecuteAsync(() => _storeConnection.OpenAsync(cancellati
637647
if (_storeConnection == null
638648
|| _storeConnection.State != ConnectionState.Open)
639649
{
640-
throw new InvalidOperationException(Strings.EntityClient_ConnectionNotOpen);
650+
throw Error.EntityClient_ConnectionNotOpen();
641651
}
642652

643653
InitializeMetadata(_storeConnection, _storeConnection, closeStoreConnectionOnFailure);
@@ -761,12 +771,12 @@ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLeve
761771

762772
if (_storeConnection == null)
763773
{
764-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
774+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
765775
}
766776

767777
if (State != ConnectionState.Open)
768778
{
769-
throw new InvalidOperationException(Strings.EntityClient_ConnectionNotOpen);
779+
throw Error.EntityClient_ConnectionNotOpen();
770780
}
771781

772782
DbTransaction storeTransaction = null;
@@ -818,12 +828,12 @@ public override void EnlistTransaction(Transaction transaction)
818828
{
819829
if (_storeConnection == null)
820830
{
821-
throw new InvalidOperationException(Strings.EntityClient_ConnectionStringNeededBeforeOperation);
831+
throw Error.EntityClient_ConnectionStringNeededBeforeOperation();
822832
}
823833

824834
if (State != ConnectionState.Open)
825835
{
826-
throw new InvalidOperationException(Strings.EntityClient_ConnectionNotOpen);
836+
throw Error.EntityClient_ConnectionNotOpen();
827837
}
828838

829839
try

src/EntityFramework/Core/Objects/ObjectContext.cs

+29-46
Original file line numberDiff line numberDiff line change
@@ -1440,14 +1440,16 @@ private EntitySet GetEntitySetForNameAndType(string entitySetName, Type entityCL
14401440
/// Calls to EnsureConnection MUST be matched with a single call to ReleaseConnection.
14411441
/// </summary>
14421442
/// <exception cref="ObjectDisposedException">
1443-
/// If the
1444-
/// <see cref="ObjectContext" />
1445-
/// instance has been disposed.
1443+
/// If the <see cref="ObjectContext" /> instance has been disposed.
14461444
/// </exception>
14471445
internal virtual void EnsureConnection()
14481446
{
1449-
if (ConnectionState.Closed
1450-
== Connection.State)
1447+
if (Connection.State == ConnectionState.Broken)
1448+
{
1449+
Connection.Close();
1450+
}
1451+
1452+
if (Connection.State == ConnectionState.Closed)
14511453
{
14521454
Connection.Open();
14531455
_openedConnection = true;
@@ -1458,17 +1460,6 @@ internal virtual void EnsureConnection()
14581460
_connectionRequestCount++;
14591461
}
14601462

1461-
// Check the connection was opened correctly
1462-
if (Connection.State == ConnectionState.Closed
1463-
|| Connection.State == ConnectionState.Broken)
1464-
{
1465-
var message = Strings.EntityClient_ExecutingOnClosedConnection(
1466-
Connection.State == ConnectionState.Closed
1467-
? Strings.EntityClient_ConnectionStateClosed
1468-
: Strings.EntityClient_ConnectionStateBroken);
1469-
throw new InvalidOperationException(message);
1470-
}
1471-
14721463
try
14731464
{
14741465
// Make sure the necessary metadata is registered
@@ -1507,14 +1498,16 @@ internal virtual void EnsureConnection()
15071498
/// Calls to EnsureConnection MUST be matched with a single call to ReleaseConnection.
15081499
/// </summary>
15091500
/// <exception cref="ObjectDisposedException">
1510-
/// If the
1511-
/// <see cref="ObjectContext" />
1512-
/// instance has been disposed.
1501+
/// If the <see cref="ObjectContext" /> instance has been disposed.
15131502
/// </exception>
15141503
internal virtual async Task EnsureConnectionAsync(CancellationToken cancellationToken)
15151504
{
1516-
if (ConnectionState.Closed
1517-
== Connection.State)
1505+
if (Connection.State == ConnectionState.Broken)
1506+
{
1507+
Connection.Close();
1508+
}
1509+
1510+
if (Connection.State == ConnectionState.Closed)
15181511
{
15191512
await Connection.OpenAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
15201513
_openedConnection = true;
@@ -1525,17 +1518,6 @@ internal virtual async Task EnsureConnectionAsync(CancellationToken cancellation
15251518
_connectionRequestCount++;
15261519
}
15271520

1528-
// Check the connection was opened correctly
1529-
if (Connection.State == ConnectionState.Closed
1530-
|| Connection.State == ConnectionState.Broken)
1531-
{
1532-
var message = Strings.EntityClient_ExecutingOnClosedConnection(
1533-
Connection.State == ConnectionState.Closed
1534-
? Strings.EntityClient_ConnectionStateClosed
1535-
: Strings.EntityClient_ConnectionStateBroken);
1536-
throw new InvalidOperationException(message);
1537-
}
1538-
15391521
try
15401522
{
15411523
// Make sure the necessary metadata is registered
@@ -2545,7 +2527,8 @@ public virtual int SaveChanges(SaveOptions options)
25452527
// if there are no changes to save, perform fast exit to avoid interacting with or starting of new transactions
25462528
if (0 < entriesAffected)
25472529
{
2548-
entriesAffected = SaveChangesToStore(options);
2530+
var executionStrategy = DbProviderServices.GetExecutionStrategy(Connection);
2531+
entriesAffected = executionStrategy.Execute(() => SaveChangesToStore(options));
25492532
}
25502533

25512534
ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
@@ -2572,18 +2555,20 @@ public Task<Int32> SaveChangesAsync(SaveOptions options)
25722555
/// <param name="options"> Describes behavior options of SaveChanges </param>
25732556
/// <param name="cancellationToken"> The token to monitor for cancellation requests </param>
25742557
/// <returns> A task representing the asynchronous operation </returns>
2575-
public virtual Task<Int32> SaveChangesAsync(SaveOptions options, CancellationToken cancellationToken)
2558+
public virtual async Task<Int32> SaveChangesAsync(SaveOptions options, CancellationToken cancellationToken)
25762559
{
25772560
PrepareToSaveChanges(options);
25782561

25792562
var entriesAffected =
2580-
Task.FromResult(
2581-
ObjectStateManager.GetObjectStateEntriesCount(EntityState.Added | EntityState.Deleted | EntityState.Modified));
2563+
ObjectStateManager.GetObjectStateEntriesCount(EntityState.Added | EntityState.Deleted | EntityState.Modified);
25822564

25832565
// if there are no changes to save, perform fast exit to avoid interacting with or starting of new transactions
2584-
if (0 < entriesAffected.Result)
2566+
if (0 < entriesAffected)
25852567
{
2586-
entriesAffected = SaveChangesToStoreAsync(options, cancellationToken);
2568+
var executionStrategy = DbProviderServices.GetExecutionStrategy(Connection);
2569+
entriesAffected = await
2570+
executionStrategy.ExecuteAsync(() => SaveChangesToStoreAsync(options, cancellationToken))
2571+
.ConfigureAwait(continueOnCapturedContext: false);
25872572
}
25882573

25892574
ObjectStateManager.AssertAllForeignKeyIndexEntriesAreValid();
@@ -2631,7 +2616,7 @@ private int SaveChangesToStore(SaveOptions options)
26312616
if (_commandInterceptor == null
26322617
|| !_commandInterceptor.IsEnabled)
26332618
{
2634-
entriesAffected = ExecuteInTransaction(() => _adapter.Update(ObjectStateManager, true));
2619+
entriesAffected = ExecuteInTransaction(() => _adapter.Update(ObjectStateManager, throwOnClosedConnection: true));
26352620
}
26362621
else
26372622
{
@@ -2640,8 +2625,6 @@ private int SaveChangesToStore(SaveOptions options)
26402625

26412626
if ((SaveOptions.AcceptAllChangesAfterSave & options) != 0)
26422627
{
2643-
// only accept changes after the local transaction commits
2644-
26452628
try
26462629
{
26472630
AcceptAllChanges();
@@ -2689,15 +2672,13 @@ private async Task<int> SaveChangesToStoreAsync(SaveOptions options, Cancellatio
26892672

26902673
if ((SaveOptions.AcceptAllChangesAfterSave & options) != 0)
26912674
{
2692-
// only accept changes after the local transaction commits
2693-
26942675
try
26952676
{
26962677
AcceptAllChanges();
26972678
}
26982679
catch (Exception e)
26992680
{
2700-
// If AcceptAllChanges throw - let's inform user that changes in database were committed
2681+
// If AcceptAllChanges throws - let's inform user that changes in database were committed
27012682
// and that Context and Database can be in inconsistent state.
27022683
throw new InvalidOperationException(Strings.ObjectContext_AcceptAllChangesFailure(e.Message));
27032684
}
@@ -3530,7 +3511,8 @@ public virtual ObjectResult<TElement> ExecuteStoreQuery<TElement>(string command
35303511
/// <returns>
35313512
/// An enumeration of objects of type <typeparamref name="TElement" /> .
35323513
/// </returns>
3533-
public virtual ObjectResult<TElement> ExecuteStoreQuery<TElement>(string commandText, ExecutionOptions executionOptions, params object[] parameters)
3514+
public virtual ObjectResult<TElement> ExecuteStoreQuery<TElement>(
3515+
string commandText, ExecutionOptions executionOptions, params object[] parameters)
35343516
{
35353517
return ExecuteStoreQueryInternal<TElement>(
35363518
commandText, /*entitySetName:*/null, executionOptions, parameters);
@@ -3552,7 +3534,8 @@ public virtual ObjectResult<TElement> ExecuteStoreQuery<TElement>(
35523534
string commandText, string entitySetName, MergeOption mergeOption, params object[] parameters)
35533535
{
35543536
Check.NotEmpty(entitySetName, "entitySetName");
3555-
return ExecuteStoreQueryInternal<TElement>(commandText, entitySetName, new ExecutionOptions(mergeOption, streaming: false), parameters);
3537+
return ExecuteStoreQueryInternal<TElement>(
3538+
commandText, entitySetName, new ExecutionOptions(mergeOption, streaming: false), parameters);
35563539
}
35573540

35583541
/// <summary>

0 commit comments

Comments
 (0)