带有 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 适合您的示波器的正确级别。

请告诉我这个答案是否足以解决您的问题。

此致, 马里奥