Я тестирую пользовательский класс UnitOfWork
с System.Data.SqlClient
объектами, он мне нужен для управления возможной одновременной транзакцией и подключением к БД для некоторых простых операций ado.net.
У меня тупик между первым или третьим завершенным выполнением, во втором выберите в классе Repo1 или Repo2, один после команды вставки.
Это происходит даже при "Serializable" изоляцииуровень.
Я ожидаю, что даже в отдельной задаче программа может выполнить все транзакции без конфликтов, таким образом, что каждая транзакция ставится в очередь в произвольном порядке, но, возможно, я просто использую неправильный уровень изоляции.
Мое намерение состоит в следующем
в той же транзакции: сделать выбор в таблице после вставки, и я ожидаю получить значение, вставленное в предыдущую команду ната же транзакция.
в командах вне этой транзакции: я ожидаю, что выборка считывает фактические зафиксированные значения, игнорируя незафиксированные значения
Этомой код
public class AdoNetUnitOfWork : IDisposable
{
SqlConnection _connection;
bool _ownsConnection;
SqlTransaction _transaction;
public AdoNetUnitOfWork(SqlConnection connection, bool ownsConnection)
{
_connection = connection;
_ownsConnection = ownsConnection;
_transaction = connection.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
}
public SqlCommand CreateCommand()
{
var command = _connection.CreateCommand();
command.Transaction = _transaction;
return command;
}
public void SaveChanges()
{
if (_transaction == null)
throw new InvalidOperationException("Transaction have already been commited. Check your transaction handling.");
_transaction.Commit();
_transaction = null;
}
public void Dispose()
{
if (_transaction != null)
{
_transaction.Rollback();
_transaction = null;
}
if (_connection != null && _ownsConnection)
{
_connection.Close();
_connection = null;
}
}
}
public class UnitOfWorkFactory
{
public static AdoNetUnitOfWork Create()
{
var connection = new SqlConnection(
"Data Source=localhost;Initial Catalog=***;Integrated Security=False;User Id=**;Password=***;MultipleActiveResultSets=True");
connection.Open();
return new AdoNetUnitOfWork(connection, true);
}
}
Класс бизнес-логики:
public class Service
{
public Service()
{
}
public OutputStr MakeCalls()
{
using (var uow = UnitOfWorkFactory.Create())
{
var repo1 = new Repo1(uow);
repo1.ExecuteQuery(out int idStart1, out int idEnd1);
var repo2 = new Repo2(uow);
repo2.ExecuteQuery(out int idStart2, out int idEnd2);
OutputStr str = new OutputStr()
{
IdStart1 = idStart1, IdEnd1 = idEnd1,
IdStart2 = idStart2, IdEnd2 = idEnd2
};
uow.SaveChanges();
return str;
}
}
}
public class OutputStr
{
public int IdStart1 { get; set; }
public int IdEnd1 { get; set; }
public int IdStart2 { get; set; }
public int IdEnd2 { get; set; }
}
Класс выполнения запроса к таблице 1:
public class Repo1
{
AdoNetUnitOfWork _uow;
public Repo1(AdoNetUnitOfWork uow)
{
_uow = uow;
}
public void ExecuteQuery(out int idStart, out int idEnd)
{
SqlCommand cmd = _uow.CreateCommand();
cmd.CommandText = "SELECT COALESCE(MAX(Id),0) from Table1";
idStart = int.Parse(cmd.ExecuteScalar().ToString());
cmd.CommandText = @"
INSERT INTO Table1
([Id])
SELECT COALESCE(MAX(Id),0)+1
from Table1";
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT COALESCE(MAX(Id),0) from Table1";
idEnd = int.Parse(cmd.ExecuteScalar().ToString());
//idEnd = 0;
}
}
Скрипт для создания таблицы1:
CREATE TABLE [dbo].[Table1]
(
[Id] [INT] NOT NULL
) ON [PRIMARY]
GO
Класс выполнения запроса2
public class Repo2
{
AdoNetUnitOfWork _uow;
public Repo2(AdoNetUnitOfWork uow)
{
_uow = uow;
}
public void ExecuteQuery(out int idStart, out int idEnd)
{
SqlCommand cmd = _uow.CreateCommand();
cmd.CommandText = "SELECT COALESCE(MAX(Id),0) from Table2";
idStart = int.Parse(cmd.ExecuteScalar().ToString());
cmd.CommandText = @"INSERT INTO Table2 ([Id])
SELECT COALESCE(MAX(Id),0)+1
FROM Table2";
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT COALESCE(MAX(Id), 0) FROM Table2";
idEnd = int.Parse(cmd.ExecuteScalar().ToString());
//idEnd = 0;
}
}
Скрипт для создания таблицы 2
CREATE TABLE [dbo].[Table2]
(
[Id] [INT] NOT NULL
) ON [PRIMARY]
GO
Консольный класс
class Program
{
static void Main(string[] args)
{
Service svc = new Service();
for (int i = 0; i < 20; i++)
{
Console.WriteLine($"Current call {i}");
new Task(() => Printer(svc, i)).Start();
}
Console.ReadKey();
}
private static void Printer(Service svc, int index)
{
OutputStr str = svc.MakeCalls();
Console.WriteLine($@"
start1:{str.IdStart1}, end1:{str.IdEnd1},
start2:{str.IdStart2}, end2:{str.IdEnd2}");
}
}
РЕДАКТИРОВАТЬ: решено с помощью этой команды в базе данных
SELECT is_read_committed_snapshot_on, snapshot_isolation_state_desc,snapshot_isolation_state
FROM sys.databases WHERE name='db'
ALTER DATABASE db SET allow_snapshot_isolation ON
ALTER DATABASE db SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE db SET read_committed_snapshot ON
ALTER DATABASE db SET MULTI_USER
SELECT is_read_committed_snapshot_on, snapshot_isolation_state_desc,snapshot_isolation_state
FROM sys.databases WHERE name='db'
и изменение кода на
_transaction = connection.BeginTransaction(IsolationLevel.Snapshot);