У меня есть следующая иерархия классов 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
в другие классы, содержащие многопоточный код. Чтобы избежать блокировки всего доступа к этому единственному экземпляру в зависимом коде, я думаю сделать SQLConnection DbAdapter._conn
типа ThreadLocal<SqlConnection>
(см. ThreadLocal). Будет ли этого достаточно, чтобы использование экземпляров этого класса было потокобезопасным?
where
в виде строк, а это означает, что они не могут использовать наиболее подходящий способ избежать проблем с внедрением SQL — параметры. - person Damien_The_Unbeliever   schedule 24.09.2020SqlConnection
между потоками. - person MickyD   schedule 24.09.2020PersonDba
и базовый класс - странный способ сделать шаблон репо - person MickyD   schedule 24.09.2020_conn
ThreadLocal. - person Bahaa   schedule 24.09.2020ExecuteReaderAsync
вместоExecuteReader
в будущем, или можно предположить, что ваш уровень доступа к данным останется синхронным навсегда? - person Theodor Zoulias   schedule 24.09.2020async
, где вы можете прыгать повсюду с точки зрения потоков. Хорошо, вы можете обойти это, используя async-local или контекст выполнения для хранения внешних данных, но это будет все равно очень плохой идеей. IMO: передайте его явно, как и в случае с транзакцией (на самом деле, поскольку транзакция и соединение тесно связаны, их передача разными способами должна быть огромным красным флагом) - person Marc Gravell   schedule 24.09.2020CloseConnection
/GetCommand
принимать его в качестве параметра и начать переписывать вызывающие объекты вверх по цепочке, чтобы передавать полученное соединение. Затем это можно легко расширить, чтобы они использовали правильные шаблоныusing
. Этот дизайн уже сломан; добавление локального хранилища потока только сделает вещи более запутанными и ненадежными, а не менее. - person Jeroen Mostert   schedule 24.09.2020