Beispiel #1
0
        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));
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        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));
        }
Beispiel #5
0
        /// <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;
            }
        }
Beispiel #6
0
        /// <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);
        }
Beispiel #7
0
        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);
        }