views:

321

answers:

1

When trying to use SQLite with System.Transactions TransactionScope with the identity generator as Increment, i noticed that i was getting an exception (given below along with code) when NHibernate was trying to retrieve the next Identity number.

This seems to be because the new SQLite connection is doing a auto enlist of the current transaction. From what i have heard SQLite only support single write transaction, but should support multiple read's, so i am surprised that i am getting a Database locked exception for a read operation. Did anybody use SQLite with Transaction Scope in this manner.

The same Code works fine if i use a NHibernate Transaction instead of TransactionScope

Code Block:

           using (var scope = new TransactionScope()) 
            { 
                var userRepository = 
container.GetInstance<IUserRepository>(); 
                var user = new User(); 
                userRepository.SaveOrUpdate(user); 
                scope.Complete(); 
            } 

Exception:

19:34:19,126 ERROR [   7] IncrementGenerator [(null)]- could not get 
increment value 
System.Data.SQLite.SQLiteException: The database file is locked 
database is locked 
   at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt) 
   at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery() 
   at System.Data.SQLite.SQLiteTransaction..ctor(SQLiteConnection 
connection, Boolean deferredLock) 
   at System.Data.SQLite.SQLiteConnection.BeginTransaction(Boolean 
deferredLock) 
   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 NHibernate.Connection.DriverConnectionProvider.GetConnection() 
   at NHibernate.Id.IncrementGenerator.GetNext(ISessionImplementor 
session) 
19:34:20,063 ERROR [   7] ADOExceptionReporter [(null)]- The database 
file is locked 
database is locked
A: 

There are two things at play here.

As you mentioned, System.Data.SQLite will auto-enlist in distributed transactions. This is on by default and can turn it off by adding Enlist=no.

The second is that System.Data.SQLite by default creates transactions with an automatic write lock. This is done based on the assumption that if a transaction is started, writes will be done. This can be overridden by starting the transactions with Serializable.ReadCommitted.

The default can also be specified in the connection string using the DefaultIsolationLevel key. Valid values are ReadCommitted and Serializable only. Other isolation levels are not supported by SQLite. ReadCommitted defers the write lock whereas Serializable obtains the write lock immediately.

Unspecified will use the default isolation level specified in the connection string. If no isolation level is specified in the connection string, Serializable is used. Serializable transactions are the default. In this mode, the engine gets an immediate lock on the database, and no other threads may begin a transaction. Other threads may read from the database, but not write.

With a ReadCommitted isolation level, locks are deferred and elevated as needed. It is possible for multiple threads to start a transaction in ReadCommitted mode, but if a thread attempts to commit a transaction while another thread has a ReadCommitted lock, it may timeout or cause a deadlock on both threads until both threads' CommandTimeout's are reached.

Sam
I did look into the Enlist option, but i cant turn that off, as i would then need to explicitly enlist it and i will not have access to the connection object for that. Could you specify what is the key to be used in ConnectionString for specifying the default transaction isolation level. The MSDN documentation says that the default isolation level for TranactionScope is ReadCommitted
Dinesh Manne
@Dinesh Manne, I edited the answer to add more info about the default isolation level. I'm not sure which docs you're referring to specifically about MSDN and TransactionScope, but SQLite doesn't have support for different types of transaction isolation levels like other databases, it reuses the isolation level for a slightly different purpose (for something other databases don't necessarily have). SQLite didn't used to do this, it used to have just a custom overload on SQLiteConnection, but then it's impossible to use deferred locks without directly referencing the SQLiteConnection class.
Sam