带有 TransactionScope 的 UnitOfWork 数据库没有变化,但通过了 unitTest
UnitOfWork w/ TransactionScope no changes in database but a passing unitTest
尝试使用带有 transactionScope 的 unitOfWork 将实体插入数据库时。我实际上会得到我添加的用户的预期结果 (int expectedUserid = uowFactory.Create().AddBasicUser(user);)。但是在处理 UnitOfWork 和 transactionScope 之后,我看不到数据库有任何变化。这让我相信我在 UnitOfWork 中错误地实现了 transactionScope。
我还注意到,当 运行(调试)以下 unitTest 并在以下行上有一个断点:60 时,我无法查询 table 的 'BasicUser' 数据库] 通过 mssql mngmnt studio(已阻止,只是说:正在执行查询...)。
Said UnitTest (Image link)
单元测试本身通过了。
问题:为什么 UnitOfWork 似乎按预期工作,但我没有在数据库中看到任何变化?以及在 transactionScope/UnitOfWork 已经被释放的情况下,Db 是如何被阻塞的。
非常感谢!
参考代码:
IUnitOfWorkFactory
namespace DomainServices
{
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}
}
UnitOfWorkFactory
using System;
using DomainServices;
using System.Transactions;
namespace DataAccess
{
public class UnitOfWorkFactory
{
private readonly IsolationLevel _isolationLevel;
private readonly Func<IsolationLevel, IUnitOfWork> CreateUnitOfWork;
public UnitOfWorkFactory(Func<IsolationLevel, IUnitOfWork> createUnitOfWork, IsolationLevel isolationLevel)
{
_isolationLevel = isolationLevel;
CreateUnitOfWork = createUnitOfWork;
}
public IUnitOfWork Create()
{
return CreateUnitOfWork.Invoke(_isolationLevel);
}
}
}
IUnitOfWork
using System;
namespace DomainServices
{
public interface IUnitOfWork : IDisposable
{
IUserRepository UserRepository { get; }
void Dispose();
void Commit();
}
}
UnitOfWork
using DataAccess.Repository;
using DomainServices;
using System.Transactions;
namespace DataAccess
{
public class UnitOfWork : IUnitOfWork
{
private readonly DatabaseConnection _dbConnection;
private readonly TransactionScope _transactionScope;
private bool isDisposed;
public IUserRepository _userRepository;
public IUserRepository UserRepository
{
get
{
if (this._userRepository == null)
{
this._userRepository = new UserRepository(this._dbConnection, this._transactionScope);
}
return this._userRepository;
}
}
public UnitOfWork(DatabaseConnection dbConnection, IsolationLevel isolationLevel)
{
this.isDisposed = false;
this._dbConnection = dbConnection;
this._transactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TransactionManager.DefaultTimeout
});
}
public void Commit()
{
this._transactionScope.Complete();
}
public void Dispose()
{
this.Dispose(true);
}
protected void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this._transactionScope.Dispose();
}
this.isDisposed = true;
}
}
}
}
IUserRepository
using Domain;
using System.Collections.Generic;
namespace DomainServices
{
public interface IUserRepository
{
int AddBasicUser(BasicUser basicUser);
IEnumerable<BasicUser> GetAllBasicUsers();
}
}
用户资料库
using System.Collections.Generic;
using Domain;
using DomainServices;
using System.Data.SqlClient;
using System;
using System.Transactions;
namespace DataAccess.Repository
{
public class UserRepository : IUserRepository
{
private readonly DatabaseConnection _dbConnection;
private readonly TransactionScope _transaction;
public UserRepository(DatabaseConnection dbConnection, TransactionScope transaction)
{
this._dbConnection = dbConnection;
this._transaction = transaction;
}
public int AddBasicUser(BasicUser basicUser)
{
string QueryString = "INSERT INTO BasicUser (Username, RegisterDate) OUTPUT Inserted.Id VALUES (@username, @registerDate);";
TODO MarWolt: Use a Maybe instead of magic values.
int basicUserId = -1;
//using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(_dbConnection.ConnectionString))
using (SqlCommand cmd = new SqlCommand(QueryString, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("Username", basicUser.UserName.Value);
cmd.Parameters.AddWithValue("RegisterDate", basicUser.RegisterDate);
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
basicUserId = Convert.ToInt32(reader["Id"]);
}
}
scope.Complete();
}
return basicUserId;
}
public IEnumerable<BasicUser> GetAllBasicUsers()
{
var QueryString = "SELECT Id, Username, LastLogin, RegisterDate FROM BasicUser;";
var result = new List<BasicUser>();
using (var conn = new SqlConnection(_dbConnection.ConnectionString))
using (var cmd = new SqlCommand(QueryString, conn))
{
conn.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
result.Add(CreateBasicUserFromReader(reader));
}
return result;
}
}
private BasicUser CreateBasicUserFromReader(SqlDataReader reader)
{
return new BasicUser(
id: Convert.ToInt32(reader["Id"]),
userName: new UserName(Convert.ToString(reader["Username"])),
lastLogin: DateTime.Now, //Convert.ToDateTime(reader["LastLogin"]), //TODO MarWolt This...
registerDate: Convert.ToDateTime(reader["RegisterDate"]),
rideTokens: new List<RideToken>()
);
}
}
}
UnitTestProject
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
using DataAccess;
using DataAccess.Repository;
using Domain;
using DomainServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject
{
[TestClass]
public class UserRepositoryTests
{
private UnitOfWorkFactory uowFactory;
private DatabaseConnection testDatabaseConnection;
public UserRepositoryTests()
{
testDatabaseConnection = new DatabaseConnection(
"Server=.\SQLEXPRESS;Initial Catalog=TestDb;User Id=DbUser;Password=DbUserPassword;Integrated Security=false;Connect Timeout=5;");
uowFactory = new UnitOfWorkFactory((IsolationLevel) => new UnitOfWork(testDatabaseConnection, IsolationLevel), IsolationLevel.ReadCommitted);
}
[TestInitialize()]
public void Initialize()
{
//TODO MarWolt: delete everything from the database on a clean way.
using (SqlConnection conn = new SqlConnection(testDatabaseConnection.ConnectionString))
using (SqlCommand cmd = new SqlCommand(Properties.Resources.ClearDbSqlScript, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
[TestMethod]
public void AddNewBasicUser()
{
var user = new BasicUser(new UserName("UnitTestBasicUserName"), DateTime.UtcNow, new List<RideToken>() { });
var users = uowFactory.Create().UserRepository.GetAllBasicUsers();
Assert.AreEqual(0, users.Count());
using (IUnitOfWork unitOfWork = uowFactory.Create())
{
unitOfWork.UserRepository.AddBasicUser(user);
unitOfWork.Commit();
}
users = uowFactory.Create().UserRepository.GetAllBasicUsers();
Assert.AreEqual(1, users.Count());
var recievedUser = users.First();
Assert.AreEqual(user.UserName.Value, recievedUser.UserName.Value);
// I'm unable to query the database via mssql server management studio when this breakpoint is active.
// I would actually expect it already to be done after line 51.
Console.WriteLine(string.Empty);
}
}
}
您的 TransactionScope 实际上并未包装查询代码。
您注释掉的代码:
//using (TransactionScope scope = new TransactionScope())
是你应该如何做的,还有另一条注释掉的行:
//scope.Complete();
我已经花时间将您的问题完全重新创建到 git 存储库中:SafariUOW。
对我来说效果很好。
Why does it seem the UnitOfWork works as intended, but do i not see any changes in the database?
我假设您的数据库存在读写权限问题,如果它实际上没有 "Commit"。
And how it comes that the Db is blocked while the transactionScope/UnitOfWork is already disposed.
嗯,如果处理了 transactionScope 或 UnitOfWork,应该阻止 Db 访问。可能是您正在创建一个隔离级别不正确的事务。
您可以查看 IsolationLevel msdn docs 适合您的示波器的正确级别。
请告诉我这个答案是否足以解决您的问题。
此致,
马里奥
尝试使用带有 transactionScope 的 unitOfWork 将实体插入数据库时。我实际上会得到我添加的用户的预期结果 (int expectedUserid = uowFactory.Create().AddBasicUser(user);)。但是在处理 UnitOfWork 和 transactionScope 之后,我看不到数据库有任何变化。这让我相信我在 UnitOfWork 中错误地实现了 transactionScope。
我还注意到,当 运行(调试)以下 unitTest 并在以下行上有一个断点:60 时,我无法查询 table 的 'BasicUser' 数据库] 通过 mssql mngmnt studio(已阻止,只是说:正在执行查询...)。
Said UnitTest (Image link)
单元测试本身通过了。
问题:为什么 UnitOfWork 似乎按预期工作,但我没有在数据库中看到任何变化?以及在 transactionScope/UnitOfWork 已经被释放的情况下,Db 是如何被阻塞的。
非常感谢!
参考代码:
IUnitOfWorkFactory
namespace DomainServices
{
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}
}
UnitOfWorkFactory
using System;
using DomainServices;
using System.Transactions;
namespace DataAccess
{
public class UnitOfWorkFactory
{
private readonly IsolationLevel _isolationLevel;
private readonly Func<IsolationLevel, IUnitOfWork> CreateUnitOfWork;
public UnitOfWorkFactory(Func<IsolationLevel, IUnitOfWork> createUnitOfWork, IsolationLevel isolationLevel)
{
_isolationLevel = isolationLevel;
CreateUnitOfWork = createUnitOfWork;
}
public IUnitOfWork Create()
{
return CreateUnitOfWork.Invoke(_isolationLevel);
}
}
}
IUnitOfWork
using System;
namespace DomainServices
{
public interface IUnitOfWork : IDisposable
{
IUserRepository UserRepository { get; }
void Dispose();
void Commit();
}
}
UnitOfWork
using DataAccess.Repository;
using DomainServices;
using System.Transactions;
namespace DataAccess
{
public class UnitOfWork : IUnitOfWork
{
private readonly DatabaseConnection _dbConnection;
private readonly TransactionScope _transactionScope;
private bool isDisposed;
public IUserRepository _userRepository;
public IUserRepository UserRepository
{
get
{
if (this._userRepository == null)
{
this._userRepository = new UserRepository(this._dbConnection, this._transactionScope);
}
return this._userRepository;
}
}
public UnitOfWork(DatabaseConnection dbConnection, IsolationLevel isolationLevel)
{
this.isDisposed = false;
this._dbConnection = dbConnection;
this._transactionScope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = isolationLevel,
Timeout = TransactionManager.DefaultTimeout
});
}
public void Commit()
{
this._transactionScope.Complete();
}
public void Dispose()
{
this.Dispose(true);
}
protected void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this._transactionScope.Dispose();
}
this.isDisposed = true;
}
}
}
}
IUserRepository
using Domain;
using System.Collections.Generic;
namespace DomainServices
{
public interface IUserRepository
{
int AddBasicUser(BasicUser basicUser);
IEnumerable<BasicUser> GetAllBasicUsers();
}
}
用户资料库
using System.Collections.Generic;
using Domain;
using DomainServices;
using System.Data.SqlClient;
using System;
using System.Transactions;
namespace DataAccess.Repository
{
public class UserRepository : IUserRepository
{
private readonly DatabaseConnection _dbConnection;
private readonly TransactionScope _transaction;
public UserRepository(DatabaseConnection dbConnection, TransactionScope transaction)
{
this._dbConnection = dbConnection;
this._transaction = transaction;
}
public int AddBasicUser(BasicUser basicUser)
{
string QueryString = "INSERT INTO BasicUser (Username, RegisterDate) OUTPUT Inserted.Id VALUES (@username, @registerDate);";
TODO MarWolt: Use a Maybe instead of magic values.
int basicUserId = -1;
//using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(_dbConnection.ConnectionString))
using (SqlCommand cmd = new SqlCommand(QueryString, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("Username", basicUser.UserName.Value);
cmd.Parameters.AddWithValue("RegisterDate", basicUser.RegisterDate);
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
basicUserId = Convert.ToInt32(reader["Id"]);
}
}
scope.Complete();
}
return basicUserId;
}
public IEnumerable<BasicUser> GetAllBasicUsers()
{
var QueryString = "SELECT Id, Username, LastLogin, RegisterDate FROM BasicUser;";
var result = new List<BasicUser>();
using (var conn = new SqlConnection(_dbConnection.ConnectionString))
using (var cmd = new SqlCommand(QueryString, conn))
{
conn.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
result.Add(CreateBasicUserFromReader(reader));
}
return result;
}
}
private BasicUser CreateBasicUserFromReader(SqlDataReader reader)
{
return new BasicUser(
id: Convert.ToInt32(reader["Id"]),
userName: new UserName(Convert.ToString(reader["Username"])),
lastLogin: DateTime.Now, //Convert.ToDateTime(reader["LastLogin"]), //TODO MarWolt This...
registerDate: Convert.ToDateTime(reader["RegisterDate"]),
rideTokens: new List<RideToken>()
);
}
}
}
UnitTestProject
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
using DataAccess;
using DataAccess.Repository;
using Domain;
using DomainServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject
{
[TestClass]
public class UserRepositoryTests
{
private UnitOfWorkFactory uowFactory;
private DatabaseConnection testDatabaseConnection;
public UserRepositoryTests()
{
testDatabaseConnection = new DatabaseConnection(
"Server=.\SQLEXPRESS;Initial Catalog=TestDb;User Id=DbUser;Password=DbUserPassword;Integrated Security=false;Connect Timeout=5;");
uowFactory = new UnitOfWorkFactory((IsolationLevel) => new UnitOfWork(testDatabaseConnection, IsolationLevel), IsolationLevel.ReadCommitted);
}
[TestInitialize()]
public void Initialize()
{
//TODO MarWolt: delete everything from the database on a clean way.
using (SqlConnection conn = new SqlConnection(testDatabaseConnection.ConnectionString))
using (SqlCommand cmd = new SqlCommand(Properties.Resources.ClearDbSqlScript, conn))
{
conn.Open();
cmd.ExecuteNonQuery();
}
}
[TestMethod]
public void AddNewBasicUser()
{
var user = new BasicUser(new UserName("UnitTestBasicUserName"), DateTime.UtcNow, new List<RideToken>() { });
var users = uowFactory.Create().UserRepository.GetAllBasicUsers();
Assert.AreEqual(0, users.Count());
using (IUnitOfWork unitOfWork = uowFactory.Create())
{
unitOfWork.UserRepository.AddBasicUser(user);
unitOfWork.Commit();
}
users = uowFactory.Create().UserRepository.GetAllBasicUsers();
Assert.AreEqual(1, users.Count());
var recievedUser = users.First();
Assert.AreEqual(user.UserName.Value, recievedUser.UserName.Value);
// I'm unable to query the database via mssql server management studio when this breakpoint is active.
// I would actually expect it already to be done after line 51.
Console.WriteLine(string.Empty);
}
}
}
您的 TransactionScope 实际上并未包装查询代码。
您注释掉的代码:
//using (TransactionScope scope = new TransactionScope())
是你应该如何做的,还有另一条注释掉的行:
//scope.Complete();
我已经花时间将您的问题完全重新创建到 git 存储库中:SafariUOW。 对我来说效果很好。
Why does it seem the UnitOfWork works as intended, but do i not see any changes in the database?
我假设您的数据库存在读写权限问题,如果它实际上没有 "Commit"。
And how it comes that the Db is blocked while the transactionScope/UnitOfWork is already disposed.
嗯,如果处理了 transactionScope 或 UnitOfWork,应该阻止 Db 访问。可能是您正在创建一个隔离级别不正确的事务。 您可以查看 IsolationLevel msdn docs 适合您的示波器的正确级别。
请告诉我这个答案是否足以解决您的问题。
此致, 马里奥