2015-10-20 2 views
1

Utilisé NuGet package: System.Data.SQLite.Core, 1.0.98.1Pooled connexion en lecture seule SQLite est utilisé pour un accès en lecture-écriture

Problème: Dans mon programme, j'utilise SQLite avec mise en commun activé et lecture seule accès dans certains cas. Normalement, il fonctionne très bien, mais s'il y a beaucoup de mélange en lecture seule/lecture-écriture des demandes intensives à base de données, puis échoue programme à l'exception suivante:

Unhandled Exception: System.Data.SQLite.SQLiteException: attempt to write a readonly database 
attempt to write a readonly database 
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt) 
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(CommandBehavior behavior) 
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery() 

Si je désactive la mise en commun, le programme fonctionne très bien . Je suppose que la connexion en lecture seule regroupée est utilisée pour la connexion en lecture-écriture. Est-ce que quelque chose me manque? Est-ce que c'est un comportement attendu ou non?

Code minimum à reproduire (échec à INSERT ou DELETE). Si j'introduis du retard, par exemple Thread.Sleep(10000), cela fonctionne très bien. Si je supprime la boucle, cela fonctionne aussi bien.

const string DbFilePath = "test.sqlite"; 
string readOnlyConnectionString = new SQLiteConnectionStringBuilder 
    { 
     DataSource = DbFilePath, 
     Pooling = true, 
     ReadOnly = true 
    }.ConnectionString; // data source=test.sqlite;pooling=True;read only=True 
string readWriteConnectionString = new SQLiteConnectionStringBuilder 
    { 
     DataSource = DbFilePath, 
     Pooling = true, 
     ReadOnly = false 
    }.ConnectionString; // data source=test.sqlite;pooling=True;read only=False 
File.Delete(DbFilePath); 
using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString)) 
using (SQLiteCommand cmd = new SQLiteCommand("CREATE TABLE items(id INTEGER NOT NULL PRIMARY KEY)", conn)) 
{ 
    conn.Open(); 
    cmd.ExecuteNonQuery(); 
} 
while (true) // <= if we comment the loop, the program executes without error 
{ 
    using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString)) 
    using (SQLiteCommand cmd = new SQLiteCommand("INSERT INTO items(id) VALUES (1)", conn)) 
    { 
     conn.Open(); 
     cmd.ExecuteNonQuery(); 
    } 
    using (SQLiteConnection conn = new SQLiteConnection(readOnlyConnectionString)) 
    using (SQLiteCommand cmd = new SQLiteCommand("SELECT COUNT(*) FROM items", conn)) 
    { 
     conn.Open(); 
     cmd.ExecuteScalar(); 
    } 
    using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString)) 
    using (SQLiteCommand cmd = new SQLiteCommand("DELETE FROM items", conn)) 
    { 
     conn.Open(); 
     cmd.ExecuteNonQuery(); 
    } 
} 
+0

Avez-vous examiné ces connexions pour vous assurer qu'elles sont effectivement différentes? Le fichier est-il réellement inscriptible sur le disque? –

+0

@ LasseV.Karlsen Les chaînes de connexion sont différentes. Le fichier est lisible et accessible en écriture. Comme je l'ai dit, si je désactive la mise en commun, ou si j'introduis un retard artificiel, le programme fonctionne bien. –

+0

Cela ressemble à un bogue, une faille de conception ou peut-être même une décision de conception dans la classe SQLiteConnectionPool. A en juger par un coup d'oeil rapide à la source ([SQLiteConnectionPool] (https://system.data.sqlite.org/index.html/artifact/aec6733a2d48e3d2)) il semble qu'il ne considère que le nom de fichier, et non la chaîne de connexion entière. –

Répondre

3

à partir du code source de la classe SQLiteConnectionPool on dirait qu'il considère que le nom de fichier lors de la gestion des entrées de la piscine.

Voici un bref extrait:

internal static void Add(
    string fileName, 
    SQLiteConnectionHandle handle, 
    int version 

Il n'y a aucune mention de la chaîne de connexion utilisée, seul le nom de fichier. En tant que tel, l'utilisation du mécanisme de pool de connexion intégré lorsque vous avez besoin de différentes chaînes de connexion pour se comporter différemment et pool séparément ne fonctionnera pas.


Maintenant, « la mise en commun de connexion » comme concept est quelque chose que l'implémenteur une hiérarchie de classes de ADO.NET décide lui/elle-même. Les classes SqlConnection regroupent par chaîne de connexion unique, mais cela n'est nullement requis par les implémentations de IDbConnection.

En tant que tel, il peut s'agir d'une décision de conception prise par les créateurs de System.Data.SQLite. Cependant, je leur enverrais un courriel et leur demanderais si c'était intentionnel ou non.