// // Copyright (c) 2012-2019 Krueger Systems, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; namespace SQLite { /// /// A pooled asynchronous connection to a SQLite database. /// public partial class SQLiteAsyncConnection { readonly SQLiteConnectionString _connectionString; /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. /// /// /// Specifies the path to the database file. /// /// /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You /// absolutely do want to store them as Ticks in all new projects. The value of false is /// only here for backwards compatibility. There is a *significant* speed advantage, with no /// down sides, when setting storeDateTimeAsTicks = true. /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless /// the storeDateTimeAsTicks parameter. /// public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true) : this (new SQLiteConnectionString (databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex, storeDateTimeAsTicks)) { } /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. /// /// /// Specifies the path to the database file. /// /// /// Flags controlling how the connection should be opened. /// Async connections should have the FullMutex flag set to provide best performance. /// /// /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You /// absolutely do want to store them as Ticks in all new projects. The value of false is /// only here for backwards compatibility. There is a *significant* speed advantage, with no /// down sides, when setting storeDateTimeAsTicks = true. /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless /// the storeDateTimeAsTicks parameter. /// public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) : this (new SQLiteConnectionString (databasePath, openFlags, storeDateTimeAsTicks)) { } /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database /// using the given connection string. /// /// /// Details on how to find and open the database. /// public SQLiteAsyncConnection (SQLiteConnectionString connectionString) { _connectionString = connectionString; } /// /// Gets the database path used by this connection. /// public string DatabasePath => GetConnection ().DatabasePath; /// /// Gets the SQLite library version number. 3007014 would be v3.7.14 /// public int LibVersionNumber => GetConnection ().LibVersionNumber; /// /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true. /// /// The date time string format. public string DateTimeStringFormat => GetConnection ().DateTimeStringFormat; /// /// The amount of time to wait for a table to become unlocked. /// public TimeSpan GetBusyTimeout () { return GetConnection ().BusyTimeout; } /// /// Sets the amount of time to wait for a table to become unlocked. /// public Task SetBusyTimeoutAsync (TimeSpan value) { return ReadAsync (conn => { conn.BusyTimeout = value; return null; }); } /// /// Enables the write ahead logging. WAL is significantly faster in most scenarios /// by providing better concurrency and better disk IO performance than the normal /// jounral mode. You only need to call this function once in the lifetime of the database. /// public Task EnableWriteAheadLoggingAsync () { return WriteAsync (conn => { conn.EnableWriteAheadLogging (); return null; }); } /// /// Whether to store DateTime properties as ticks (true) or strings (false). /// public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks; /// /// Whether to store TimeSpan properties as ticks (true) or strings (false). /// public bool StoreTimeSpanAsTicks => GetConnection ().StoreTimeSpanAsTicks; /// /// Whether to writer queries to during execution. /// /// The tracer. public bool Trace { get { return GetConnection ().Trace; } set { GetConnection ().Trace = value; } } /// /// The delegate responsible for writing trace lines. /// /// The tracer. public Action Tracer { get { return GetConnection ().Tracer; } set { GetConnection ().Tracer = value; } } /// /// Whether Trace lines should be written that show the execution time of queries. /// public bool TimeExecution { get { return GetConnection ().TimeExecution; } set { GetConnection ().TimeExecution = value; } } /// /// Returns the mappings from types to tables that the connection /// currently understands. /// public IEnumerable TableMappings => GetConnection ().TableMappings; /// /// Closes all connections to all async databases. /// You should *never* need to do this. /// This is a blocking operation that will return when all connections /// have been closed. /// public static void ResetPool () { SQLiteConnectionPool.Shared.Reset (); } /// /// Gets the pooled lockable connection used by this async connection. /// You should never need to use this. This is provided only to add additional /// functionality to SQLite-net. If you use this connection, you must use /// the Lock method on it while using it. /// public SQLiteConnectionWithLock GetConnection () { return SQLiteConnectionPool.Shared.GetConnection (_connectionString); } /// /// Closes any pooled connections used by the database. /// public Task CloseAsync () { return Task.Factory.StartNew (() => { SQLiteConnectionPool.Shared.CloseConnection (_connectionString); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { return read (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { return write (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// /// Enable or disable extension loading. /// public Task EnableLoadExtensionAsync (bool enabled) { return WriteAsync (conn => { conn.EnableLoadExtension (enabled); return null; }); } /// /// Executes a "create table if not exists" on the database. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated. /// public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { return WriteAsync (conn => conn.CreateTable (createFlags)); } /// /// Executes a "create table if not exists" on the database. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// Type to reflect to a database table. /// Optional flags allowing implicit PK and indexes based on naming conventions. /// /// Whether the table was created or migrated. /// public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) { return WriteAsync (conn => conn.CreateTable (ty, createFlags)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() where T5 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types) { return WriteAsync (conn => conn.CreateTables (createFlags, types)); } /// /// Executes a "drop table" on the database. This is non-recoverable. /// public Task DropTableAsync () where T : new() { return WriteAsync (conn => conn.DropTable ()); } /// /// Executes a "drop table" on the database. This is non-recoverable. /// /// /// The TableMapping used to identify the table. /// public Task DropTableAsync (TableMapping map) { return WriteAsync (conn => conn.DropTable (map)); } /// /// Creates an index for the specified table and column. /// /// Name of the database table /// Name of the column to index /// Whether the index should be unique public Task CreateIndexAsync (string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnName, unique)); } /// /// Creates an index for the specified table and column. /// /// Name of the index to create /// Name of the database table /// Name of the column to index /// Whether the index should be unique public Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnName, unique)); } /// /// Creates an index for the specified table and columns. /// /// Name of the database table /// An array of column names to index /// Whether the index should be unique public Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnNames, unique)); } /// /// Creates an index for the specified table and columns. /// /// Name of the index to create /// Name of the database table /// An array of column names to index /// Whether the index should be unique public Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnNames, unique)); } /// /// Creates an index for the specified object property. /// e.g. CreateIndex<Client>(c => c.Name); /// /// Type to reflect to a database table. /// Property to index /// Whether the index should be unique public Task CreateIndexAsync (Expression> property, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (property, unique)); } /// /// Inserts the given object and retrieves its /// auto incremented primary key if it has one. /// /// /// The object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj) { return WriteAsync (conn => conn.Insert (obj)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// The type of object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, Type objType) { return WriteAsync (conn => conn.Insert (obj, objType)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, string extra) { return WriteAsync (conn => conn.Insert (obj, extra)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// The type of object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, string extra, Type objType) { return WriteAsync (conn => conn.Insert (obj, extra, objType)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. /// /// /// The object to insert. /// /// /// The number of rows modified. /// public Task InsertOrReplaceAsync (object obj) { return WriteAsync (conn => conn.InsertOrReplace (obj)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. /// /// /// The object to insert. /// /// /// The type of object to insert. /// /// /// The number of rows modified. /// public Task InsertOrReplaceAsync (object obj, Type objType) { return WriteAsync (conn => conn.InsertOrReplace (obj, objType)); } /// /// Updates all of the columns of a table using the specified object /// except for its primary key. /// The object is required to have a primary key. /// /// /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The number of rows updated. /// public Task UpdateAsync (object obj) { return WriteAsync (conn => conn.Update (obj)); } /// /// Updates all of the columns of a table using the specified object /// except for its primary key. /// The object is required to have a primary key. /// /// /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The type of object to insert. /// /// /// The number of rows updated. /// public Task UpdateAsync (object obj, Type objType) { return WriteAsync (conn => conn.Update (obj, objType)); } /// /// Updates all specified objects. /// /// /// An of the objects to insert. /// /// /// A boolean indicating if the inserts should be wrapped in a transaction /// /// /// The number of rows modified. /// public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true) { return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction)); } /// /// Deletes the given object from the database using its primary key. /// /// /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The number of rows deleted. /// public Task DeleteAsync (object objectToDelete) { return WriteAsync (conn => conn.Delete (objectToDelete)); } /// /// Deletes the object with the specified primary key. /// /// /// The primary key of the object to delete. /// /// /// The number of objects deleted. /// /// /// The type of object. /// public Task DeleteAsync (object primaryKey) { return WriteAsync (conn => conn.Delete (primaryKey)); } /// /// Deletes the object with the specified primary key. /// /// /// The primary key of the object to delete. /// /// /// The TableMapping used to identify the table. /// /// /// The number of objects deleted. /// public Task DeleteAsync (object primaryKey, TableMapping map) { return WriteAsync (conn => conn.Delete (primaryKey, map)); } /// /// Deletes all the objects from the specified table. /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the /// specified table. Do you really want to do that? /// /// /// The number of objects deleted. /// /// /// The type of objects to delete. /// public Task DeleteAllAsync () { return WriteAsync (conn => conn.DeleteAll ()); } /// /// Deletes all the objects from the specified table. /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the /// specified table. Do you really want to do that? /// /// /// The TableMapping used to identify the table. /// /// /// The number of objects deleted. /// public Task DeleteAllAsync (TableMapping map) { return WriteAsync (conn => conn.DeleteAll (map)); } /// /// Backup the entire database to the specified path. /// /// Path to backup file. /// The name of the database to backup (usually "main"). public Task BackupAsync (string destinationDatabasePath, string databaseName = "main") { return WriteAsync (conn => { conn.Backup (destinationDatabasePath, databaseName); return 0; }); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The object with the given primary key. Throws a not found exception /// if the object is not found. /// public Task GetAsync (object pk) where T : new() { return ReadAsync (conn => conn.Get (pk)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The TableMapping used to identify the table. /// /// /// The object with the given primary key. Throws a not found exception /// if the object is not found. /// public Task GetAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Get (pk, map)); } /// /// Attempts to retrieve the first object that matches the predicate from the table /// associated with the specified type. /// /// /// A predicate for which object to find. /// /// /// The object that matches the given predicate. Throws a not found exception /// if the object is not found. /// public Task GetAsync (Expression> predicate) where T : new() { return ReadAsync (conn => conn.Get (predicate)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The object with the given primary key or null /// if the object is not found. /// public Task FindAsync (object pk) where T : new() { return ReadAsync (conn => conn.Find (pk)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The TableMapping used to identify the table. /// /// /// The object with the given primary key or null /// if the object is not found. /// public Task FindAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Find (pk, map)); } /// /// Attempts to retrieve the first object that matches the predicate from the table /// associated with the specified type. /// /// /// A predicate for which object to find. /// /// /// The object that matches the given predicate or null /// if the object is not found. /// public Task FindAsync (Expression> predicate) where T : new() { return ReadAsync (conn => conn.Find (predicate)); } /// /// Attempts to retrieve the first object that matches the query from the table /// associated with the specified type. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// The object that matches the given predicate or null /// if the object is not found. /// public Task FindWithQueryAsync (string query, params object[] args) where T : new() { return ReadAsync (conn => conn.FindWithQuery (query, args)); } /// /// Attempts to retrieve the first object that matches the query from the table /// associated with the specified type. /// /// /// The TableMapping used to identify the table. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// The object that matches the given predicate or null /// if the object is not found. /// public Task FindWithQueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => conn.FindWithQuery (map, query, args)); } /// /// Retrieves the mapping that is automatically generated for the given type. /// /// /// The type whose mapping to the database is returned. /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// public Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None) { return ReadAsync (conn => conn.GetMapping (type, createFlags)); } /// /// Retrieves the mapping that is automatically generated for the given type. /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// public Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { return ReadAsync (conn => conn.GetMapping (createFlags)); } /// /// Query the built-in sqlite table_info table for a specific tables columns. /// /// The columns contains in the table. /// Table name. public Task> GetTableInfoAsync (string tableName) { return ReadAsync (conn => conn.GetTableInfo (tableName)); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// Use this method instead of Query when you don't expect rows back. Such cases include /// INSERTs, UPDATEs, and DELETEs. /// You can set the Trace or TimeExecution properties of the connection /// to profile execution. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// The number of rows modified in the database as a result of this execution. /// public Task ExecuteAsync (string query, params object[] args) { return WriteAsync (conn => conn.Execute (query, args)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, bool runInTransaction = true) { return WriteAsync (conn => conn.InsertAll (objects, runInTransaction)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true) { return WriteAsync (conn => conn.InsertAll (objects, extra, runInTransaction)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// /// The type of object to insert. /// /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true) { return WriteAsync (conn => conn.InsertAll (objects, objType, runInTransaction)); } /// /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception /// is rethrown. /// /// /// The to perform within a transaction. can contain any number /// of operations on the connection but should never call or /// . /// public Task RunInTransactionAsync (Action action) { return WriteAsync (conn => { conn.BeginTransaction (); try { action (conn); conn.Commit (); return null; } catch (Exception) { conn.Rollback (); throw; } }); } /// /// Returns a queryable interface to the table represented by the given type. /// /// /// A queryable object that is able to translate Where, OrderBy, and Take /// queries into native SQL. /// public AsyncTableQuery Table () where T : new() { // // This isn't async as the underlying connection doesn't go out to the database // until the query is performed. The Async methods are on the query iteself. // var conn = GetConnection (); return new AsyncTableQuery (conn.Table ()); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// Use this method when return primitive values. /// You can set the Trace or TimeExecution properties of the connection /// to profile execution. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// The number of rows modified in the database as a result of this execution. /// public Task ExecuteScalarAsync (string query, params object[] args) { return WriteAsync (conn => { var command = conn.CreateCommand (query, args); return command.ExecuteScalar (); }); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// It returns each row of the result using the mapping automatically generated for /// the given type. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// An enumerable with one result for each row returned by the query. /// public Task> QueryAsync (string query, params object[] args) where T : new() { return ReadAsync (conn => conn.Query (query, args)); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// It returns each row of the result using the specified mapping. This function is /// only used by libraries in order to query the database via introspection. It is /// normally not used. /// /// /// A to use to convert the resulting rows /// into objects. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// An enumerable with one result for each row returned by the query. /// public Task> QueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => conn.Query (map, query, args)); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// It returns each row of the result using the mapping automatically generated for /// the given type. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// An enumerable with one result for each row returned by the query. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database /// connection must remain open for the lifetime of the enumerator. /// public Task> DeferredQueryAsync (string query, params object[] args) where T : new() { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (query, args).ToList ()); } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. /// It returns each row of the result using the specified mapping. This function is /// only used by libraries in order to query the database via introspection. It is /// normally not used. /// /// /// A to use to convert the resulting rows /// into objects. /// /// /// The fully escaped SQL. /// /// /// Arguments to substitute for the occurences of '?' in the query. /// /// /// An enumerable with one result for each row returned by the query. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database /// connection must remain open for the lifetime of the enumerator. /// public Task> DeferredQueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (map, query, args).ToList ()); } } /// /// Query to an asynchronous database connection. /// public class AsyncTableQuery where T : new() { TableQuery _innerQuery; /// /// Creates a new async query that uses given the synchronous query. /// public AsyncTableQuery (TableQuery innerQuery) { _innerQuery = innerQuery; } Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; using (conn.Lock ()) { return read (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; using (conn.Lock ()) { return write (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// /// Filters the query based on a predicate. /// public AsyncTableQuery Where (Expression> predExpr) { return new AsyncTableQuery (_innerQuery.Where (predExpr)); } /// /// Skips a given number of elements from the query and then yields the remainder. /// public AsyncTableQuery Skip (int n) { return new AsyncTableQuery (_innerQuery.Skip (n)); } /// /// Yields a given number of elements from the query and then skips the remainder. /// public AsyncTableQuery Take (int n) { return new AsyncTableQuery (_innerQuery.Take (n)); } /// /// Order the query results according to a key. /// public AsyncTableQuery OrderBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery OrderByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery ThenBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenBy (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery ThenByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenByDescending (orderExpr)); } /// /// Queries the database and returns the results as a List. /// public Task> ToListAsync () { return ReadAsync (conn => _innerQuery.ToList ()); } /// /// Queries the database and returns the results as an array. /// public Task ToArrayAsync () { return ReadAsync (conn => _innerQuery.ToArray ()); } /// /// Execute SELECT COUNT(*) on the query /// public Task CountAsync () { return ReadAsync (conn => _innerQuery.Count ()); } /// /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. /// public Task CountAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.Count (predExpr)); } /// /// Returns the element at a given index /// public Task ElementAtAsync (int index) { return ReadAsync (conn => _innerQuery.ElementAt (index)); } /// /// Returns the first element of this query. /// public Task FirstAsync () { return ReadAsync (conn => _innerQuery.First ()); } /// /// Returns the first element of this query, or null if no element is found. /// public Task FirstOrDefaultAsync () { return ReadAsync (conn => _innerQuery.FirstOrDefault ()); } /// /// Returns the first element of this query that matches the predicate. /// public Task FirstAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.First (predExpr)); } /// /// Returns the first element of this query that matches the predicate. /// public Task FirstOrDefaultAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.FirstOrDefault (predExpr)); } /// /// Delete all the rows that match this query and the given predicate. /// public Task DeleteAsync (Expression> predExpr) { return WriteAsync (conn => _innerQuery.Delete (predExpr)); } /// /// Delete all the rows that match this query. /// public Task DeleteAsync () { return WriteAsync (conn => _innerQuery.Delete ()); } } class SQLiteConnectionPool { class Entry { WeakReference connection; public SQLiteConnectionString ConnectionString { get; } public Entry (SQLiteConnectionString connectionString) { ConnectionString = connectionString; } public SQLiteConnectionWithLock Connect () { SQLiteConnectionWithLock c = null; var wc = connection; if (wc == null || !wc.TryGetTarget (out c)) { c = new SQLiteConnectionWithLock (ConnectionString); // If the database is FullMutex, then we don't need to bother locking if (ConnectionString.OpenFlags.HasFlag (SQLiteOpenFlags.FullMutex)) { c.SkipLock = true; } connection = new WeakReference (c); } return c; } public void Close () { var wc = connection; if (wc != null && wc.TryGetTarget (out var c)) { c.Close (); } connection = null; } } readonly Dictionary _entries = new Dictionary (); readonly object _entriesLock = new object (); static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool (); /// /// Gets the singleton instance of the connection tool. /// public static SQLiteConnectionPool Shared { get { return _shared; } } public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString) { var key = connectionString.UniqueKey; Entry entry; lock (_entriesLock) { if (!_entries.TryGetValue (key, out entry)) { entry = new Entry (connectionString); _entries[key] = entry; } } return entry.Connect (); } public void CloseConnection (SQLiteConnectionString connectionString) { var key = connectionString.UniqueKey; Entry entry; lock (_entriesLock) { if (_entries.TryGetValue (key, out entry)) { _entries.Remove (key); } } entry?.Close (); } /// /// Closes all connections managed by this pool. /// public void Reset () { List entries; lock (_entriesLock) { entries = new List (_entries.Values); _entries.Clear (); } foreach (var e in entries) { e.Close (); } } } /// /// This is a normal connection except it contains a Lock method that /// can be used to serialize access to the database /// in lieu of using the sqlite's FullMutex support. /// public class SQLiteConnectionWithLock : SQLiteConnection { readonly object _lockPoint = new object (); /// /// Initializes a new instance of the class. /// /// Connection string containing the DatabasePath. public SQLiteConnectionWithLock (SQLiteConnectionString connectionString) : base (connectionString) { } /// /// Gets or sets a value indicating whether this skip lock. /// /// true if skip lock; otherwise, false. public bool SkipLock { get; set; } /// /// Lock the database to serialize access to it. To unlock it, call Dispose /// on the returned object. /// /// The lock. public IDisposable Lock () { return SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper (_lockPoint); } class LockWrapper : IDisposable { object _lockPoint; public LockWrapper (object lockPoint) { _lockPoint = lockPoint; Monitor.Enter (_lockPoint); } public void Dispose () { Monitor.Exit (_lockPoint); } } class FakeLockWrapper : IDisposable { public void Dispose () { } } } }