Problema con SQLite:memoria:con NHibernate

Problema con SQLite:memoria:con NHibernate

Un database di memoria SQLite esiste solo finché la connessione ad esso rimane aperta. Per usarlo negli unit test con NHibernate:
1. Aprire una ISession all'inizio del test (magari in un metodo [SetUp]).
2. Utilizzare la connessione da quella sessione nella chiamata SchemaExport.
3. Usa la stessa sessione nei tuoi test.
4. Chiudi la sessione al termine del test (magari con un metodo [TearDown]).


Sono stato in grado di utilizzare un database in memoria SQLite ed evitare di dover ricostruire lo schema per ogni test utilizzando il supporto di SQLite per "Cache condivisa", che consente di condividere un database in memoria tra le connessioni.

Ho eseguito le seguenti operazioni in AssemblyInitialize (Sto usando MSTest):

  • Configura NHibernate (fluentemente) per utilizzare SQLite con la seguente stringa di connessione:

    FullUri=file:memorydb.db?mode=memory&cache=shared
    
  • Usa quella configurazione per creare un hbm2ddl.SchemaExport oggetto ed eseguirlo su una connessione separata (ma con la stessa stringa di connessione di nuovo).

  • Lascia quella connessione aperta e referenziata da un campo statico fino a AssemblyCleanup , a quel punto viene chiuso e smaltito. Questo perché SQLite ha bisogno di almeno una connessione attiva da tenere nel database in memoria per sapere che è ancora necessaria ed evitare di riordinare.

Prima dell'esecuzione di ogni test, viene creata una nuova sessione e il test viene eseguito in una transazione di cui viene eseguito il rollback alla fine.

Ecco un esempio del codice a livello di assembly di test:

[TestClass]
public static class SampleAssemblySetup
{
    private const string ConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared";
    private static SQLiteConnection _connection;

    [AssemblyInitialize]
    public static void AssemblyInit(TestContext context)
    {
        var configuration = Fluently.Configure()
                                       .Database(SQLiteConfiguration.Standard.ConnectionString(ConnectionString))
                                       .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("MyMappingsAssembly")))
                                       .ExposeConfiguration(x => x.SetProperty("current_session_context_class", "call"))
                                       .BuildConfiguration();

        // Create the schema in the database
        // Because it's an in-memory database, we hold this connection open until all the tests are finished
        var schemaExport = new SchemaExport(configuration);
        _connection = new SQLiteConnection(ConnectionString);
        _connection.Open();
        schemaExport.Execute(false, true, false, _connection, null);
    }

    [AssemblyCleanup]
    public static void AssemblyTearDown()
    {
        if (_connection != null)
        {
            _connection.Dispose();
            _connection = null;
        }
    }
}

E una classe base per ogni classe/apparecchio di unit test:

public class TestBase
{
    [TestInitialize]
    public virtual void Initialize()
    {
        NHibernateBootstrapper.InitializeSession();
        var transaction = SessionFactory.Current.GetCurrentSession().BeginTransaction();
    }

    [TestCleanup]
    public virtual void Cleanup()
    {
        var currentSession = SessionFactory.Current.GetCurrentSession();
        if (currentSession.Transaction != null)
        {
            currentSession.Transaction.Rollback();
            currentSession.Close();
        }

        NHibernateBootstrapper.CleanupSession();
    }
}

La gestione delle risorse potrebbe migliorare, lo ammetto, ma dopotutto si tratta di unit test (i miglioramenti suggeriti sono ben accetti!).


Stiamo usando SQLite in memoria per tutti i nostri test di database. Stiamo utilizzando un'unica connessione ADO per i test che viene riutilizzata per tutte le sessioni NH aperte dallo stesso test.

  1. Prima di ogni test:crea una connessione
  2. Crea schema su questa connessione
  3. Esegui test. La stessa connessione viene utilizzata per tutte le sessioni
  4. Dopo il test:chiudere la connessione

Ciò consente anche di eseguire test con più sessioni incluse. Anche la SessionFactory viene creata una volta per tutti i test, perché la lettura dei file di mappatura richiede un bel po' di tempo.

Modifica

Utilizzo della cache condivisa

A partire da System.Data.Sqlite 1.0.82 (o Sqlite 3.7.13), esiste una cache condivisa, che consente a più connessioni di condividere gli stessi dati, anche per i database In-Memory. Ciò consente la creazione del database in memoria in una connessione e l'utilizzo in un'altra. (Non l'ho ancora provato, ma in teoria dovrebbe funzionare):

  • Cambia la stringa di connessione in file::memory:?cache=shared
  • Apri una connessione e crea lo schema
  • Mantieni questa connessione aperta fino alla fine del test
  • Lascia che NH crei altre connessioni (comportamento normale) durante il test.