Проблемы с блокировкой в sqlite и SubSonic при использовании транзакций в одном потоке

StackOverflow https://stackoverflow.com/questions/2291364

Вопрос

Я получаю исключения блокировки при попытке использовать транзакции с SubSonic и SQLite.Я использую это из одного потока, и никакие другие процессы не обращаются к моей базе данных, так что я действительно не ожидал никаких подобных проблем.

Если я напишу код, подобный приведенному ниже, я получу исключение при втором вызове Save() внутри цикла - то есть при третьем вызове Save() поверх всех.

       using (TransactionScope ts = new TransactionScope())
       {
            using (SharedDbConnectionScope sharedConnectinScope = new SharedDbConnectionScope())
            { 
                SomeDALObject x = new SomeDALObject()
                x.Property1 = "blah";
                x.Property2 = "blah blah";
                x.Save();

                foreach (KeyValuePair<string, string> attribute in attributes)
                { 
                    AnotherDALObject y = new AnotherDALObject()
                    y.Property1 = attribute.Key
                    y.Property2 = attribute.Value

                    y.Save();  // this is where the exception is raised, on the 2nd time through this loop
                }
            }
       }

Если у меня есть инструкции using(), как указано выше, или если у меня просто есть using (TransactionScope ts = new TransactionScope()) тогда я получаю System.Data.SQLite.SQLiteException с сообщением

Файл базы данных заблокирован

база данных заблокирована

Трассировка стека - это:

   at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
   at System.Data.SQLite.SQLiteDataReader.NextResult()
   at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
   at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
   at System.Data.SQLite.SQLiteTransaction..ctor(SQLiteConnection connection, Boolean deferredLock)
   at System.Data.SQLite.SQLiteConnection.BeginDbTransaction(IsolationLevel isolationLevel)
   at System.Data.SQLite.SQLiteConnection.BeginTransaction()
   at System.Data.SQLite.SQLiteEnlistment..ctor(SQLiteConnection cnn, Transaction scope)
   at System.Data.SQLite.SQLiteConnection.EnlistTransaction(Transaction transaction)
   at System.Data.SQLite.SQLiteConnection.Open()
   at SubSonic.SQLiteDataProvider.CreateConnection(String newConnectionString)
   at SubSonic.SQLiteDataProvider.CreateConnection()
   at SubSonic.SQLiteDataProvider.ExecuteScalar(QueryCommand qry)
   at SubSonic.DataService.ExecuteScalar(QueryCommand cmd)
   at SubSonic.ActiveRecord`1.Save(String userName)
   at SubSonic.ActiveRecord`1.Save()
   at (my line of code above).

Если у меня есть using statments, вложенные наоборот, с SharedDbConnectionScope снаружи, то я получаю TransactionException с сообщением "Операция недопустима для состояния транзакции". Трассировка стека является:

at System.Transactions.TransactionState.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions, Transaction atomicTransaction)
   at System.Transactions.Transaction.EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
   at System.Data.SQLite.SQLiteEnlistment..ctor(SQLiteConnection cnn, Transaction scope)
   at System.Data.SQLite.SQLiteConnection.EnlistTransaction(Transaction transaction)
   at System.Data.SQLite.SQLiteConnection.Open()
   at SubSonic.SQLiteDataProvider.CreateConnection(String newConnectionString)
   at SubSonic.SQLiteDataProvider.CreateConnection()
   at SubSonic.SQLiteDataProvider.ExecuteScalar(QueryCommand qry)
   at SubSonic.DataService.ExecuteScalar(QueryCommand cmd)
   at SubSonic.ActiveRecord`1.Save(String userName)
   at SubSonic.ActiveRecord`1.Save()
   at (my line of code above)

и внутреннее исключение - это "Тайм-аут транзакции".

У меня нет никакого пользовательского кода в моих сгенерированных классах DAL или чего-либо еще умного, что я могу придумать, что могло бы вызвать это.

Кто-нибудь еще сталкивался с подобными проблемами транзакций или кто-нибудь может подсказать, с чего мне начать поиск проблемы?

Спасибо!

Обновить:Я заметил упоминание о вещах, связанных с транзакциями, в примечаниях к выпуску версий 1.0.61-65 (например здесь), поэтому, возможно, обновление SubSonic для работы с последней версией поставщика данных .Net решило бы некоторые из этих проблем...

Это было полезно?

Решение

При создании пересмотренного поставщика sqlite для subsonic 2.x я создал полный набор модульных тестов на основе существующих тестов subsonic sqlserver.(Эти тесты также были проверены с исправленным кодом.) Единственными неудачными тестами были тесты, связанные с транзакциями (возможно, и с миграцией).Сообщение об ошибке "Файл базы данных заблокирован", как вы уже видели.Subsonic был написан в основном для sql server, который не выполняет блокировку на уровне файлов, как это делает SQLite, поэтому некоторые вещи не работают;его нужно было бы переписать, чтобы справиться с этим лучше.

Я никогда не пользовался TransactionScope так, как вы.Я выполняю свои транзакции subsonic 2.2 подобным образом, и пока никаких проблем с поставщиком SQLite нет.Я могу подтвердить, что вам нужно использовать транзакции с SQLite, если вы имеете дело с несколькими строками или это действительно медленно.

public void DeleteStuff(List<Stuff> piaRemoves)
{
    QueryCommandCollection qcc = new QueryCommandCollection();

    foreach(Stuff item in piaRemoves)
    {
        Query qry1 = new Query(Stuff.Schema);
        qry1.QueryType = QueryType.Delete;
        qry1.AddWhere(Stuff.Columns.ItemID, item.ItemID);
        qry1.AddWhere(Stuff.Columns.ColumnID, item.ColumnID);
        qry1.AddWhere(Stuff.Columns.ParentID, item.ParentID);
        QueryCommand cmd = qry1.BuildDeleteCommand();
        qcc.Add(cmd);
    }
    DataService.ExecuteTransaction(qcc);
}

Другие советы

В итоге я воспользовался предложением Пола и переписал свой код примерно так:

    QueryCommandCollection qcc = new QueryCommandCollection();

    SomeDALObject x = new SomeDALObject()
    x.Property1 = "blah";
    x.Property2 = "blah blah";
    qcc.Add(x.GetSaveCommand());

    foreach (KeyValuePair<string, string> attribute in attributes)
    { 
        AnotherDALObject y = new AnotherDALObject()
        y.Property1 = attribute.Key
        y.Property2 = attribute.Value

        qcc.Add(y.GetSaveCommand());
    }

    DataService.ExecuteTransaction(qcc);

На самом деле это намного лучше, поскольку вся подготовка к обращениям к базе данных выполняется до открытия транзакции, поэтому транзакция будет открыта гораздо меньше времени.

Это не будет работать так хорошо, если вам нужно вернуть автоматически сгенерированные идентификаторы, чтобы запускать вставки для дочерних записей;для этого вам нужно будет использовать другой подход.

Затем я столкнулся с некоторыми другими проблемами с потоками / транзакциями:когда у меня было несколько потоков, выполняющих DataService.ExecuteTransaction() в то же время я бы получил AccessViolationExceptions и NullReferenceExceptions, в основном, немного беспорядок.Но изменение для использования Вилка Пола из SubSonic с обновленным SqlDataProvider, а также переходом на использование System.Data.SQLite версия v1.0.65.0 кажется, это мгновенно исправлено.Ура!

Обновить:На самом деле я все еще сталкиваюсь с проблемами потоковой передачи, используя SubSonic с sqlite.По сути, SQLiteDataProvider в SubSonic написан не для работы с многопоточностью.Еще не все потеряно...

Мы используем SQL lite для тестирования кода, связанного с действиями базы данных.SQL lite не поддерживает вложенную транзакцию.У нас были похожие проблемы, когда у нас были NHibernate и .Чистая транзакция.В конечном счете нам пришлось довольствоваться использованием SQL Express для тестирования кода, связанного с базой данных.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top