A valid use of UseTransaction throws an exception

Discussion of open issues, suggestions and bugs regarding ADO.NET provider for Oracle
Post Reply
TheCoolest
Posts: 22
Joined: Tue 24 Mar 2020 11:02

A valid use of UseTransaction throws an exception

Post by TheCoolest » Thu 09 Jun 2022 15:49

This is another regression we are seeing after switching from 9.14.1180 on .NET Core 3.1 to 9.16.1434 and .NET 6.
Although the way to reproduce it is a bit convoluted, it used to work correctly with the older build, and it works properly with MSSQL and SQLite.
Exception:

Code: Select all

Unhandled exception. System.InvalidOperationException: The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.
   at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction..ctor(IRelationalConnection connection, DbTransaction transaction, Guid transactionId, IDiagnosticsLogger`1 logger, Boolean transactionOwned, ISqlGenerationHelper sqlGenerationHelper)
   at Devart.Common.Entity.c0..ctor(IRelationalConnection A_0, DbTransaction A_1, Guid A_2, IDiagnosticsLogger`1 A_3, Boolean A_4, ISqlGenerationHelper A_5)
   at Devart.Data.Oracle.Entity.at..ctor(IRelationalConnection A_0, DbTransaction A_1, Guid A_2, IDiagnosticsLogger`1 A_3, Boolean A_4, ISqlGenerationHelper A_5)
   at Devart.Data.Oracle.Entity.au.Create(IRelationalConnection connection, DbTransaction transaction, Guid transactionId, IDiagnosticsLogger`1 logger, Boolean transactionOwned)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.CreateRelationalTransaction(DbTransaction transaction, Guid transactionId, Boolean transactionOwned)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.UseTransaction(DbTransaction transaction, Guid transactionId)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.UseTransaction(DatabaseFacade databaseFacade, DbTransaction transaction, Guid transactionId)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.UseTransaction(DatabaseFacade databaseFacade, DbTransaction transaction)
Code to reproduce:

Code: Select all

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Data.Common;

namespace efcore_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            test_devart_fail_reuse_transaction_in_new_scope();
            Console.WriteLine("Hello World!");
        }

        private static void test_devart_fail_reuse_transaction_in_new_scope()
        {
            var sp = new devart_fail_reuse_transaction_in_new_scope.ServiceProviderWrapper(devart_fail_reuse_transaction_in_new_scope.DbType.Oracle);
            var seeder = new devart_fail_reuse_transaction_in_new_scope.Seeder(sp);
            seeder.Seed();
            var logic = new devart_fail_reuse_transaction_in_new_scope.Logic(sp);
            logic.ExecuteQuery();
        }
    }
        
    internal class devart_fail_reuse_transaction_in_new_scope
    {
        public enum DbType
        {
            Oracle,
            SQLite,
            MSSQL
        }

        public class ServiceProviderWrapper
        {
            private readonly IServiceProvider _serviceProvider;

            public IServiceProvider ServiceProvider => _serviceProvider;

            public ServiceProviderWrapper(DbType dbType)
            {
                _serviceProvider = new ServiceCollection()
                   .AddLogging(b => b.AddConsole())
                   .AddScoped<DbConnectionHolder>()
                   .AddDbContext<MyDbContext>((sp, options) =>
                   {
                       var conn = sp.GetService<DbConnectionHolder>();
                       options = options.EnableSensitiveDataLogging();
                       if (dbType == DbType.Oracle)
                       {
                           if (conn?.DbConnection != null)
                           {
                               options.UseOracle(conn.DbConnection);
                               System.Diagnostics.Debug.WriteLine("Using Oracle DbConnection: " + conn.DbConnection.ConnectionString);
                           }
                           else
                           {
                               options.UseOracle(OracleConstants.ORACLE_CONNECTION);
                               System.Diagnostics.Debug.WriteLine("Using Oracle hardcoded string");
                           }
                       }
                       else if (dbType == DbType.SQLite)
                       {
                           if (conn?.DbConnection != null)
                           {
                               options.UseSqlite(conn.DbConnection);
                               System.Diagnostics.Debug.WriteLine("Using SQLite DbConnection: " + conn.DbConnection.ConnectionString);
                           }
                           else
                           {
                               options.UseSqlite("Data Source=efcoredemo.db");
                               System.Diagnostics.Debug.WriteLine("Using SQLite hardcoded string");
                           }
                       }
                       else if (dbType == DbType.MSSQL)
                       {
                           if (conn?.DbConnection != null)
                           {
                               options.UseSqlServer(conn.DbConnection);
                               System.Diagnostics.Debug.WriteLine("Using MSSQL DbConnection: " + conn.DbConnection.ConnectionString);
                           }
                           else
                           {
                               options.UseSqlServer("Server=(localdb)\\ProjectsV13;Database=devart_fail_reuse_transaction_in_new_scope;Trusted_Connection=True;MultipleActiveResultSets=true");
                               System.Diagnostics.Debug.WriteLine("Using MSSQL hardcoded string");
                           }
                       }
                   }, ServiceLifetime.Scoped)
                   .BuildServiceProvider();
            }
        }

        public class Logic
        {
            private readonly ServiceProviderWrapper _serviceProviderWrapper;

            public Logic(ServiceProviderWrapper serviceProviderWrapper)
            {
                _serviceProviderWrapper = serviceProviderWrapper;
            }

            public void ExecuteQuery()
            {
                using (var parentScope = _serviceProviderWrapper.ServiceProvider.CreateScope())
                {
                    var parentScopeDbContext = parentScope.ServiceProvider.GetRequiredService<MyDbContext>();
                    var transaction = parentScopeDbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
                    using (var scope = parentScope.ServiceProvider.CreateScope())
                    {
                        var dbTransaction = parentScopeDbContext.Database.CurrentTransaction?.GetDbTransaction();
                        if (dbTransaction != null)
                        {
                            var conn = scope.ServiceProvider.GetRequiredService<DbConnectionHolder>();
                            conn.DbConnection = dbTransaction.Connection;
                            var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
                            dbContext.Database.UseTransaction(dbTransaction);
                        }
                    }
                }
            }
        }

        internal class DbConnectionHolder
        {
            public DbConnection DbConnection { get; set; }
        }

        public class Seeder
        {
            private readonly ServiceProviderWrapper _serviceProviderWrapper;

            public Seeder(ServiceProviderWrapper serviceProviderWrapper)
            {
                _serviceProviderWrapper = serviceProviderWrapper;
            }

            public void Seed()
            {
                using (var scope = _serviceProviderWrapper.ServiceProvider.CreateScope())
                {
                    var dbContext = scope.ServiceProvider.GetService<MyDbContext>();
                    var rdc = dbContext.Database.GetService<IRelationalDatabaseCreator>();
                    if (rdc.Exists())
                        rdc.EnsureDeleted();
                    rdc.EnsureCreated();
                    if (!dbContext.Items.Any())
                    {
                        for (int i = 0; i < 10; i++)
                        {
                            var rand = new Random();
                            int cat = rand.Next() % 3;
                            int colors = rand.Next() % 4;
                            var item = new Item { CategoryId = cat, Name = $"Item {i}" };
                            for (int c = 0; c <= colors; c++)
                            {
                                item.AddColor(c);
                            }
                            dbContext.Items.Add(item);
                        }
                    }
                    if (!dbContext.Categories.Any())
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            var category = new Category { Name = $"Category {i}" };
                            dbContext.Categories.Add(category);
                        }
                    }
                    dbContext.SaveChanges();
                }
            }
        }

        public class Dto
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Colors { get; set; }
        }

        public class Item
        {
            private readonly List<Color> _colors = new List<Color>();
            public IReadOnlyCollection<Color> Colors => _colors;
            public int Id { get; set; }
            public int CategoryId { get; set; }
            public string Name { get; set; }

            internal void AddColor(int i)
            {
                var color = new Color { Name = $"Color {i}" };
                _colors.Add(color);
            }
        }

        public class Color
        {
            public int ItemId { get; private set; }
            public virtual Item Item { get; private set; }
            public int Id { get; set; }
            public string Name { get; set; }
        }

        public class Category
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        public class MyDbContext : DbContext
        {
            public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole();
            });

            public DbSet<Item> Items { get; set; }
            public DbSet<Color> Colors { get; set; }
            public DbSet<Category> Categories { get; set; }

            public MyDbContext(DbContextOptions options) : base(options)
            {
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.ApplyConfiguration(new ItemConfiguration());
                modelBuilder.ApplyConfiguration(new CategoryConfiguration());
            }
        }

        public class ItemConfiguration : IEntityTypeConfiguration<Item>
        {
            public void Configure(EntityTypeBuilder<Item> builder)
            {
                builder.ToTable("Item");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.CategoryId).HasColumnName("categoryId");
                builder.Property(t => t.Name).HasColumnName("name");
            }
        }

        public class ColorConfiguration : IEntityTypeConfiguration<Color>
        {
            public void Configure(EntityTypeBuilder<Color> builder)
            {
                builder.ToTable("Color");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.Name).HasColumnName("name");

                builder.HasOne(s => s.Item)
                    .WithMany(s => s.Colors)
                    .HasForeignKey(s => s.ItemId);
            }
        }

        public class CategoryConfiguration : IEntityTypeConfiguration<Category>
        {
            public void Configure(EntityTypeBuilder<Category> builder)
            {
                builder.ToTable("Category");
                builder.HasKey(o => o.Id);
                builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
                builder.Property(t => t.Name).HasColumnName("name");
            }
        }
    }
}

TheCoolest
Posts: 22
Joined: Tue 24 Mar 2020 11:02

Re: A valid use of UseTransaction throws an exception

Post by TheCoolest » Mon 04 Jul 2022 15:22

Any updates on this? This issue was not resolved in 10.0.0.

TheCoolest
Posts: 22
Joined: Tue 24 Mar 2020 11:02

Re: A valid use of UseTransaction throws an exception

Post by TheCoolest » Fri 12 Aug 2022 08:05

Can anyone give an update? It's been over 2 months.

Also, please get rid of the spam one post above.

DmitryGm
Devart Team
Posts: 152
Joined: Fri 11 Dec 2020 10:27

Re: A valid use of UseTransaction throws an exception

Post by DmitryGm » Tue 30 Aug 2022 13:24

Thank you for your report. We have reproduced the issue and the investigation is in progress. We will inform you as soon as we have any results.

Post Reply