C# 在DAL类ThreadLocal中创建SqlConnection安全吗?
我有下面的DAL类层次结构(部分显示),用于抽象对数据库的数据访问。我希望以线程安全的方式使用它:C# 在DAL类ThreadLocal中创建SqlConnection安全吗?,c#,.net,sql-server,multithreading,sqlconnection,C#,.net,Sql Server,Multithreading,Sqlconnection,我有下面的DAL类层次结构(部分显示),用于抽象对数据库的数据访问。我希望以线程安全的方式使用它: public class DbAdapter : IDbAdapter, IDisposable { private SqlConnection _conn; private readonly string _connString; protected DbAdapter(string connString) { if (string.IsNull
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;
}
}
公共类DbAdapter:IDbAdapter,IDisposable
{
专用SqlConnection _conn;
私有只读字符串_connString;
受保护的DbAdapter(字符串连接字符串)
{
if(string.IsNullOrWhiteSpace(connString))
抛出新ArgumentException(“值不能为null、空或空白”,nameof(connString));
_connString=connString;
}
公共空间处置()
{
CloseConnection();
}
公共SqlConnection GetConnection()
{
if(_conn==null | | | u conn.State==ConnectionState.Closed)
_conn=新的SqlConnection(\u connString);
else if(_conn.State==ConnectionState.breaked)
{
_康涅狄格州关闭();
_conn.Open();
}
返回连接;
}
公共连接()
{
if(_conn!=null&&u conn.State==ConnectionState.Open)
_康涅狄格州关闭();
_conn=null;
}
公共SqlCommand GetCommand(字符串查询、SqlTransaction事务)
{
var cmd=new SqlCommand
{
连接=事务!=null?事务。连接:GetConnection()
};
if(事务!=null)
cmd.Transaction=Transaction;
cmd.CommandType=CommandType.Text;
cmd.CommandText=查询;
cmd.CommandTimeout=500;
返回cmd;
}
//省略了其他方法
}
公共类PersonDba:DbAdapter
{
//省略
public IList GetPersons(string where子句、string order子句、SqlTransaction=null)
{
var query=“选择Id、姓名、PersonId、出生日期、修改自Persons”;
如果(!string.IsNullOrWhiteSpace(whereClause))
query+=where子句;
如果(!string.IsNullOrWhiteSpace(orderClause))
query+=order子句;
IList结果=新列表();
var sqlCmd=GetCommand(查询、事务);
使用(var reader=sqlCmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while(reader.Read())
{
var 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”))
};
结果:增加(人);
}
}
返回结果;
}
}
通常,我将
PersonDba
的单个实例注入到其他类中,这些类包含多线程代码。为了避免在依赖代码中锁定对单个实例的所有访问,我正在考虑将SQLConnectionDbAdapter.\u conn
类型设置为ThreadLocal
(请参阅)。这足以确保使用此类的实例是线程安全的吗?假设您希望通过使用而不是ExecuteReader,在将来保持DAL异步的可能性,然后将类DbAdapter
的字段\u conn
转换为ThreadLocal
是不安全的。原因是在等待异步请求之后,异步工作流很可能会在另一个ThreadPool
线程上继续。因此,错误的连接将被关闭,其他一些不相关的并发数据访问操作可能会被中断和中止。这是一个糟糕的抽象,因为您强制消费者将where
子句构造为字符串,这意味着他们不能使用最合适的方法来避免SQL注入问题——参数。你有一个更大的设计问题。每个线程都应该有自己的repo对象和后续数据库连接。您的PersonDba
和基类是执行repo模式的一种奇怪的方式。这主要是主观的,但我强烈建议不要这样做,因为它根本不适用于async
,因为在这里,您可以在线程方面到处跳跃。好的,您可以通过使用异步本地或执行上下文来存储环境数据来避开这一步,但这仍然是一个非常糟糕的主意。IMO:显式地传递它,就像您处理事务一样(事实上,由于事务和连接是深度连接的,以不同的方式传递它们应该是一个巨大的危险信号)。当然,您可以使它成为本地线程。强烈地考虑咬子弹和重构这些东西,但是,第一个开始可能是使<代码>闭包< /代码> />代码> GETCOMPION/CODE >接受它作为参数,并开始重写链上的呼叫者以传递他们得到的连接。然后可以很容易地扩展它,使它们使用适当的模式。这个设计已经被打破了;添加线程本地存储只会让事情变得更加混乱和不可靠,而不是更少;另外-where/order by子句作为字符串只是请求SQL注入我正在考虑一个非静态ThreadLocal\u conn变量。此外,当前代码是同步的,如果在消费类中可以保证线程安全执行,那么也可以保持同步。@baha如果您同意永远在同步模型中受约束,那么ThreadLocal
是安全的。是的,将其设置为静态是没有意义的,因为您可能希望为不同的数据库维护每个线程的多个连接。我会更新我的答案。