簡體   English   中英

嵌套的SqlConnection.Open在TransactionScope中拋出異常

[英]Nested SqlConnection.Open throws an exception inside TransactionScope

我在我的存儲庫單元測試中使用TransactionScope來回滾測試所做的任何更改。

測試的設置和拆卸過程如下所示:

[TestFixture]
public class DeviceRepositoryTests {
    private static readonly string ConnectionString =
        ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;

    private TransactionScope transaction;
    private DeviceRepository repository;

    [SetUp]
    public void SetUp() {
        transaction = new TransactionScope(TransactionScopeOption.Required);
        repository = new DeviceRepository(ConnectionString);
    }

    [TearDown]
    public void TearDown() {
        transaction.Dispose();
    }
}

有問題的測試包括將記錄插入數據庫的代碼和用於檢索這些記錄的CUT。

    [Test]
    public async void GetAll_DeviceHasSensors_ReturnsDevicesWithSensors() {
        int device1Id = AddDevice();
        AddSensor();

        var devices = await repository.GetAllAsync();

        // Asserts
    }

AddDeviceAddSensor方法打開sql連接並在數據庫中插入一行:

    private int AddDevice() {
        var sqlString = "<SQL>";
        using (var connection = CreateConnection()) 
        using (var command = new SqlCommand(sqlString, connection)) {
            var insertedId = command.ExecuteScalar();
            Assert.AreNotEqual(0, insertedId);
            return (int) insertedId;
        }
    }

    private void AddSensor() {
        const string sqlString = "<SQL>";
        using (var connection = CreateConnection()) 
        using (var command = new SqlCommand(sqlString, connection)) {
            var rowsAffected = command.ExecuteNonQuery();
            Assert.AreEqual(1, rowsAffected);
        }
    }

    private SqlConnection CreateConnection() {
        var result = new SqlConnection(ConnectionString);
        result.Open();
        return result;
    }

GetAllAsync方法打開連接,執行查詢,並為每個獲取的行打開新連接以獲取子對象。

public class DeviceRepository {
    private readonly string connectionString;

    public DeviceRepository(string connectionString) {
        this.connectionString = connectionString;
    }

    public async Task<List<Device>> GetAllAsync() {
        var result = new List<Device>();
        const string sql = "<SQL>";

        using (var connection = await CreateConnection())
        using (var command = GetCommand(sql, connection, null))
        using (var reader = await command.ExecuteReaderAsync()) {
            while (await reader.ReadAsync()) {
                var device = new Device {
                    Id = reader.GetInt32(reader.GetOrdinal("id"))
                };

                device.Sensors = await GetSensors(device.Id);

                result.Add(device);
            }
        }

        return result;
    }

    private async Task<List<Sensor>> GetSensors(int deviceId) {
        var result = new List<Sensor>();
        const string sql = "<SQL>";

        using (var connection = await CreateConnection()) 
        using (var command = GetCommand(sql, connection, null))
        using (var reader = await command.ExecuteReaderAsync()) {
            while (await reader.ReadAsync()) {
                // Fetch row and add object to result
            }
        }

        return result;
    }

    private async Task<SqlConnection> CreateConnection() {
        var connection = new SqlConnection(connectionString);
        await connection.OpenAsync();
        return connection;
    }
}

問題是,當GetSensors方法調用SqlConnection.Open我得到以下異常:

System.Transactions.TransactionAbortedException : The transaction has aborted.
  ----> System.Transactions.TransactionPromotionException : Failure while attempting to promote transaction.
  ----> System.Data.SqlClient.SqlException : There is already an open DataReader associated with this Command which must be closed first.
  ----> System.ComponentModel.Win32Exception : The wait operation timed out

我可以移動從第一個連接范圍中取出子對象的代碼(這會起作用),但是讓我說我​​不想這樣做。

此異常是否意味着無法在單個TransactionScope內打開與DB的同時連接?

編輯

GetCommand只調用SqlCommand構造GetCommand並進行一些日志記錄。

private static SqlCommand GetCommand(string sql, SqlConnection connection, SqlParameter[] parameters) {
    LogSql(sql);
    var command = new SqlCommand(sql, connection);

    if (parameters != null)
        command.Parameters.AddRange(parameters);

    return command;
}

問題是兩個DataReader對象無法同時打開數據庫(除非啟用了MARS )。 這種限制是設計的。 在我看來,你有幾個選擇:

  1. 在連接字符串上啟用MARS ; 添加此MultipleActiveResultSets=True
  2. 如果沒有必要,請不要使用DataReader 但是你編寫代碼的方式非常必要。
  3. 加載設備填充Sensor屬性。
  4. 使用Dapper,它可以完成所有這些(包括填充Sensor )並且可能更快。

使用Dapper你可以做這樣的事情(你不需要GetSensors ):

public async Task<List<Device>> GetAllAsync() {
    var result = new List<Device>();
    const string sql = "<SQL>";

    using (var connection = await CreateConnection())
    using (var multi = connection.QueryMultiple(sql, parms)) {
        result = multi.Read<Device>().ToList();
        var sensors = multi.Read<Sensors>().ToList();

        result.ForEach(device => device.Sensors =
            sensors.Where(s => s.DeviceId = device.Id).ToList());
    }

    return result;
}

這里你的sql看起來像這樣:

SELECT * FROM Devices
SELECT * FROM Sensors

請參閱Dapper的Multi Mapping文檔

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM