Многопоточность при работе базой данных. Использование DBExecutor
Glossary Item Box
Общие сведения
Использование нескольких потоков при работе с базой данных через UserConnection может привести к проблемам синхронизации старта и коммита транзакций.
ВАЖНО
Проблема возникает при работе с базой данных, даже если DBExecutor не используется напрямую, а используется, например, через EntitySchemaQuery.
ВАЖНО
Поскольку для работы с базой данных используются неуправляемые (unmanaged) ресурсы, то создание экземпляра DBExecutor необходимо оборачивать в оператор using. Или же явно вызывать метод Dispose() для освобождения ресурсов. Подробнее об использовании оператора using можно можно узнать в документации "C# Guide".
Пример неправильного использования
Ниже приведен фрагмент исходного кода, в котором DBExecutor используется неправильно. Нельзя выполнять вызов методов экземпляра DBExecutor в параллельных потоках.
// Создание параллельного потока. var task = new Task(() => { // Использование экземпляра DBExecutor в параллельном потоке. using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) { dbExecutor.StartTransaction(); //... dbExecutor.CommitTransaction(); } }); // Запуск асинхронной задачи в параллельном потоке. // Выполнение программы в основном потоке продолжается дальше. task.Start(); //... var select = (Select)new Select(UserConnection) .Column("Id") .From("Contact") .Where("Name") .IsEqual(Column.Parameter("Supervisor")); // Использование экземпляра DBExecutor в основном потоке приведет к возникновению ошибки, // т.к. экземпляр DBExecutor уже используется в параллельном потоке. using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) { using (IDataReader dataReader = select.ExecuteReader(dbExecutor)) { while (dataReader.Read()) { //... } } }
Пример правильного использования
Ниже приведен фрагмент исходного кода, в котором DBExecutor используется корректно. Вызов методов экземпляра DBExecutor производится последовательно, в одном потоке.
// Первое использование экземпляра DBExecutor в основном потоке. using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) { dbExecutor.StartTransaction(); //... dbExecutor.CommitTransaction(); } //... var select = (Select)new Select(UserConnection) .Column("Id") .From("Contact") .Where("Name") .IsEqual(Column.Parameter("Supervisor")); // Повторное использование экземпляра DBExecutor в основном потоке. using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) { using (IDataReader dataReader = select.ExecuteReader(dbExecutor)) { while (dataReader.Read()) { //... } } }