【问题标题】:Is it safe to make SqlConnection in a DAL class ThreadLocal?在 DAL 类 ThreadLocal 中创建 SqlConnection 是否安全?
【发布时间】: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&lt;SqlConnection&gt; 类型的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


【解决方案1】:

假设您希望在将来保持使 DAL 异步的可能性,通过使用 ExecuteReaderAsync 而不是 ExecuteReader,然后将类 DbAdapter 的字段 _conn 转换为 @987654326 @ 不安全。原因是在等待异步请求之后,异步工作流很可能会在另一个ThreadPool 线程上继续。所以错误的连接会被关闭,其他一些不相关的并发数据访问操作可能会被中断和中止。

【讨论】:

  • 我正在考虑一个 non-static ThreadLocal _conn 变量。此外,目前代码是同步的,如果可以在消费类中保证线程安全执行,也可以保持同步。
  • @Bahaa 如果您可以接受永远被限制在同步模型中,那么ThreadLocal&lt;SqlConnection&gt; 是安全的。是的,将其设为静态没有意义,因为您可能希望为不同的数据库维护每个线程的多个连接。我会更新我的答案。
猜你喜欢
  • 2011-02-05
  • 2015-07-17
  • 2016-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-11
  • 2011-09-22
  • 2011-09-07
相关资源
最近更新 更多