protected T RunInExecutionWrapper <T>(Func <T> run) { // We wrap this so it will repeat the whole test if there is a timeout or // we lose connectivity in the cloud. var executionStrategy = new SqlAzureExecutionStrategy(); SqlAzureDbConfiguration.SuspendExecutionStrategy = true; return(executionStrategy.Execute(run)); }
public static IEnumerable <T> QueryResiliently <T>(string sql, dynamic param = null, CommandType?commandType = null) { IEnumerable <T> result = null; var executionStrategy = new SqlAzureExecutionStrategy(); executionStrategy.Execute(() => { using (var connection = GetOpenConnection()) { // QueryAsync is marked async in the source code, but is not in metadata. +https://code.google.com/p/dapper-dot-net/source/browse/Dapper+NET45/SqlMapperAsync.cs result = SqlMapper.Query <T>(connection, sql, param: param, transaction: null, commandTimeout: null, commandType: commandType); } }); return(result); }
/// <summary> /// Strip the beginning of the exception message which contains the name of the stored procedure and the argument values. We do not disclose the values to the client. /// 20150724 AF. We strip the prefix in Runnymede.Website.Utils.CustomExceptionResult /// </summary> /// <param name="ex"></param> /// <returns></returns> //public static Exception StripException(SqlException ex) //{ // var index = ex.Message.IndexOf("::"); // The magic separator used within stored procedures. // return (index > 0) ? new Exception(ex.Message.Substring(index + 2).Trim(), ex) : ex; //} public static int ExecuteResiliently(string sql, dynamic param = null, CommandType?commandType = null) { int rowsAffected = 0; var executionStrategy = new SqlAzureExecutionStrategy(); executionStrategy.Execute(() => { using (var connection = GetOpenConnection()) { // Outside-initiated user transactions are not supported. See Limitations with Retrying Execution Strategies (EF6 onwards) +http://msdn.microsoft.com/en-us/data/dn307226 // +http://entityframework.codeplex.com/wikipage?title=Connection+Resiliency+Spec // To find a workaround, search for SqlAzureExecutionStrategy in the project code. rowsAffected = SqlMapper.Execute(connection, sql, param: param, transaction: null, commandTimeout: null, commandType: commandType); } }); return(rowsAffected); }
public override T BuildAndPersist(IRepository repository = null) { if (repository == null) { var executionStrategy = new SqlAzureExecutionStrategy(); SqlAzureDbConfiguration.SuspendExecutionStrategy = true; var entity = executionStrategy.Execute(() => base.BuildAndPersist(null)); SqlAzureDbConfiguration.SuspendExecutionStrategy = false; return(entity); } return(base.BuildAndPersist(repository)); }
/// <summary> /// /// Suspend Retry Execution Strategy handling method: /// Encapsulate the wrapping db user transaction and final context.SaveChanges() code in this method. /// Don't leak the SuspendRetryExecutionStrategy setting change. /// /// </summary> /// <param name="db">The db context that will be for all enclosed within a transaction database update operations</param> /// <param name="dbOperationsAction">The delegate with any entity updates operations performed using the db context</param> public static void TransactWithRetryStrategy(ApplicationDbContext db, Action dbOperationsAction) { try { ConnRetryConf.SuspendRetryExecutionStrategy = true; var executionStrategy = new SqlAzureExecutionStrategy(2, TimeSpan.FromSeconds(10)); executionStrategy .Execute(() => { using (var dbContextTransaction = db.Database.BeginTransaction(IsolationLevel.Serializable)) { dbOperationsAction(); db.SaveChanges(); dbContextTransaction.Commit(); } }); // Don't leak suspend setting } finally { ConnRetryConf.SuspendRetryExecutionStrategy = false; } }
/// <summary> /// Saves all changes made in this context to the underlying database /// </summary> /// <param name="context">The DB context</param> /// <returns>The number of objects written to the underlying database</returns> private int SaveChanges ( DbContext context ) { Validate.IsNotNull(context); var success = false; var preEventQueue = GenerateEventQueue(true); var postEventQueue = GenerateEventQueue(); var aggregates = GetPendingAggregates(); var rows = default(int); ProcessEventQueue ( preEventQueue, true ); var executionStrategy = new SqlAzureExecutionStrategy(); AzureEfConfiguration.SuspendExecutionStrategy = true; executionStrategy.Execute ( () => { using (var transaction = context.Database.BeginTransaction()) { try { rows = context.SaveChanges(); transaction.Commit(); success = true; } catch (Exception ex) { transaction.Rollback(); if (ex is DBConcurrencyException || ex is OptimisticConcurrencyException) { // NOTE: // For concurrency exceptions we want to ensure the // entities are not cached in the context so we can // stop the same error being raised indefinitely. RefreshAll(); } throw; } } } ); AzureEfConfiguration.SuspendExecutionStrategy = false; if (success) { foreach (var aggregate in aggregates) { aggregate.UnpublishedEvents.Clear(); } ProcessEventQueue(postEventQueue); } return(rows); }
private async Task <ApplicationUser> InternalSignup(SignupBindingModel model, ExternalLoginInfo loginInfo, string extId) { string email = null; string name = null; string password = null; // ExternalLoginInfo comes from an external authentication provider, i.e. Facebook or Google. if (loginInfo != null) { email = loginInfo.Email; name = loginInfo.ExternalIdentity.Name; } // SignupBindingModel comes filled out manually by the user. Override values from the external service with the manually entered values. if (model != null) { email = model.Email; name = model.Name; password = model.Password; } if ((String.IsNullOrEmpty(password) && loginInfo == null)) { throw new ArgumentNullException(); } var user = new ApplicationUser { UserName = email, Email = email, }; var displayName = CoalesceDisplayName(name); // The internal Connection Resiliency strategy does not work with user-initiated transactions. // The workaround is described in "Limitations with Retrying Execution Strategies" +http://msdn.microsoft.com/en-us/data/dn307226.aspx AppDbConfiguration.SuspendExecutionStrategy = true; // try-finally is not needed. The value is stored per request. var executionStrategy = new SqlAzureExecutionStrategy(); await executionStrategy.Execute( async() => { /* We do not use this.userManager within the Connection Resiliency block. * The DbContext should be constructed within the code block to be retried. This ensures that we are starting with a clean state for each retry. */ using (var dbContext = new ApplicationDbContext()) using (var userStore = new CustomUserStore(dbContext)) using (var userManager = new ApplicationUserManager(userStore)) { // Reuse the original validators. userManager.UserValidator = this.userManager.UserValidator; userManager.PasswordValidator = this.userManager.PasswordValidator; /* dbo.appGetNewUserId() generates random Ids and tests them against dbo.aspnetUsers until it finds a vacant one. * Yes, fragmentation in the table increases (it would be somewhat anyway due to editing), bad splits on insert ( but not every time after a page has initially been split in halves). * I believe that after the initial page split many next inserts and edits cause a next split not soon. * So the only harm is about 1/4 extra space (not doubled, because we continue filling pages randomly up until the default fillfactor.) * But the space havily depends on what users put in nvarchars anyway, so to worry about the space is pointless. * Clustered index seeks shouldn't be affected. Splits affect only leaf B-tree pages, branch pages have default fillfactor less than 100% always. * The benefit is we get rid of ExtId nchar(12) in many denormalized tables. * Just to remind, the initial idea behind the original ExtId was to prevent a bad guy from guessing sequecial Ids and download all avatars (avatars are accessable directly as Blobs). * So we sent the true Id to the client plus a separate ExtId for avatar URL and we stored it denormalized in many places. * Now ExtId is just a pre-signup cookie value not used anywhere else. */ user.Id = await dbContext.Database.SqlQuery <int>("select dbo.appGetNewUserId();").SingleAsync(); using (var transaction = dbContext.Database.BeginTransaction()) { // First phase. Create an ASP.NET Identity user in dbo.aspnetUsers var identityResult = String.IsNullOrWhiteSpace(password) ? await userManager.CreateAsync(user) : await userManager.CreateAsync(user, password); if (identityResult.Succeeded) { // Second phase. dbo.appUsers. Postpone the creation of the money account until it is really used. await dbContext.Database.ExecuteSqlCommandAsync( "insert dbo.appUsers (Id, DisplayName, ExtId) values (@p0, @p1, @p2);", user.Id, displayName, extId); /* The UserManager checks the existence of the username in another connection. * So in the case of an external authorisation (Google, Facebook) AddLoginAsync() does not see the username created by CreateAsync() in our transaction. * Using IsolationLevel.ReadUncommitted for our transaction would not help since the UserManager reads with ReadCommitted. * So we are forced to commit prematurely. */ transaction.Commit(); } else { transaction.Rollback(); } if (identityResult.Succeeded && loginInfo != null) { identityResult = await userManager.AddLoginAsync(user.Id, loginInfo.Login); } if (!identityResult.Succeeded) { // A hack to report the error to the caller method. See InspectErrorAfterSignup(). user.Id = 0; var dummyClaim = new CustomUserClaim { ClaimType = ClaimTypes.UserData, ClaimValue = identityResult.PlainErrorMessage("Signup failed.") }; user.Claims.Add(dummyClaim); } } } }); AppDbConfiguration.SuspendExecutionStrategy = false; return(user); }