Execution strategy 'SqlServerRetryingExecutionStrategy' does not support user-initiated transactions
Asked Answered
F

1

14

I have an ASP.NET Core 5 MVC web application, working with Entity Framework Core 5.

We implement web components, like Grid or Spreadsheet (we work with Telerik). When I do some changes in the component, and then try to save the changes, the component calls my ApplicationDbContext.SaveChanges. And then I get the following error:

System.InvalidOperationException: The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user-initiated transactions. Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.

enter image description here

Here is my method where I ConfigureServices for the DBContext:

public static void AddDbContext(this IServiceCollection services, string connectionString) =>
    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseSqlServer(connectionString,
            providerOptions =>
            {
                providerOptions
                    .EnableRetryOnFailure(
                        maxRetryCount: 5, 
                        maxRetryDelay: TimeSpan.FromSeconds(30),
                        errorNumbersToAdd: null)
                    .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
            });
        options.EnableSensitiveDataLogging();
        options.ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning));
    });

That error does not happen systematically. For sure when I try to save to DB multiple elements. But also when only one element. Does not happen when I edit or same an element one by one, in a classic form, only when it happens in multiple objects scenario (Grid or Spreadsheet)...

The StackTrace would be like this: enter image description here

As example, I give bellow a method allowing to submit Entreprise objects, from a Spreadsheet to the DB, in order to use CreateExecutionStrategy method:

public ActionResult Data_Source_Entreprises_Submit(SpreadsheetSubmitViewModel model)
{
    var result = new SpreadsheetSubmitViewModel()
    {
        Created = new List<SpreadsheetEntrepriseViewModel>(),
        Updated = new List<SpreadsheetEntrepriseViewModel>(),
        Destroyed = new List<SpreadsheetEntrepriseViewModel>()
    };

    if ((model.Created != null || model.Updated != null || model.Destroyed != null) && ModelState.IsValid)
    {
        /*             executionStrategy                                 */
        var executionStrategy = _dbContext.Database.CreateExecutionStrategy();

        executionStrategy.Execute(() =>
            {
                using (var transaction = _dbContext.Database.BeginTransaction())
                {
                    try
                    {
                        ///////// Business Logic //////////////////
                        if (model.Created != null)
                        {
                            foreach (var created in model.Created)
                            {
                                EntrepriseDTO dto = new EntrepriseDTO() { Nom = created.Nom };
                                var createdDto = _entrepriseService.Create(dto);
                                created.Id = createdDto.Id;
                                result.Created.Add(created);
                            }
                        }

                        if (model.Updated != null)
                        {
                            foreach (var updated in model.Updated)
                            {
                                var spec = new EntrepriseForDetailsSpecification(updated.Id);
                                var dto = _entrepriseService.Read(spec);
                                dto.Nom = updated.Nom;
                                _entrepriseService.Update(dto);
                                result.Updated.Add(updated);
                            }
                        }

                        if (model.Destroyed != null)
                        {
                            foreach (var destroyed in model.Destroyed)
                            {
                                _entrepriseService.Delete(destroyed.Id);
                                result.Destroyed.Add(destroyed);
                            }
                        }
                        ///////// END Business Logic ///////////////

                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        transaction.Rollback();
                    }
                }
            }
        );
        /*             END executionStrategy                   */
        
        return Json(result);
    }
    else
    {
        return StatusCode(400, "The models contain invalid property values.");
    }
}

as result I had the following error:

The connection does not support MultipleActiveResultSets

Then I modified the connection string, including MultipleActiveResultSets=True, but finally obtained another error:

A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

I don't use in that code asynchronous code, I don't know what is wrong...

Faber answered 25/4, 2021 at 14:39 Comment(0)
F
16

I needed to verify all my repository Async calls, and see if all of them was "awaited".

I found one that was not "awaited"... fixing that permitted me to save the objects from the Grid(or Spreadsheet) in the DB. More information is in the MS docs on EF Core Asynchronous Programming...

Faber answered 26/4, 2021 at 14:9 Comment(1)
For me, I put it in an async function that I forgot to await.Remarque

© 2022 - 2024 — McMap. All rights reserved.