question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unexpected implicit transaction

See original GitHub issue

Describe your issue

In some situations, when linq2db can’t complete query in one request, it creates transaction. Problem is that this transaction cannot be detected automatically, only by manually checking every query. It would be nice to have an option to disable this behavior, because it looks like client side evaluation - it can lead to unwanted side effects. I spent many hours during deadlock investigation wondering why I have update lock in simple selects 😃

Steps to reproduce

Code:

using System.Data;
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.SqlServer;
using LinqToDB.Mapping;
using Microsoft.Data.SqlClient;

const string serverConnectionString =
    @"Server=localhost;User Id=sa;Password=change_this_password;MultipleActiveResultSets=True;Encrypt=False;";
const string dbConnectionString =
    @"Server=localhost;Database=TestDb;User Id=sa;Password=change_this_password;MultipleActiveResultSets=True;Encrypt=False;";

DataConnection.WriteTraceLine = (message, displayName, t) =>
{
    Console.WriteLine($"{t}: {message} {displayName}");
    Console.WriteLine();
};

CreateDb();

var dataOptions = new DataOptions()
    .UseSqlServer(dbConnectionString, SqlServerVersion.v2017,
        SqlServerProvider.MicrosoftDataSqlClient);
CreateSchema(dataOptions);

using var db = new TestDataConnection(dataOptions);

DataConnection.TurnTraceSwitchOn();

var query = db.Master.Select(x => new
{
    x.Id1,
    Details = x.Details.Select(d => d.DetailValue)
}).FirstOrDefault(x => x.Id1 == 3);

Console.WriteLine(query!.Id1);

static void CreateDb()
{
    using var connection = new SqlConnection(serverConnectionString);
    connection.Open();

    using var command = new SqlCommand("alter database TestDB set single_user with rollback immediate", connection);
    command.ExecuteNonQuery();

    command.CommandText = "drop database if exists TestDb;";
    command.ExecuteNonQuery();

    command.CommandText = "create database TestDb;";
    command.ExecuteNonQuery();
}

void CreateSchema(DataOptions options)
{
    using var cnn = new TestDataConnection(options);

    cnn.DropTable<MasterClass>(throwExceptionIfNotExists: false);
    cnn.CreateTable<MasterClass>();

    cnn.DropTable<DetailClass>(throwExceptionIfNotExists: false);
    cnn.CreateTable<DetailClass>();

    var (masterRecords, detailRecords) = GenerateData();
    var bulkCopyOptions = new BulkCopyOptions { BulkCopyType = BulkCopyType.MultipleRows };
    cnn.Master.BulkCopy(bulkCopyOptions, masterRecords);
    cnn.Detail.BulkCopy(bulkCopyOptions, detailRecords);
}

static (MasterClass[], DetailClass[]) GenerateData()
{
    var master = Enumerable.Range(1, 10)
        .Select(i => new MasterClass { Id1 = i, Id2 = i, Value = "Str" + i })
        .ToArray();

    var detail = master.SelectMany(m => m.Id1 % 2 == 0
            ? Enumerable.Empty<DetailClass>()
            : Enumerable.Range(1, m.Id1)
                .Select(i => new DetailClass
                {
                    DetailId = m.Id1 * 1000 + i,
                    DetailValue = "DetailValue" + m.Id1 * 1000 + i,
                    MasterId = m.Id1
                }))
        .ToArray();

    return (master, detail);
}

class TestDataConnection : DataConnection
{
    public TestDataConnection(DataOptions options) : base(options)
    {
        DataProvider.SqlProviderFlags.DefaultMultiQueryIsolationLevel = IsolationLevel.ReadCommitted;
    }
    
    public ITable<MasterClass> Master => this.GetTable<MasterClass>();
    public ITable<DetailClass> Detail => this.GetTable<DetailClass>();
}

[Table]
class MasterClass
{
    [Column] [PrimaryKey] public int Id1 { get; set; }
    [Column] [PrimaryKey] public int Id2 { get; set; }
    [Column] public string? Value { get; set; }

    [Column] public byte[]? ByteValues { get; set; }

    [Association(ThisKey = nameof(Id1), OtherKey = nameof(DetailClass.MasterId))]
    public List<DetailClass> Details { get; set; } = null!;
}

[Table]
class DetailClass
{
    [Column] [PrimaryKey] public int DetailId { get; set; }
    [Column] public int? MasterId { get; set; }
    [Column] public string? DetailValue { get; set; }
}

csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
    <PackageReference Include="linq2db" Version="5.1.1" />
  </ItemGroup>

</Project>

docker-compose:

version: '3.7'

services:
  sql-server:
    container_name: sql-server
    image: mcr.microsoft.com/mssql/server:2017-latest
    ports:
      - "1433:1433"
    environment:
      SA_PASSWORD: "change_this_password"
      ACCEPT_EULA: "Y"

Generated SQL

Query 1:

DECLARE @take Int -- Int32
SET     @take = 1

SELECT
        [key_data_result].[Id1],
        [key_data_result].[Id2],
        [detail].[DetailValue]
FROM
        (
                SELECT DISTINCT
                        [t1].[Id1],
                        [t1].[Id2]
                FROM
                        (
                                SELECT TOP (@take)
                                        [x].[Id1],
                                        [x].[Id2]
                                FROM
                                        [MasterClass] [x]
                                WHERE
                                        [x].[Id1] = 3
                        ) [t1]
        ) [key_data_result]
                INNER JOIN [DetailClass] [detail] ON [key_data_result].[Id1] = [detail].[MasterId]

Query 2:

DECLARE @take Int -- Int32
SET     @take = 1

SELECT TOP (@take)
        [x].[Id1],
        [x].[Id2]
FROM
        [MasterClass] [x]
WHERE
        [x].[Id1] = 3

Environment details

Linq To DB version: 5.1.1

Database (with version): SQL Server 2017

ADO.NET Provider (with version): Microsoft.Data.SqlClient 5.1.1

Operating system: Windows 10

.NET Version: 6.0

Issue Analytics

  • State:open
  • Created 6 months ago
  • Comments:8 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
sdanylivcommented, Apr 4, 2023

Well, we start transaction to get consistent data on time of the query. Anyway we can disable this behavior in next linq2db version via DataOptions if needed.

0reactions
sdanylivcommented, Apr 6, 2023

Yes, sure. Go on, PR’s are always welcome. Currently I have no free time.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Unexpected implicit transactions in query with ; #2933
I read the documentation and saw that the driver requires explicit transaction blocks to get transactions. It took me a pretty long time...
Read more >
SET IMPLICIT_TRANSACTIONS (Transact-SQL)
When the transaction mode is implicit, no unseen BEGIN TRANSACTION is issued if @@trancount > 0 already. However, any explicit BEGIN TRANSACTION ......
Read more >
How Implicit Transactions Work in SQL Server
In SQL Server, an implicit transaction is when a new transaction is implicitly started when the prior transaction completes, but each ...
Read more >
System.Transaction implicit transaction messing with my ...
I'm trying to use System.Transaction.TransactionScope to create a transaction to call a few stored procedures but it doesn't seem to clean up ...
Read more >
SQL Server Implicit Transaction Mode: Proceed with Caution!
It means that any transactional statement will be implicitly put in an open transaction for you. It's as if an invisible BEGIN TRANSACTION...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found