Проблемы с блокировкой sqlite и SubSonic при использовании транзакций в одном потоке - PullRequest
1 голос
/ 18 февраля 2010

Я получаю исключения блокировки при попытке использовать транзакции с 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).

Если у меня есть вложенные инструкции использования, наоборот, с 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 Data Provider решит некоторые из этих проблем ...

Ответы [ 3 ]

3 голосов
/ 19 февраля 2010

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

Я никогда не использовал TransactionScope, как вы. Я делаю мои транзакции с дозвуком 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);
}
2 голосов
/ 19 февраля 2010

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

    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);

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

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

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

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

0 голосов
/ 19 февраля 2010

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

...