在 DAL class ThreadLocal 中创建 SqlConnection 是否安全?
Is it safe to make SqlConnection in a DAL class ThreadLocal?
我有以下 DAL class 层次结构(部分显示),我用它来抽象化对数据库的数据访问。我想以线程安全的方式使用它:
public class DbAdapter : IDbAdapter, IDisposable
{
private SqlConnection _conn;
private readonly string _connString;
protected DbAdapter(string connString)
{
if (string.IsNullOrWhiteSpace(connString))
throw new ArgumentException("Value cannot be null, empty or whitespace", nameof(connString));
_connString = connString;
}
public void Dispose()
{
CloseConnection();
}
public SqlConnection GetConnection()
{
if (_conn == null || _conn.State == ConnectionState.Closed)
_conn = new SqlConnection(_connString);
else if (_conn.State == ConnectionState.Broken)
{
_conn.Close();
_conn.Open();
}
return _conn;
}
public void CloseConnection()
{
if (_conn != null && _conn.State == ConnectionState.Open)
_conn.Close();
_conn = null;
}
public SqlCommand GetCommand(string query, SqlTransaction transaction)
{
var cmd = new SqlCommand
{
Connection = transaction != null ? transaction.Connection : GetConnection()
};
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.CommandTimeout = 500;
return cmd;
}
// Omitted other methods
}
public class PersonDba : DbAdapter
{
//Omitted
public IList<Person> GetPersons(string whereClause, string orderClause, SqlTransaction transaction = null)
{
var query = "SELECT Id, Name, PersonId, Birthdate, Modified FROM Persons ";
if (!string.IsNullOrWhiteSpace(whereClause))
query += whereClause;
if (!string.IsNullOrWhiteSpace(orderClause))
query += orderClause;
IList<Person> result = new List<Person>();
var sqlCmd = GetCommand(query, transaction);
using (var reader = sqlCmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
var person = new Person
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
PersonId = reader.GetInt32(reader.GetOrdinal("PersonId")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Birthdate = reader.GetDateTime(reader.GetOrdinal("Birthdate")),
LastModified = reader.GetDateTime(reader.GetOrdinal("Modified"))
};
result.Add(person);
}
}
return result;
}
}
通常,我将 PersonDba
的单个实例注入其他 classes,其中包含多线程代码。为了避免在相关代码中锁定对单个实例的所有访问,我正在考虑制作 ThreadLocal<SqlConnection>
类型的 SQLConnection DbAdapter._conn
(请参阅 ThreadLocal)。这足以确保使用此 class 的实例是线程安全的吗?
假设您希望在未来通过使用 ExecuteReaderAsync
而不是 ExecuteReader
来保持使您的 DAL 异步的可能性,然后转换 _conn
的字段class DbAdapter
到 ThreadLocal<SqlConnection>
不安全。原因是在等待异步请求之后,异步工作流很可能会在另一个 ThreadPool
线程上继续。所以错误的连接将被关闭,一些其他不相关的并发数据访问操作可能会被中断并中止。
我有以下 DAL class 层次结构(部分显示),我用它来抽象化对数据库的数据访问。我想以线程安全的方式使用它:
public class DbAdapter : IDbAdapter, IDisposable
{
private SqlConnection _conn;
private readonly string _connString;
protected DbAdapter(string connString)
{
if (string.IsNullOrWhiteSpace(connString))
throw new ArgumentException("Value cannot be null, empty or whitespace", nameof(connString));
_connString = connString;
}
public void Dispose()
{
CloseConnection();
}
public SqlConnection GetConnection()
{
if (_conn == null || _conn.State == ConnectionState.Closed)
_conn = new SqlConnection(_connString);
else if (_conn.State == ConnectionState.Broken)
{
_conn.Close();
_conn.Open();
}
return _conn;
}
public void CloseConnection()
{
if (_conn != null && _conn.State == ConnectionState.Open)
_conn.Close();
_conn = null;
}
public SqlCommand GetCommand(string query, SqlTransaction transaction)
{
var cmd = new SqlCommand
{
Connection = transaction != null ? transaction.Connection : GetConnection()
};
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandType = CommandType.Text;
cmd.CommandText = query;
cmd.CommandTimeout = 500;
return cmd;
}
// Omitted other methods
}
public class PersonDba : DbAdapter
{
//Omitted
public IList<Person> GetPersons(string whereClause, string orderClause, SqlTransaction transaction = null)
{
var query = "SELECT Id, Name, PersonId, Birthdate, Modified FROM Persons ";
if (!string.IsNullOrWhiteSpace(whereClause))
query += whereClause;
if (!string.IsNullOrWhiteSpace(orderClause))
query += orderClause;
IList<Person> result = new List<Person>();
var sqlCmd = GetCommand(query, transaction);
using (var reader = sqlCmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
var person = new Person
{
Id = reader.GetInt32(reader.GetOrdinal("Id")),
PersonId = reader.GetInt32(reader.GetOrdinal("PersonId")),
Name = reader.GetString(reader.GetOrdinal("Name")),
Birthdate = reader.GetDateTime(reader.GetOrdinal("Birthdate")),
LastModified = reader.GetDateTime(reader.GetOrdinal("Modified"))
};
result.Add(person);
}
}
return result;
}
}
通常,我将 PersonDba
的单个实例注入其他 classes,其中包含多线程代码。为了避免在相关代码中锁定对单个实例的所有访问,我正在考虑制作 ThreadLocal<SqlConnection>
类型的 SQLConnection DbAdapter._conn
(请参阅 ThreadLocal)。这足以确保使用此 class 的实例是线程安全的吗?
假设您希望在未来通过使用 ExecuteReaderAsync
而不是 ExecuteReader
来保持使您的 DAL 异步的可能性,然后转换 _conn
的字段class DbAdapter
到 ThreadLocal<SqlConnection>
不安全。原因是在等待异步请求之后,异步工作流很可能会在另一个 ThreadPool
线程上继续。所以错误的连接将被关闭,一些其他不相关的并发数据访问操作可能会被中断并中止。