dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.72k stars 3.17k forks source link

Issue: TransactionScope Error - "The current TransactionScope is already complete. You should dispose the TransactionScope." #33812

Closed microappstech closed 3 months ago

microappstech commented 4 months ago

I'm encountering an error with TransactionScope in my ASP.NET Core application. The error message is:

Error: System.InvalidOperationException: This connection was used with an ambient transaction. The original ambient transaction needs to be completed before this connection can be used outside of it.

   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.HandleAmbientTransactions()

   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean errorsExpected)

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject)

   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(Enumerator enumerator)

   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.<>c.<MoveNext>b__21_0(DbContext _, Enumerator enumerator)

   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)

   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()

   at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found)

   at lambda_method557(Closure, QueryContext)

   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)

   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)

   at Radzen.Blazor.RadzenDataGrid`1.get_View()

   at Radzen.PagedDataBoundComponent`1.get_PagedView()

   at Radzen.Blazor.RadzenDataGrid`1.DrawGroupOrDataRows(RenderTreeBuilder builder, IList`1 visibleColumns)

   at Radzen.Blazor.RadzenDataGrid`1.<>c__DisplayClass20_0.<DrawRows>b__0(RenderTreeBuilder builder)

   at Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(Int32 sequence, RenderFragment fragment)

   at Radzen.Blazor.RadzenDataGrid`1.<BuildRenderTree>b__641_0(RenderTreeBuilder __builder2)

   at Microsoft.AspNetCore.Components.CascadingValue`1.Render(RenderTreeBuilder builder)

   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)

Here's the relevant sample code:

public async Task HandleValidate()
{
    using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        try
        {
            Errors = new List<string>();

            if (Manager.Id == 0 && !IsEdit)
            {
                ApplicationUser _user = new ApplicationUser()
                {
                    Email = Manager.Email,
                    UserName = Manager.Email,
                    Password = Manager.Password,
                    NormalizedUserName = Manager.FullName,
                    PhoneNumber = Manager.Phone,
                    FullName = Manager.FullName,
                };

                var TaskUser = await Security!.RegisterUser(_user);
                if (TaskUser.Succeeded)
                {
                    var role = await Security!.AddRoleToUser(TaskUser.Value, "MANAGER");
                    var result = crudRepository!.Add(Manager);
                    if (result.Succeeded)
                    {
                        Notify("Success", "Item created successfully", NotificationSeverity.Success);
                        dialogService.Close();
                    }
                    else
                    {
                        Errors.Add(result.Error);
                        return; 
                    }
                }
                else
                {
                    Errors.Add(TaskUser.Error);
                    return;
                }
            }
            else
            {
                var result = crudRepository.Update(Manager);
                if (result.Succeeded)
                {
                    Notify("Success", "Item updated successfully", NotificationSeverity.Success);
                    dialogService.Close();
                }
                else
                {
                    Errors.Add(result.Error);
                }
            }

            scope.Complete();
        }
        catch (Exception ex)
        {
            Notify("Error", "Something went wrong! Please try again", NotificationSeverity.Error);
        }
    }
}

Aditional Context :

Environment : .NET Version: .NET Core 8 Blazor IDE: Visual Studio 2022 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: 8 =>net8.0 EF Version 8.0.2 =>

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.2" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
</ItemGroup>

Operating system: Windows 11

AndriySvyryd commented 3 months ago

This happens because the connection is opened and closed transparently for each operation. To prevent this call context.Database.OpenConnectionAsync() and context.Database.CloseConnection() or switch to regular transactions (context.Database.BeginTransactionAsync()). I understand that this exposes some implementation details, but currently it's the recommended way to get transactional behavior as TransactionScope isn't very reliable in async code.