Unexpected implicit transaction
See original GitHub issueDescribe 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:
- Created 6 months ago
- Comments:8 (3 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
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.Yes, sure. Go on, PR’s are always welcome. Currently I have no free time.