Skip to content

Instantly share code, notes, and snippets.

@chrisfcarroll
Last active January 29, 2026 16:22
Show Gist options
  • Select an option

  • Save chrisfcarroll/3f7c92e62b4806a748f5eb70672bf094 to your computer and use it in GitHub Desktop.

Select an option

Save chrisfcarroll/3f7c92e62b4806a748f5eb70672bf094 to your computer and use it in GitHub Desktop.
Create and bootstrap a Sqlite database. In particular, create shared in-memory databases, either named or anonymous.
using Microsoft.Data.Sqlite;
/// <summary>
/// This class encapsulates the creation and one-time bootstrapping
/// of a Sqlite database.
/// In particular, it can create named or anonymous shared in-memory databases
/// which will persist until the last connection to the shared database is
/// closed.
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>For named, shared, in-memory instances use
/// <see cref="SqliteDb.InMemoryNamedShared.Instance(string)"/>.</item>
/// <item>For access to the anonymous, shared, in-memory instance
/// use <see cref="SqliteDb.InMemoryAnonymousShared.Instance"/>.</item>
/// <item>For disk-backed instances, or any other option, pass an appropriate
/// Sqlite Data Source to the constructor, for instance new("path/to/myfile.db").</item>
/// </list>
/// </remarks>
class SqliteDb : IDisposable, IAsyncDisposable
{
public static class InMemoryShared
{
/// <summary>
/// Returns a <see cref="SqliteDb"/> whose <see cref="Connection"/>
/// is to an in-memory database that is shared with any other
/// connection using the same <paramref name="name"/>.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static SqliteDb Named(string name)
=> new(UriFor(name));
/// <summary>
/// Return a Data Source name for use in a Sqlite connection
/// string to connect to a shared, named, in-memory database.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
internal static string UriFor(string name)
=> $"file:{name}?mode=memory&cache=shared";
/// <summary>
/// Returns a <see cref="SqliteDb"/> whose <see cref="Connection"/>
/// is to an in-memory database that is shared with any other
/// connection using the anonymous shared instance.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static SqliteDb AnonymousInstance => new();
/// <summary>
/// Sqlite magic string for un-named in-memory database with shared cache.
/// Every new connection using this string will get the same database.
/// The database will persist until the last connection is closed.
/// </summary>
/// <seealso href="https://www.sqlite.org/inmemorydb.html"/>
public const string AnonymousInstanceUri = "file::memory:?cache=shared";
}
/// <summary>
/// The data source passed to the constructor. Either a file path
/// or magic string such as <see cref="InMemoryAnonymousShared.Uri"/>
/// or a named in-memory string such as <see cref="InMemoryNamedShared.Uri(string)"/>.
/// </summary>
public string DataSource { get; }
/// <summary>A single connection to the database.</summary>
public SqliteConnection Connection { get; private set; }
/// <summary>
/// Create a new database and initialise it with the contents
/// of the given SQL script file.
/// If one of <see cref="InitFromFile"/> or <see cref="InitFromString"/>
/// has already been called, this method does nothing.
/// </summary>
/// <param name="sqlScriptFile"></param>
/// <returns><c>this</c></returns>
public SqliteDb InitFromFile(string sqlScriptFile)
{
lock (dbInittedLock)
{
if (isInitted) return this;
string dbDeployScript = File.ReadAllText(sqlScriptFile);
InitFromString(dbDeployScript);
}
return this;
}
/// <summary>
/// Create a new database and initialise it with the
/// given SQL script.
/// If one of <see cref="InitFromFile"/> or <see cref="InitFromString"/>
/// has already been called, this method does nothing.
/// </summary>
/// <param name="sql"></param>
/// <returns><c>this</c></returns>
public SqliteDb InitFromString(string sql)
{
lock (dbInittedLock)
{
if (isInitted) return this;
Connection = new SqliteConnection($"Data Source=" + DataSource);
Connection.Open();
using var cmd = Connection.CreateCommand();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
isInitted = true;
}
return this;
}
readonly Lock dbInittedLock = new();
bool isInitted;
/// <summary>
/// This class encapsulates the creation and one-time bootstrapping
/// of a Sqlite database.
/// In particular, it can create named or anonymous shared in-memory databases
/// which will persist until the last connection to the shared database is
/// closed.
/// </summary>
/// <param name="dataSource">
/// Either
/// <list type="bullet">
/// <item>A file path to a sqlite database. This can be an existing file or one
/// to be created.</item>
/// <item>A uri for an in-memory database, for instance the uris used
/// by <see cref="SqliteDb.InMemoryShared"/></item>
/// </list>
/// Defaults to <see cref="InMemoryShared.AnonymousInstanceUri"/>, which will
/// result in Sqlite creating an anonymous, shared, in-memory database.
/// The database will persist until the last connection to the
/// anonymous shared instance is closed.
/// </param>
/// <remarks>
/// To create named, shared, in-memory instances use
/// <see cref="SqliteDb.InMemoryNamedShared.Instance(string)"/>.
/// </remarks>
public SqliteDb(string dataSource = InMemoryShared.AnonymousInstanceUri)
{
DataSource = dataSource;
Connection = new SqliteConnection($"Data Source=" + DataSource + ";");
}
#region Dispose
/// <summary>
/// Disposes the <see cref="Connection"/>.
/// An in-memory database will be dropped when the last connection
/// to it is closed.
/// </summary>
public void Dispose() => Connection.Dispose();
/// <summary>
/// Disposes the <see cref="Connection"/>.
/// An in-memory database will be dropped when the last connection
/// to it is closed.
/// </summary>
public ValueTask DisposeAsync() => Connection.DisposeAsync();
#endregion
}

SqliteDb

Usage

// Create a shared in-memory database and bootstrap it with some sql.
using var dbInstance1 = SqliteDb
                            .InMemoryShared
                            .Named("Db1")
                            .InitFromString("Create Table Strings(Id string)");
                            
var db1 = dbInstance1.Connection;
db1.Execute("Insert Into Strings Values('1')");
db1.Query<string>("Select * from Strings");

// Create a shared in-memory database and bootstrap it with a sql script file.
var sqlScriptPath = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllText(sqlScriptPath, "Create Table Files(Id string)");
using var dbInstance2 = SqliteDb
                            .InMemoryShared
                            .Named("Db2")
                            .InitFromFile(sqlScriptPath);
var db2 = dbInstance2.Connection;
System.IO.File.Delete(sqlScriptPath);

// Initting an already initted SqliteDb instance will do nothing.
dbInstance1.InitFromFile("No error here. dbInstance1 is already initted.");


// Other references with the same named in-memory database will see the existing database.
// result will contain the row inserted above.
using var anotherRefenceToDbs1 = SqliteDb.InMemoryShared.Named("Db1");
var result= anotherRefenceToDbs1.Connection.Query<string>("Select * from Strings").ToList();
result.ForEach(Console.WriteLine);
result.ShouldBeOfLength(1).Single().ShouldBe("1");

//Creating a Sqlite db on disk works as normal
var file1 = System.IO.Path.GetTempFileName();
using var fileBackedDb1 = new SqliteDb(file1);
Console.WriteLine(fileBackedDb1.Connection.ConnectionString);
#:package Microsoft.Data.Sqlite
#:package Microsoft.Data.Sqlite
#:package Dapper
#:package TestBase
using Microsoft.Data.Sqlite;
using Dapper;
using TestBase;
using TooString;
new SelfTest().SqliteDbOpensAndSharesInMemoryNamesInstances();
new SelfTest().SqliteDbOpensAndSharesInMemoryAnonymousInstance();
//```csharp
// Create a shared in-memory database and bootstrap it with some sql.
using var dbInstance1 = SqliteDb
.InMemoryShared
.Named("Db1")
.InitFromString("Create Table Strings(Id string)");
var db1 = dbInstance1.Connection;
db1.Execute("Insert Into Strings Values('1')");
db1.Query<string>("Select * from Strings");
// Create a shared in-memory database and bootstrap it with a sql script file.
var sqlScriptPath = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllText(sqlScriptPath, "Create Table Files(Id string)");
using var dbInstance2 = SqliteDb
.InMemoryShared
.Named("Db2")
.InitFromFile(sqlScriptPath);
var db2 = dbInstance2.Connection;
System.IO.File.Delete(sqlScriptPath);
// Initting an already initted SqliteDb instance will do nothing.
dbInstance1.InitFromFile("No error here. dbInstance1 is already initted.");
// Other references with the same named in-memory database will see the existing database.
// result will contain the row inserted above.
using var anotherRefenceToDbs1 = SqliteDb.InMemoryShared.Named("Db1");
var result= anotherRefenceToDbs1.Connection.Query<string>("Select * from Strings").ToList();
result.ForEach(Console.WriteLine);
result.ShouldBeOfLength(1).Single().ShouldBe("1");
//Creating a Sqlite db on disk works as normal
var file1 = System.IO.Path.GetTempFileName();
using var fileBackedDb1 = new SqliteDb(file1);
Console.WriteLine(fileBackedDb1.Connection.ConnectionString);
//```
//[TestClass]
public class SelfTest
{
//[TestMethod]
public void SqliteDbOpensAndSharesInMemoryNamesInstances()
{
var dbs = Enumerable
.Range(0, 10)
.Select(i => SqliteDb.InMemoryShared.Named(i.ToString())).ToList();
dbs.ForEach(db =>
{
db.InitFromString(
$"Create Table A(Name string);Insert Into A Values('Hello {db.DataSource}')");
var rows =
db.Connection.Query<string>("Select * from A");
rows.Single().ShouldBe($"Hello {db.DataSource}");
});
dbs.ForEach(db =>
{
using var db2 = new SqliteConnection(db.Connection!.ConnectionString);
var rows = db2.Query<string>("Select * from A").ToList();
Console.WriteLine(rows.ToJson());
rows.Single().ShouldBe($"Hello {db.DataSource}");
});
dbs.ForEach(db => db.Dispose());
//Can't really prove that Sqlite drops the in-memory databases?
}
//[TestMethod]
public void SqliteDbOpensAndSharesInMemoryAnonymousInstance()
{
var id = Guid.NewGuid().ToString();
using var db = new SqliteDb()
.InitFromString($"Create Table A(Name string);Insert Into A Values('Hello {id}')")
.Connection;
var rows = db.Query<string>("Select * from A").ToList();
Console.WriteLine(rows.ToJson());
rows.Single().ShouldBe($"Hello {id}");
var dbs = Enumerable
.Range(0, 10)
.Select(i => SqliteDb.InMemoryShared.AnonymousInstance).ToList();
dbs.ForEach(db =>
{
var rows = db.Connection!.Query<string>("Select * from A");
rows.Single().ShouldBe($"Hello {id}");
db.Dispose();
});
//Can't really prove that Sqlite drops the in-memory database?
}
}
/// <summary>
/// This class encapsulates the creation and one-time bootstrapping
/// of a Sqlite database.
/// In particular, it can create named or anonymous shared in-memory databases
/// which will persist until the last connection to the shared database is
/// closed.
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>For named, shared, in-memory instances use
/// <see cref="SqliteDb.InMemoryNamedShared.Instance(string)"/>.</item>
/// <item>For access to the anonymous, shared, in-memory instance
/// use <see cref="SqliteDb.InMemoryAnonymousShared.Instance"/>.</item>
/// <item>For disk-backed instances, or any other option, pass an appropriate
/// Sqlite Data Source to the constructor, for instance new("path/to/myfile.db").</item>
/// </list>
/// </remarks>
class SqliteDb : IDisposable, IAsyncDisposable
{
public static class InMemoryShared
{
/// <summary>
/// Returns a <see cref="SqliteDb"/> whose <see cref="Connection"/>
/// is to an in-memory database that is shared with any other
/// connection using the same <paramref name="name"/>.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static SqliteDb Named(string name)
=> new(UriFor(name));
/// <summary>
/// Return a Data Source name for use in a Sqlite connection
/// string to connect to a shared, named, in-memory database.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
internal static string UriFor(string name)
=> $"file:{name}?mode=memory&cache=shared";
/// <summary>
/// Returns a <see cref="SqliteDb"/> whose <see cref="Connection"/>
/// is to an in-memory database that is shared with any other
/// connection using the anonymous shared instance.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static SqliteDb AnonymousInstance => new();
/// <summary>
/// Sqlite magic string for un-named in-memory database with shared cache.
/// Every new connection using this string will get the same database.
/// The database will persist until the last connection is closed.
/// </summary>
/// <seealso href="https://www.sqlite.org/inmemorydb.html"/>
public const string AnonymousInstanceUri = "file::memory:?cache=shared";
}
/// <summary>
/// The data source passed to the constructor. Either a file path
/// or magic string such as <see cref="InMemoryAnonymousShared.Uri"/>
/// or a named in-memory string such as <see cref="InMemoryNamedShared.Uri(string)"/>.
/// </summary>
public string DataSource { get; }
/// <summary>A single connection to the database.</summary>
public SqliteConnection Connection { get; private set; }
/// <summary>
/// Create a new database and initialise it with the contents
/// of the given SQL script file.
/// If one of <see cref="InitFromFile"/> or <see cref="InitFromString"/>
/// has already been called, this method does nothing.
/// </summary>
/// <param name="sqlScriptFile"></param>
/// <returns><c>this</c></returns>
public SqliteDb InitFromFile(string sqlScriptFile)
{
lock (dbInittedLock)
{
if (isInitted) return this;
string dbDeployScript = File.ReadAllText(sqlScriptFile);
InitFromString(dbDeployScript);
}
return this;
}
/// <summary>
/// Create a new database and initialise it with the
/// given SQL script.
/// If one of <see cref="InitFromFile"/> or <see cref="InitFromString"/>
/// has already been called, this method does nothing.
/// </summary>
/// <param name="sql"></param>
/// <returns><c>this</c></returns>
public SqliteDb InitFromString(string sql)
{
lock (dbInittedLock)
{
if (isInitted) return this;
Connection = new SqliteConnection($"Data Source=" + DataSource);
Connection.Open();
using var cmd = Connection.CreateCommand();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
isInitted = true;
}
return this;
}
readonly Lock dbInittedLock = new();
bool isInitted;
/// <summary>
/// This class encapsulates the creation and one-time bootstrapping
/// of a Sqlite database.
/// In particular, it can create named or anonymous shared in-memory databases
/// which will persist until the last connection to the shared database is
/// closed.
/// </summary>
/// <param name="dataSource">
/// Either
/// <list type="bullet">
/// <item>A file path to a sqlite database. This can be an existing file or one
/// to be created.</item>
/// <item>A uri for an in-memory database, for instance the uris used
/// by <see cref="SqliteDb.InMemoryShared"/></item>
/// </list>
/// Defaults to <see cref="InMemoryShared.AnonymousInstanceUri"/>, which will
/// result in Sqlite creating an anonymous, shared, in-memory database.
/// The database will persist until the last connection to the
/// anonymous shared instance is closed.
/// </param>
/// <remarks>
/// To create named, shared, in-memory instances use
/// <see cref="SqliteDb.InMemoryNamedShared.Instance(string)"/>.
/// </remarks>
public SqliteDb(string dataSource = InMemoryShared.AnonymousInstanceUri)
{
DataSource = dataSource;
Connection = new SqliteConnection($"Data Source=" + DataSource + ";");
}
#region Dispose
/// <summary>
/// Disposes the <see cref="Connection"/>.
/// An in-memory database will be dropped when the last connection
/// to it is closed.
/// </summary>
public void Dispose() => Connection.Dispose();
/// <summary>
/// Disposes the <see cref="Connection"/>.
/// An in-memory database will be dropped when the last connection
/// to it is closed.
/// </summary>
public ValueTask DisposeAsync() => Connection.DisposeAsync();
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment