【发布时间】:2020-09-24 09:14:01
【问题描述】:
我有以下 DAL 类层次结构(部分显示),用于抽象对数据库的数据访问。我想以线程安全的方式使用它:
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 的单个实例注入到包含多线程代码的其他类中。为了避免在依赖代码中锁定对该单个实例的所有访问,我正在考虑创建ThreadLocal<SqlConnection> 类型的SQLConnection DbAdapter._conn(请参阅ThreadLocal)。这足以确保使用此类的实例是线程安全的吗?
【问题讨论】:
-
这是一个糟糕的抽象,因为您强制消费者将
where子句构造为字符串,这意味着他们无法使用最合适的方式来避免 SQL 注入问题 - 参数。跨度> -
你有一个更大的设计问题。 每个线程都应该有自己的 repo 对象和后续的数据库连接。你的
PersonDba和基类是一种奇怪的方式来做一个回购模式 -
这主要是主观的,但我会强烈建议不要这样做,尤其是因为它根本无法与
async一起工作,你可能会到处跳线程方面的位置。好的,您可以通过使用 async-local 或 execution-context 来存储环境数据来避开这一点,但这仍然是一个非常糟糕的主意。 IMO:明确地传递它,就像你处理事务一样(事实上,由于事务和连接是紧密相连的,以不同的方式传递它们应该是一个巨大的危险信号) -
当然,你可以让它成为线程本地的。不过,强烈考虑咬紧牙关重构这些东西——第一个开始可能是让
CloseConnection/GetCommand接受它作为参数,然后开始重写调用者链以传递他们获得的连接。然后可以轻松扩展它以使它们使用正确的using模式。这个设计已经被打破了;添加线程本地存储只会让事情变得更加混乱和不可靠,而不是更少。 -
旁注:你在这里做了很多工作,像“Dapper”这样的工具可以为你做的更简单;还有 - 作为字符串的 where/order-by 子句只是要求 SQL 注入
标签: c# .net sql-server multithreading sqlconnection