< Summary

Information
Class: SQLite.AsyncTableQuery<T>
Assembly: SQLite.Tests
File(s): /home/runner/work/sqlite-net/sqlite-net/src/SQLiteAsync.cs
Tag: 197_13668160376
Line coverage
56%
Covered lines: 42
Uncovered lines: 32
Coverable lines: 74
Total lines: 1992
Line coverage: 56.7%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%110100%
ReadAsync(...)0%110100%
WriteAsync(...)0%2100%
Where(...)0%110100%
Skip(...)0%110100%
Take(...)0%110100%
OrderBy(...)0%110100%
OrderByDescending(...)0%110100%
ThenBy(...)0%2100%
ThenByDescending(...)0%2100%
ToListAsync()0%110100%
ToArrayAsync()0%2100%
CountAsync()0%110100%
CountAsync(...)0%2100%
ElementAtAsync(...)0%110100%
FirstAsync()0%110100%
FirstOrDefaultAsync()0%110100%
FirstAsync(...)0%2100%
FirstOrDefaultAsync(...)0%2100%
DeleteAsync(...)0%2100%
DeleteAsync()0%2100%

File(s)

/home/runner/work/sqlite-net/sqlite-net/src/SQLiteAsync.cs

#LineLine coverage
 1//
 2// Copyright (c) 2012-2024 Krueger Systems, Inc.
 3//
 4// Permission is hereby granted, free of charge, to any person obtaining a copy
 5// of this software and associated documentation files (the "Software"), to deal
 6// in the Software without restriction, including without limitation the rights
 7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 8// copies of the Software, and to permit persons to whom the Software is
 9// furnished to do so, subject to the following conditions:
 10//
 11// The above copyright notice and this permission notice shall be included in
 12// all copies or substantial portions of the Software.
 13//
 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 20// THE SOFTWARE.
 21//
 22
 23using System;
 24using System.Collections;
 25using System.Collections.Generic;
 26using System.Diagnostics.CodeAnalysis;
 27using System.Linq;
 28using System.Linq.Expressions;
 29using System.Threading;
 30using System.Threading.Tasks;
 31
 32#pragma warning disable 1591 // XML Doc Comments
 33
 34namespace SQLite
 35{
 36  public interface ISQLiteAsyncConnection
 37  {
 38    string DatabasePath { get; }
 39    int LibVersionNumber { get; }
 40    string DateTimeStringFormat { get; }
 41    bool StoreDateTimeAsTicks { get; }
 42    bool StoreTimeSpanAsTicks { get; }
 43    bool Trace { get; set; }
 44    Action<string> Tracer { get; set; }
 45    bool TimeExecution { get; set; }
 46    IEnumerable<TableMapping> TableMappings { get; }
 47
 48    Task BackupAsync (string destinationDatabasePath, string databaseName = "main");
 49    Task CloseAsync ();
 50    Task<int> CreateIndexAsync (string tableName, string columnName, bool unique = false);
 51    Task<int> CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false);
 52    Task<int> CreateIndexAsync (string tableName, string[] columnNames, bool unique = false);
 53    Task<int> CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false);
 54    Task<int> CreateIndexAsync<
 55#if NET8_0_OR_GREATER
 56      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 57#endif
 58      T> (Expression<Func<T, object>> property, bool unique = false);
 59    Task<CreateTableResult> CreateTableAsync<
 60#if NET8_0_OR_GREATER
 61      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 62#endif
 63      T> (CreateFlags createFlags = CreateFlags.None) where T : new();
 64    Task<CreateTableResult> CreateTableAsync (
 65#if NET8_0_OR_GREATER
 66      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 67#endif
 68      Type ty,
 69      CreateFlags createFlags = CreateFlags.None);
 70    Task<CreateTablesResult> CreateTablesAsync<
 71#if NET8_0_OR_GREATER
 72      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 73#endif
 74      T,
 75#if NET8_0_OR_GREATER
 76      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 77#endif
 78      T2> (CreateFlags createFlags = CreateFlags.None)
 79      where T : new()
 80      where T2 : new();
 81    Task<CreateTablesResult> CreateTablesAsync<
 82#if NET8_0_OR_GREATER
 83      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 84#endif
 85      T,
 86#if NET8_0_OR_GREATER
 87      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 88#endif
 89      T2,
 90#if NET8_0_OR_GREATER
 91      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 92#endif
 93      T3> (CreateFlags createFlags = CreateFlags.None)
 94      where T : new()
 95      where T2 : new()
 96      where T3 : new();
 97    Task<CreateTablesResult> CreateTablesAsync<
 98#if NET8_0_OR_GREATER
 99      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 100#endif
 101      T,
 102#if NET8_0_OR_GREATER
 103      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 104#endif
 105      T2,
 106#if NET8_0_OR_GREATER
 107      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 108#endif
 109      T3,
 110#if NET8_0_OR_GREATER
 111      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 112#endif
 113      T4> (CreateFlags createFlags = CreateFlags.None)
 114      where T : new()
 115      where T2 : new()
 116      where T3 : new()
 117      where T4 : new();
 118    Task<CreateTablesResult> CreateTablesAsync<
 119#if NET8_0_OR_GREATER
 120      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 121#endif
 122      T,
 123#if NET8_0_OR_GREATER
 124      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 125#endif
 126      T2,
 127#if NET8_0_OR_GREATER
 128      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 129#endif
 130      T3,
 131#if NET8_0_OR_GREATER
 132      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 133#endif
 134      T4,
 135#if NET8_0_OR_GREATER
 136      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 137#endif
 138      T5> (CreateFlags createFlags = CreateFlags.None)
 139      where T : new()
 140      where T2 : new()
 141      where T3 : new()
 142      where T4 : new()
 143      where T5 : new();
 144#if NET8_0_OR_GREATER
 145    [RequiresUnreferencedCode ("This method requires 'DynamicallyAccessedMemberTypes.All' on each input 'Type' instance.
 146#endif
 147    Task<CreateTablesResult> CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types);
 148    Task<IEnumerable<T>> DeferredQueryAsync<
 149#if NET8_0_OR_GREATER
 150      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 151#endif
 152      T> (string query, params object[] args) where T : new();
 153    Task<IEnumerable<object>> DeferredQueryAsync (TableMapping map, string query, params object[] args);
 154    Task<int> DeleteAllAsync<
 155#if NET8_0_OR_GREATER
 156      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 157#endif
 158      T> ();
 159    Task<int> DeleteAllAsync (TableMapping map);
 160#if NET8_0_OR_GREATER
 161    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'objec
 162#endif
 163    Task<int> DeleteAsync (object objectToDelete);
 164    Task<int> DeleteAsync<
 165#if NET8_0_OR_GREATER
 166      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 167#endif
 168      T> (object primaryKey);
 169    Task<int> DeleteAsync (object primaryKey, TableMapping map);
 170    Task<int> DropTableAsync<
 171#if NET8_0_OR_GREATER
 172      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 173#endif
 174      T> () where T : new();
 175    Task<int> DropTableAsync (TableMapping map);
 176    Task EnableLoadExtensionAsync (bool enabled);
 177    Task EnableWriteAheadLoggingAsync ();
 178    Task<int> ExecuteAsync (string query, params object[] args);
 179    Task<T> ExecuteScalarAsync<T> (string query, params object[] args);
 180    Task<T> FindAsync<
 181#if NET8_0_OR_GREATER
 182      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 183#endif
 184      T> (object pk) where T : new();
 185    Task<object> FindAsync (object pk, TableMapping map);
 186    Task<T> FindAsync<
 187#if NET8_0_OR_GREATER
 188      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 189#endif
 190      T> (Expression<Func<T, bool>> predicate) where T : new();
 191    Task<T> FindWithQueryAsync<
 192#if NET8_0_OR_GREATER
 193      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 194#endif
 195      T> (string query, params object[] args) where T : new();
 196    Task<object> FindWithQueryAsync (TableMapping map, string query, params object[] args);
 197    Task<T> GetAsync<
 198#if NET8_0_OR_GREATER
 199      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 200#endif
 201      T> (object pk) where T : new();
 202    Task<object> GetAsync (object pk, TableMapping map);
 203    Task<T> GetAsync<
 204#if NET8_0_OR_GREATER
 205      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 206#endif
 207      T> (Expression<Func<T, bool>> predicate) where T : new();
 208    TimeSpan GetBusyTimeout ();
 209    SQLiteConnectionWithLock GetConnection ();
 210    Task<TableMapping> GetMappingAsync (
 211#if NET8_0_OR_GREATER
 212      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 213#endif
 214      Type type,
 215      CreateFlags createFlags = CreateFlags.None);
 216    Task<TableMapping> GetMappingAsync<
 217#if NET8_0_OR_GREATER
 218      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 219#endif
 220      T> (CreateFlags createFlags = CreateFlags.None) where T : new();
 221    Task<List<SQLiteConnection.ColumnInfo>> GetTableInfoAsync (string tableName);
 222#if NET8_0_OR_GREATER
 223    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 224#endif
 225    Task<int> InsertAllAsync (IEnumerable objects, bool runInTransaction = true);
 226#if NET8_0_OR_GREATER
 227    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 228#endif
 229    Task<int> InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true);
 230#if NET8_0_OR_GREATER
 231    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 232#endif
 233    Task<int> InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true);
 234#if NET8_0_OR_GREATER
 235    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 236#endif
 237    Task<int> InsertAsync (object obj);
 238    Task<int> InsertAsync (
 239      object obj,
 240#if NET8_0_OR_GREATER
 241      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 242#endif
 243      Type objType);
 244#if NET8_0_OR_GREATER
 245    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 246#endif
 247    Task<int> InsertAsync (object obj, string extra);
 248    Task<int> InsertAsync (
 249      object obj,
 250      string extra,
 251#if NET8_0_OR_GREATER
 252      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 253#endif
 254      Type objType);
 255#if NET8_0_OR_GREATER
 256    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 257#endif
 258    Task<int> InsertOrReplaceAsync (object obj);
 259    Task<int> InsertOrReplaceAsync (
 260      object obj,
 261#if NET8_0_OR_GREATER
 262      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 263#endif
 264      Type objType);
 265    Task<List<T>> QueryAsync<
 266#if NET8_0_OR_GREATER
 267      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 268#endif
 269      T> (string query, params object[] args) where T : new();
 270    Task<List<object>> QueryAsync (TableMapping map, string query, params object[] args);
 271    Task<List<T>> QueryScalarsAsync<T> (string query, params object[] args);
 272    Task ReKeyAsync (string key);
 273    Task ReKeyAsync (byte[] key);
 274    Task RunInTransactionAsync (Action<SQLiteConnection> action);
 275    Task SetBusyTimeoutAsync (TimeSpan value);
 276    AsyncTableQuery<T> Table<
 277#if NET8_0_OR_GREATER
 278      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 279#endif
 280      T> () where T : new();
 281#if NET8_0_OR_GREATER
 282    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 283#endif
 284    Task<int> UpdateAllAsync (IEnumerable objects, bool runInTransaction = true);
 285#if NET8_0_OR_GREATER
 286    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 287#endif
 288    Task<int> UpdateAsync (object obj);
 289    Task<int> UpdateAsync (
 290      object obj,
 291#if NET8_0_OR_GREATER
 292      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 293#endif
 294      Type objType);
 295  }
 296
 297  /// <summary>
 298  /// A pooled asynchronous connection to a SQLite database.
 299  /// </summary>
 300  public partial class SQLiteAsyncConnection : ISQLiteAsyncConnection
 301  {
 302    readonly SQLiteConnectionString _connectionString;
 303
 304    /// <summary>
 305    /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath.
 306    /// </summary>
 307    /// <param name="databasePath">
 308    /// Specifies the path to the database file.
 309    /// </param>
 310    /// <param name="storeDateTimeAsTicks">
 311    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 312    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 313    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 314    /// down sides, when setting storeDateTimeAsTicks = true.
 315    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 316    /// the storeDateTimeAsTicks parameter.
 317    /// </param>
 318    public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true)
 319      : this (new SQLiteConnectionString (databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenF
 320    {
 321    }
 322
 323    /// <summary>
 324    /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath.
 325    /// </summary>
 326    /// <param name="databasePath">
 327    /// Specifies the path to the database file.
 328    /// </param>
 329    /// <param name="openFlags">
 330    /// Flags controlling how the connection should be opened.
 331    /// Async connections should have the FullMutex flag set to provide best performance.
 332    /// </param>
 333    /// <param name="storeDateTimeAsTicks">
 334    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 335    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 336    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 337    /// down sides, when setting storeDateTimeAsTicks = true.
 338    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 339    /// the storeDateTimeAsTicks parameter.
 340    /// </param>
 341    public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true)
 342      : this (new SQLiteConnectionString (databasePath, openFlags, storeDateTimeAsTicks))
 343    {
 344    }
 345
 346    /// <summary>
 347    /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database
 348    /// using the given connection string.
 349    /// </summary>
 350    /// <param name="connectionString">
 351    /// Details on how to find and open the database.
 352    /// </param>
 353    public SQLiteAsyncConnection (SQLiteConnectionString connectionString)
 354    {
 355      _connectionString = connectionString;
 356    }
 357
 358    /// <summary>
 359    /// Gets the database path used by this connection.
 360    /// </summary>
 361    public string DatabasePath => GetConnection ().DatabasePath;
 362
 363    /// <summary>
 364    /// Gets the SQLite library version number. 3007014 would be v3.7.14
 365    /// </summary>
 366    public int LibVersionNumber => GetConnection ().LibVersionNumber;
 367
 368    /// <summary>
 369    /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
 370    /// </summary>
 371    /// <value>The date time string format.</value>
 372    public string DateTimeStringFormat => GetConnection ().DateTimeStringFormat;
 373
 374    /// <summary>
 375    /// The amount of time to wait for a table to become unlocked.
 376    /// </summary>
 377    public TimeSpan GetBusyTimeout ()
 378    {
 379      return GetConnection ().BusyTimeout;
 380    }
 381
 382    /// <summary>
 383    /// Sets the amount of time to wait for a table to become unlocked.
 384    /// </summary>
 385    public Task SetBusyTimeoutAsync (TimeSpan value)
 386    {
 387      return ReadAsync<object> (conn => {
 388        conn.BusyTimeout = value;
 389        return null;
 390      });
 391    }
 392
 393    /// <summary>
 394    /// Enables the write ahead logging. WAL is significantly faster in most scenarios
 395    /// by providing better concurrency and better disk IO performance than the normal
 396    /// journal mode. You only need to call this function once in the lifetime of the database.
 397    /// </summary>
 398    public Task EnableWriteAheadLoggingAsync ()
 399    {
 400      return WriteAsync<object> (conn => {
 401        conn.EnableWriteAheadLogging ();
 402        return null;
 403      });
 404    }
 405
 406    /// <summary>
 407    /// Whether to store DateTime properties as ticks (true) or strings (false).
 408    /// </summary>
 409    public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks;
 410
 411    /// <summary>
 412    /// Whether to store TimeSpan properties as ticks (true) or strings (false).
 413    /// </summary>
 414    public bool StoreTimeSpanAsTicks => GetConnection ().StoreTimeSpanAsTicks;
 415
 416    /// <summary>
 417    /// Whether to writer queries to <see cref="Tracer"/> during execution.
 418    /// </summary>
 419    /// <value>The tracer.</value>
 420    public bool Trace {
 421      get { return GetConnection ().Trace; }
 422      set { GetConnection ().Trace = value; }
 423    }
 424
 425    /// <summary>
 426    /// The delegate responsible for writing trace lines.
 427    /// </summary>
 428    /// <value>The tracer.</value>
 429    public Action<string> Tracer {
 430      get { return GetConnection ().Tracer; }
 431      set { GetConnection ().Tracer = value; }
 432    }
 433
 434    /// <summary>
 435    /// Whether Trace lines should be written that show the execution time of queries.
 436    /// </summary>
 437    public bool TimeExecution {
 438      get { return GetConnection ().TimeExecution; }
 439      set { GetConnection ().TimeExecution = value; }
 440    }
 441
 442    /// <summary>
 443    /// Returns the mappings from types to tables that the connection
 444    /// currently understands.
 445    /// </summary>
 446    public IEnumerable<TableMapping> TableMappings => GetConnection ().TableMappings;
 447
 448    /// <summary>
 449    /// Closes all connections to all async databases.
 450    /// You should *never* need to do this.
 451    /// This is a blocking operation that will return when all connections
 452    /// have been closed.
 453    /// </summary>
 454    public static void ResetPool ()
 455    {
 456      SQLiteConnectionPool.Shared.Reset ();
 457    }
 458
 459    /// <summary>
 460    /// Gets the pooled lockable connection used by this async connection.
 461    /// You should never need to use this. This is provided only to add additional
 462    /// functionality to SQLite-net. If you use this connection, you must use
 463    /// the Lock method on it while using it.
 464    /// </summary>
 465    public SQLiteConnectionWithLock GetConnection ()
 466    {
 467      return SQLiteConnectionPool.Shared.GetConnection (_connectionString);
 468    }
 469
 470    SQLiteConnectionWithLock GetConnectionAndTransactionLock (out object transactionLock)
 471    {
 472      return SQLiteConnectionPool.Shared.GetConnectionAndTransactionLock (_connectionString, out transactionLock);
 473    }
 474
 475    /// <summary>
 476    /// Closes any pooled connections used by the database.
 477    /// </summary>
 478    public Task CloseAsync ()
 479    {
 480      return Task.Factory.StartNew (() => {
 481        SQLiteConnectionPool.Shared.CloseConnection (_connectionString);
 482      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 483    }
 484
 485    Task<T> ReadAsync<T> (Func<SQLiteConnectionWithLock, T> read)
 486    {
 487      return Task.Factory.StartNew (() => {
 488        var conn = GetConnection ();
 489        using (conn.Lock ()) {
 490          return read (conn);
 491        }
 492      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 493    }
 494
 495    Task<T> WriteAsync<T> (Func<SQLiteConnectionWithLock, T> write)
 496    {
 497      return Task.Factory.StartNew (() => {
 498        var conn = GetConnection ();
 499        using (conn.Lock ()) {
 500          return write (conn);
 501        }
 502      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 503    }
 504
 505    Task<T> TransactAsync<T> (Func<SQLiteConnectionWithLock, T> transact)
 506    {
 507      return Task.Factory.StartNew (() => {
 508        var conn = GetConnectionAndTransactionLock (out var transactionLock);
 509        lock (transactionLock) {
 510          using (conn.Lock ()) {
 511            return transact (conn);
 512          }
 513        }
 514      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 515    }
 516
 517    /// <summary>
 518    /// Enable or disable extension loading.
 519    /// </summary>
 520    public Task EnableLoadExtensionAsync (bool enabled)
 521    {
 522      return WriteAsync<object> (conn => {
 523        conn.EnableLoadExtension (enabled);
 524        return null;
 525      });
 526    }
 527
 528    /// <summary>
 529    /// Executes a "create table if not exists" on the database. It also
 530    /// creates any specified indexes on the columns of the table. It uses
 531    /// a schema automatically generated from the specified type. You can
 532    /// later access this schema by calling GetMapping.
 533    /// </summary>
 534    /// <returns>
 535    /// Whether the table was created or migrated.
 536    /// </returns>
 537    public Task<CreateTableResult> CreateTableAsync<
 538#if NET8_0_OR_GREATER
 539      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 540#endif
 541      T> (CreateFlags createFlags = CreateFlags.None)
 542      where T : new()
 543    {
 544      return WriteAsync (conn => conn.CreateTable<T> (createFlags));
 545    }
 546
 547    /// <summary>
 548    /// Executes a "create table if not exists" on the database. It also
 549    /// creates any specified indexes on the columns of the table. It uses
 550    /// a schema automatically generated from the specified type. You can
 551    /// later access this schema by calling GetMapping.
 552    /// </summary>
 553    /// <param name="ty">Type to reflect to a database table.</param>
 554    /// <param name="createFlags">Optional flags allowing implicit PK and indexes based on naming conventions.</param>
 555    /// <returns>
 556    /// Whether the table was created or migrated.
 557    /// </returns>
 558    public Task<CreateTableResult> CreateTableAsync (
 559#if NET8_0_OR_GREATER
 560      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 561#endif
 562      Type ty,
 563      CreateFlags createFlags = CreateFlags.None)
 564    {
 565      return WriteAsync (conn => conn.CreateTable (ty, createFlags));
 566    }
 567
 568    /// <summary>
 569    /// Executes a "create table if not exists" on the database for each type. It also
 570    /// creates any specified indexes on the columns of the table. It uses
 571    /// a schema automatically generated from the specified type. You can
 572    /// later access this schema by calling GetMapping.
 573    /// </summary>
 574    /// <returns>
 575    /// Whether the table was created or migrated for each type.
 576    /// </returns>
 577#if NET8_0_OR_GREATER
 578    [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This method preserves metadata for all type ar
 579#endif
 580    public Task<CreateTablesResult> CreateTablesAsync<
 581#if NET8_0_OR_GREATER
 582      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 583#endif
 584      T,
 585#if NET8_0_OR_GREATER
 586      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 587#endif
 588      T2> (CreateFlags createFlags = CreateFlags.None)
 589      where T : new()
 590      where T2 : new()
 591    {
 592      return CreateTablesAsync (createFlags, typeof (T), typeof (T2));
 593    }
 594
 595    /// <summary>
 596    /// Executes a "create table if not exists" on the database for each type. It also
 597    /// creates any specified indexes on the columns of the table. It uses
 598    /// a schema automatically generated from the specified type. You can
 599    /// later access this schema by calling GetMapping.
 600    /// </summary>
 601    /// <returns>
 602    /// Whether the table was created or migrated for each type.
 603    /// </returns>
 604#if NET8_0_OR_GREATER
 605    [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This method preserves metadata for all type ar
 606#endif
 607    public Task<CreateTablesResult> CreateTablesAsync<
 608#if NET8_0_OR_GREATER
 609      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 610#endif
 611      T,
 612#if NET8_0_OR_GREATER
 613      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 614#endif
 615      T2,
 616#if NET8_0_OR_GREATER
 617      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 618#endif
 619      T3> (CreateFlags createFlags = CreateFlags.None)
 620      where T : new()
 621      where T2 : new()
 622      where T3 : new()
 623    {
 624      return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3));
 625    }
 626
 627    /// <summary>
 628    /// Executes a "create table if not exists" on the database for each type. It also
 629    /// creates any specified indexes on the columns of the table. It uses
 630    /// a schema automatically generated from the specified type. You can
 631    /// later access this schema by calling GetMapping.
 632    /// </summary>
 633    /// <returns>
 634    /// Whether the table was created or migrated for each type.
 635    /// </returns>
 636#if NET8_0_OR_GREATER
 637    [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This method preserves metadata for all type ar
 638#endif
 639    public Task<CreateTablesResult> CreateTablesAsync<
 640#if NET8_0_OR_GREATER
 641      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 642#endif
 643      T,
 644#if NET8_0_OR_GREATER
 645      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 646#endif
 647      T2,
 648#if NET8_0_OR_GREATER
 649      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 650#endif
 651      T3,
 652#if NET8_0_OR_GREATER
 653      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 654#endif
 655      T4> (CreateFlags createFlags = CreateFlags.None)
 656      where T : new()
 657      where T2 : new()
 658      where T3 : new()
 659      where T4 : new()
 660    {
 661      return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4));
 662    }
 663
 664    /// <summary>
 665    /// Executes a "create table if not exists" on the database for each type. It also
 666    /// creates any specified indexes on the columns of the table. It uses
 667    /// a schema automatically generated from the specified type. You can
 668    /// later access this schema by calling GetMapping.
 669    /// </summary>
 670    /// <returns>
 671    /// Whether the table was created or migrated for each type.
 672    /// </returns>
 673#if NET8_0_OR_GREATER
 674    [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This method preserves metadata for all type ar
 675#endif
 676    public Task<CreateTablesResult> CreateTablesAsync<
 677#if NET8_0_OR_GREATER
 678      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 679#endif
 680      T,
 681#if NET8_0_OR_GREATER
 682      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 683#endif
 684      T2,
 685#if NET8_0_OR_GREATER
 686      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 687#endif
 688      T3,
 689#if NET8_0_OR_GREATER
 690      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 691#endif
 692      T4,
 693#if NET8_0_OR_GREATER
 694      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 695#endif
 696      T5> (CreateFlags createFlags = CreateFlags.None)
 697      where T : new()
 698      where T2 : new()
 699      where T3 : new()
 700      where T4 : new()
 701      where T5 : new()
 702    {
 703      return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5));
 704    }
 705
 706    /// <summary>
 707    /// Executes a "create table if not exists" on the database for each type. It also
 708    /// creates any specified indexes on the columns of the table. It uses
 709    /// a schema automatically generated from the specified type. You can
 710    /// later access this schema by calling GetMapping.
 711    /// </summary>
 712    /// <returns>
 713    /// Whether the table was created or migrated for each type.
 714    /// </returns>
 715#if NET8_0_OR_GREATER
 716    [RequiresUnreferencedCode ("This method requires 'DynamicallyAccessedMemberTypes.All' on each input 'Type' instance.
 717#endif
 718    public Task<CreateTablesResult> CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types)
 719    {
 720      return WriteAsync (conn => conn.CreateTables (createFlags, types));
 721    }
 722
 723    /// <summary>
 724    /// Executes a "drop table" on the database.  This is non-recoverable.
 725    /// </summary>
 726    public Task<int> DropTableAsync<
 727#if NET8_0_OR_GREATER
 728      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 729#endif
 730      T> ()
 731      where T : new()
 732    {
 733      return WriteAsync (conn => conn.DropTable<T> ());
 734    }
 735
 736    /// <summary>
 737    /// Executes a "drop table" on the database.  This is non-recoverable.
 738    /// </summary>
 739    /// <param name="map">
 740    /// The TableMapping used to identify the table.
 741    /// </param>
 742    public Task<int> DropTableAsync (TableMapping map)
 743    {
 744      return WriteAsync (conn => conn.DropTable (map));
 745    }
 746
 747    /// <summary>
 748    /// Creates an index for the specified table and column.
 749    /// </summary>
 750    /// <param name="tableName">Name of the database table</param>
 751    /// <param name="columnName">Name of the column to index</param>
 752    /// <param name="unique">Whether the index should be unique</param>
 753    /// <returns>Zero on success.</returns>
 754    public Task<int> CreateIndexAsync (string tableName, string columnName, bool unique = false)
 755    {
 756      return WriteAsync (conn => conn.CreateIndex (tableName, columnName, unique));
 757    }
 758
 759    /// <summary>
 760    /// Creates an index for the specified table and column.
 761    /// </summary>
 762    /// <param name="indexName">Name of the index to create</param>
 763    /// <param name="tableName">Name of the database table</param>
 764    /// <param name="columnName">Name of the column to index</param>
 765    /// <param name="unique">Whether the index should be unique</param>
 766    /// <returns>Zero on success.</returns>
 767    public Task<int> CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false)
 768    {
 769      return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnName, unique));
 770    }
 771
 772    /// <summary>
 773    /// Creates an index for the specified table and columns.
 774    /// </summary>
 775    /// <param name="tableName">Name of the database table</param>
 776    /// <param name="columnNames">An array of column names to index</param>
 777    /// <param name="unique">Whether the index should be unique</param>
 778    /// <returns>Zero on success.</returns>
 779    public Task<int> CreateIndexAsync (string tableName, string[] columnNames, bool unique = false)
 780    {
 781      return WriteAsync (conn => conn.CreateIndex (tableName, columnNames, unique));
 782    }
 783
 784    /// <summary>
 785    /// Creates an index for the specified table and columns.
 786    /// </summary>
 787    /// <param name="indexName">Name of the index to create</param>
 788    /// <param name="tableName">Name of the database table</param>
 789    /// <param name="columnNames">An array of column names to index</param>
 790    /// <param name="unique">Whether the index should be unique</param>
 791    /// <returns>Zero on success.</returns>
 792    public Task<int> CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false)
 793    {
 794      return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnNames, unique));
 795    }
 796
 797    /// <summary>
 798    /// Creates an index for the specified object property.
 799    /// e.g. CreateIndex&lt;Client&gt;(c => c.Name);
 800    /// </summary>
 801    /// <typeparam name="T">Type to reflect to a database table.</typeparam>
 802    /// <param name="property">Property to index</param>
 803    /// <param name="unique">Whether the index should be unique</param>
 804    /// <returns>Zero on success.</returns>
 805    public Task<int> CreateIndexAsync<
 806#if NET8_0_OR_GREATER
 807      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 808#endif
 809      T> (Expression<Func<T, object>> property, bool unique = false)
 810    {
 811      return WriteAsync (conn => conn.CreateIndex (property, unique));
 812    }
 813
 814    /// <summary>
 815    /// Inserts the given object and (and updates its
 816    /// auto incremented primary key if it has one).
 817    /// </summary>
 818    /// <param name="obj">
 819    /// The object to insert.
 820    /// </param>
 821    /// <returns>
 822    /// The number of rows added to the table.
 823    /// </returns>
 824#if NET8_0_OR_GREATER
 825    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 826#endif
 827    public Task<int> InsertAsync (object obj)
 828    {
 829      return WriteAsync (conn => conn.Insert (obj));
 830    }
 831
 832    /// <summary>
 833    /// Inserts the given object (and updates its
 834    /// auto incremented primary key if it has one).
 835    /// The return value is the number of rows added to the table.
 836    /// </summary>
 837    /// <param name="obj">
 838    /// The object to insert.
 839    /// </param>
 840    /// <param name="objType">
 841    /// The type of object to insert.
 842    /// </param>
 843    /// <returns>
 844    /// The number of rows added to the table.
 845    /// </returns>
 846    public Task<int> InsertAsync (
 847      object obj,
 848#if NET8_0_OR_GREATER
 849      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 850#endif
 851      Type objType)
 852    {
 853      return WriteAsync (conn => conn.Insert (obj, objType));
 854    }
 855
 856    /// <summary>
 857    /// Inserts the given object (and updates its
 858    /// auto incremented primary key if it has one).
 859    /// The return value is the number of rows added to the table.
 860    /// </summary>
 861    /// <param name="obj">
 862    /// The object to insert.
 863    /// </param>
 864    /// <param name="extra">
 865    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 866    /// </param>
 867    /// <returns>
 868    /// The number of rows added to the table.
 869    /// </returns>
 870#if NET8_0_OR_GREATER
 871    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 872#endif
 873    public Task<int> InsertAsync (object obj, string extra)
 874    {
 875      return WriteAsync (conn => conn.Insert (obj, extra));
 876    }
 877
 878    /// <summary>
 879    /// Inserts the given object (and updates its
 880    /// auto incremented primary key if it has one).
 881    /// The return value is the number of rows added to the table.
 882    /// </summary>
 883    /// <param name="obj">
 884    /// The object to insert.
 885    /// </param>
 886    /// <param name="extra">
 887    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 888    /// </param>
 889    /// <param name="objType">
 890    /// The type of object to insert.
 891    /// </param>
 892    /// <returns>
 893    /// The number of rows added to the table.
 894    /// </returns>
 895    public Task<int> InsertAsync (
 896      object obj,
 897      string extra,
 898#if NET8_0_OR_GREATER
 899      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 900#endif
 901      Type objType)
 902    {
 903      return WriteAsync (conn => conn.Insert (obj, extra, objType));
 904    }
 905
 906    /// <summary>
 907    /// Inserts the given object (and updates its
 908    /// auto incremented primary key if it has one).
 909    /// The return value is the number of rows added to the table.
 910    /// If a UNIQUE constraint violation occurs with
 911    /// some pre-existing object, this function deletes
 912    /// the old object.
 913    /// </summary>
 914    /// <param name="obj">
 915    /// The object to insert.
 916    /// </param>
 917    /// <returns>
 918    /// The number of rows modified.
 919    /// </returns>
 920#if NET8_0_OR_GREATER
 921    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 922#endif
 923    public Task<int> InsertOrReplaceAsync (object obj)
 924    {
 925      return WriteAsync (conn => conn.InsertOrReplace (obj));
 926    }
 927
 928    /// <summary>
 929    /// Inserts the given object (and updates its
 930    /// auto incremented primary key if it has one).
 931    /// The return value is the number of rows added to the table.
 932    /// If a UNIQUE constraint violation occurs with
 933    /// some pre-existing object, this function deletes
 934    /// the old object.
 935    /// </summary>
 936    /// <param name="obj">
 937    /// The object to insert.
 938    /// </param>
 939    /// <param name="objType">
 940    /// The type of object to insert.
 941    /// </param>
 942    /// <returns>
 943    /// The number of rows modified.
 944    /// </returns>
 945    public Task<int> InsertOrReplaceAsync (
 946      object obj,
 947#if NET8_0_OR_GREATER
 948      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 949#endif
 950      Type objType)
 951    {
 952      return WriteAsync (conn => conn.InsertOrReplace (obj, objType));
 953    }
 954
 955    /// <summary>
 956    /// Updates all of the columns of a table using the specified object
 957    /// except for its primary key.
 958    /// The object is required to have a primary key.
 959    /// </summary>
 960    /// <param name="obj">
 961    /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
 962    /// </param>
 963    /// <returns>
 964    /// The number of rows updated.
 965    /// </returns>
 966#if NET8_0_OR_GREATER
 967    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'obj'.
 968#endif
 969    public Task<int> UpdateAsync (object obj)
 970    {
 971      return WriteAsync (conn => conn.Update (obj));
 972    }
 973
 974    /// <summary>
 975    /// Updates all of the columns of a table using the specified object
 976    /// except for its primary key.
 977    /// The object is required to have a primary key.
 978    /// </summary>
 979    /// <param name="obj">
 980    /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
 981    /// </param>
 982    /// <param name="objType">
 983    /// The type of object to insert.
 984    /// </param>
 985    /// <returns>
 986    /// The number of rows updated.
 987    /// </returns>
 988    public Task<int> UpdateAsync (
 989      object obj,
 990#if NET8_0_OR_GREATER
 991      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 992#endif
 993      Type objType)
 994    {
 995      return WriteAsync (conn => conn.Update (obj, objType));
 996    }
 997
 998    /// <summary>
 999    /// Updates all specified objects.
 1000    /// </summary>
 1001    /// <param name="objects">
 1002    /// An <see cref="IEnumerable"/> of the objects to insert.
 1003    /// </param>
 1004    /// <param name="runInTransaction">
 1005    /// A boolean indicating if the inserts should be wrapped in a transaction
 1006    /// </param>
 1007    /// <returns>
 1008    /// The number of rows modified.
 1009    /// </returns>
 1010#if NET8_0_OR_GREATER
 1011    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 1012#endif
 1013    public Task<int> UpdateAllAsync (IEnumerable objects, bool runInTransaction = true)
 1014    {
 1015      return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction));
 1016    }
 1017
 1018    /// <summary>
 1019    /// Deletes the given object from the database using its primary key.
 1020    /// </summary>
 1021    /// <param name="objectToDelete">
 1022    /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute.
 1023    /// </param>
 1024    /// <returns>
 1025    /// The number of rows deleted.
 1026    /// </returns>
 1027#if NET8_0_OR_GREATER
 1028    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of 'objec
 1029#endif
 1030    public Task<int> DeleteAsync (object objectToDelete)
 1031    {
 1032      return WriteAsync (conn => conn.Delete (objectToDelete));
 1033    }
 1034
 1035    /// <summary>
 1036    /// Deletes the object with the specified primary key.
 1037    /// </summary>
 1038    /// <param name="primaryKey">
 1039    /// The primary key of the object to delete.
 1040    /// </param>
 1041    /// <returns>
 1042    /// The number of objects deleted.
 1043    /// </returns>
 1044    /// <typeparam name='T'>
 1045    /// The type of object.
 1046    /// </typeparam>
 1047    public Task<int> DeleteAsync<
 1048#if NET8_0_OR_GREATER
 1049      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1050#endif
 1051      T> (object primaryKey)
 1052    {
 1053      return WriteAsync (conn => conn.Delete<T> (primaryKey));
 1054    }
 1055
 1056    /// <summary>
 1057    /// Deletes the object with the specified primary key.
 1058    /// </summary>
 1059    /// <param name="primaryKey">
 1060    /// The primary key of the object to delete.
 1061    /// </param>
 1062    /// <param name="map">
 1063    /// The TableMapping used to identify the table.
 1064    /// </param>
 1065    /// <returns>
 1066    /// The number of objects deleted.
 1067    /// </returns>
 1068    public Task<int> DeleteAsync (object primaryKey, TableMapping map)
 1069    {
 1070      return WriteAsync (conn => conn.Delete (primaryKey, map));
 1071    }
 1072
 1073    /// <summary>
 1074    /// Deletes all the objects from the specified table.
 1075    /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
 1076    /// specified table. Do you really want to do that?
 1077    /// </summary>
 1078    /// <returns>
 1079    /// The number of objects deleted.
 1080    /// </returns>
 1081    /// <typeparam name='T'>
 1082    /// The type of objects to delete.
 1083    /// </typeparam>
 1084    public Task<int> DeleteAllAsync<
 1085#if NET8_0_OR_GREATER
 1086      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1087#endif
 1088      T> ()
 1089    {
 1090      return WriteAsync (conn => conn.DeleteAll<T> ());
 1091    }
 1092
 1093    /// <summary>
 1094    /// Deletes all the objects from the specified table.
 1095    /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
 1096    /// specified table. Do you really want to do that?
 1097    /// </summary>
 1098    /// <param name="map">
 1099    /// The TableMapping used to identify the table.
 1100    /// </param>
 1101    /// <returns>
 1102    /// The number of objects deleted.
 1103    /// </returns>
 1104    public Task<int> DeleteAllAsync (TableMapping map)
 1105    {
 1106      return WriteAsync (conn => conn.DeleteAll (map));
 1107    }
 1108
 1109    /// <summary>
 1110    /// Backup the entire database to the specified path.
 1111    /// </summary>
 1112    /// <param name="destinationDatabasePath">Path to backup file.</param>
 1113    /// <param name="databaseName">The name of the database to backup (usually "main").</param>
 1114    public Task BackupAsync (string destinationDatabasePath, string databaseName = "main")
 1115    {
 1116      return WriteAsync (conn => {
 1117        conn.Backup (destinationDatabasePath, databaseName);
 1118        return 0;
 1119      });
 1120    }
 1121
 1122    /// <summary>
 1123    /// Attempts to retrieve an object with the given primary key from the table
 1124    /// associated with the specified type. Use of this method requires that
 1125    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1126    /// </summary>
 1127    /// <param name="pk">
 1128    /// The primary key.
 1129    /// </param>
 1130    /// <returns>
 1131    /// The object with the given primary key. Throws a not found exception
 1132    /// if the object is not found.
 1133    /// </returns>
 1134    public Task<T> GetAsync<
 1135#if NET8_0_OR_GREATER
 1136      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1137#endif
 1138      T> (object pk)
 1139      where T : new()
 1140    {
 1141      return ReadAsync (conn => conn.Get<T> (pk));
 1142    }
 1143
 1144    /// <summary>
 1145    /// Attempts to retrieve an object with the given primary key from the table
 1146    /// associated with the specified type. Use of this method requires that
 1147    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1148    /// </summary>
 1149    /// <param name="pk">
 1150    /// The primary key.
 1151    /// </param>
 1152    /// <param name="map">
 1153    /// The TableMapping used to identify the table.
 1154    /// </param>
 1155    /// <returns>
 1156    /// The object with the given primary key. Throws a not found exception
 1157    /// if the object is not found.
 1158    /// </returns>
 1159    public Task<object> GetAsync (object pk, TableMapping map)
 1160    {
 1161      return ReadAsync (conn => conn.Get (pk, map));
 1162    }
 1163
 1164    /// <summary>
 1165    /// Attempts to retrieve the first object that matches the predicate from the table
 1166    /// associated with the specified type.
 1167    /// </summary>
 1168    /// <param name="predicate">
 1169    /// A predicate for which object to find.
 1170    /// </param>
 1171    /// <returns>
 1172    /// The object that matches the given predicate. Throws a not found exception
 1173    /// if the object is not found.
 1174    /// </returns>
 1175    public Task<T> GetAsync<
 1176#if NET8_0_OR_GREATER
 1177      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1178# endif
 1179      T> (Expression<Func<T, bool>> predicate)
 1180      where T : new()
 1181    {
 1182      return ReadAsync (conn => conn.Get<T> (predicate));
 1183    }
 1184
 1185    /// <summary>
 1186    /// Attempts to retrieve an object with the given primary key from the table
 1187    /// associated with the specified type. Use of this method requires that
 1188    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1189    /// </summary>
 1190    /// <param name="pk">
 1191    /// The primary key.
 1192    /// </param>
 1193    /// <returns>
 1194    /// The object with the given primary key or null
 1195    /// if the object is not found.
 1196    /// </returns>
 1197    public Task<T> FindAsync<
 1198#if NET8_0_OR_GREATER
 1199      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1200#endif
 1201      T> (object pk)
 1202      where T : new()
 1203    {
 1204      return ReadAsync (conn => conn.Find<T> (pk));
 1205    }
 1206
 1207    /// <summary>
 1208    /// Attempts to retrieve an object with the given primary key from the table
 1209    /// associated with the specified type. Use of this method requires that
 1210    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1211    /// </summary>
 1212    /// <param name="pk">
 1213    /// The primary key.
 1214    /// </param>
 1215    /// <param name="map">
 1216    /// The TableMapping used to identify the table.
 1217    /// </param>
 1218    /// <returns>
 1219    /// The object with the given primary key or null
 1220    /// if the object is not found.
 1221    /// </returns>
 1222    public Task<object> FindAsync (object pk, TableMapping map)
 1223    {
 1224      return ReadAsync (conn => conn.Find (pk, map));
 1225    }
 1226
 1227    /// <summary>
 1228    /// Attempts to retrieve the first object that matches the predicate from the table
 1229    /// associated with the specified type.
 1230    /// </summary>
 1231    /// <param name="predicate">
 1232    /// A predicate for which object to find.
 1233    /// </param>
 1234    /// <returns>
 1235    /// The object that matches the given predicate or null
 1236    /// if the object is not found.
 1237    /// </returns>
 1238    public Task<T> FindAsync<
 1239#if NET8_0_OR_GREATER
 1240      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1241#endif
 1242      T> (Expression<Func<T, bool>> predicate)
 1243      where T : new()
 1244    {
 1245      return ReadAsync (conn => conn.Find<T> (predicate));
 1246    }
 1247
 1248    /// <summary>
 1249    /// Attempts to retrieve the first object that matches the query from the table
 1250    /// associated with the specified type.
 1251    /// </summary>
 1252    /// <param name="query">
 1253    /// The fully escaped SQL.
 1254    /// </param>
 1255    /// <param name="args">
 1256    /// Arguments to substitute for the occurences of '?' in the query.
 1257    /// </param>
 1258    /// <returns>
 1259    /// The object that matches the given predicate or null
 1260    /// if the object is not found.
 1261    /// </returns>
 1262    public Task<T> FindWithQueryAsync<
 1263#if NET8_0_OR_GREATER
 1264      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1265#endif
 1266      T> (string query, params object[] args)
 1267      where T : new()
 1268    {
 1269      return ReadAsync (conn => conn.FindWithQuery<T> (query, args));
 1270    }
 1271
 1272    /// <summary>
 1273    /// Attempts to retrieve the first object that matches the query from the table
 1274    /// associated with the specified type.
 1275    /// </summary>
 1276    /// <param name="map">
 1277    /// The TableMapping used to identify the table.
 1278    /// </param>
 1279    /// <param name="query">
 1280    /// The fully escaped SQL.
 1281    /// </param>
 1282    /// <param name="args">
 1283    /// Arguments to substitute for the occurences of '?' in the query.
 1284    /// </param>
 1285    /// <returns>
 1286    /// The object that matches the given predicate or null
 1287    /// if the object is not found.
 1288    /// </returns>
 1289    public Task<object> FindWithQueryAsync (TableMapping map, string query, params object[] args)
 1290    {
 1291      return ReadAsync (conn => conn.FindWithQuery (map, query, args));
 1292    }
 1293
 1294    /// <summary>
 1295    /// Retrieves the mapping that is automatically generated for the given type.
 1296    /// </summary>
 1297    /// <param name="type">
 1298    /// The type whose mapping to the database is returned.
 1299    /// </param>
 1300    /// <param name="createFlags">
 1301    /// Optional flags allowing implicit PK and indexes based on naming conventions
 1302    /// </param>
 1303    /// <returns>
 1304    /// The mapping represents the schema of the columns of the database and contains
 1305    /// methods to set and get properties of objects.
 1306    /// </returns>
 1307    public Task<TableMapping> GetMappingAsync (
 1308#if NET8_0_OR_GREATER
 1309      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1310#endif
 1311      Type type,
 1312      CreateFlags createFlags = CreateFlags.None)
 1313    {
 1314      return ReadAsync (conn => conn.GetMapping (type, createFlags));
 1315    }
 1316
 1317    /// <summary>
 1318    /// Retrieves the mapping that is automatically generated for the given type.
 1319    /// </summary>
 1320    /// <param name="createFlags">
 1321    /// Optional flags allowing implicit PK and indexes based on naming conventions
 1322    /// </param>
 1323    /// <returns>
 1324    /// The mapping represents the schema of the columns of the database and contains
 1325    /// methods to set and get properties of objects.
 1326    /// </returns>
 1327    public Task<TableMapping> GetMappingAsync<
 1328#if NET8_0_OR_GREATER
 1329      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1330#endif
 1331      T> (CreateFlags createFlags = CreateFlags.None)
 1332      where T : new()
 1333    {
 1334      return ReadAsync (conn => conn.GetMapping<T> (createFlags));
 1335    }
 1336
 1337    /// <summary>
 1338    /// Query the built-in sqlite table_info table for a specific tables columns.
 1339    /// </summary>
 1340    /// <returns>The columns contains in the table.</returns>
 1341    /// <param name="tableName">Table name.</param>
 1342    public Task<List<SQLiteConnection.ColumnInfo>> GetTableInfoAsync (string tableName)
 1343    {
 1344      return ReadAsync (conn => conn.GetTableInfo (tableName));
 1345    }
 1346
 1347    /// <summary>
 1348    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1349    /// in the command text for each of the arguments and then executes that command.
 1350    /// Use this method instead of Query when you don't expect rows back. Such cases include
 1351    /// INSERTs, UPDATEs, and DELETEs.
 1352    /// You can set the Trace or TimeExecution properties of the connection
 1353    /// to profile execution.
 1354    /// </summary>
 1355    /// <param name="query">
 1356    /// The fully escaped SQL.
 1357    /// </param>
 1358    /// <param name="args">
 1359    /// Arguments to substitute for the occurences of '?' in the query.
 1360    /// </param>
 1361    /// <returns>
 1362    /// The number of rows modified in the database as a result of this execution.
 1363    /// </returns>
 1364    public Task<int> ExecuteAsync (string query, params object[] args)
 1365    {
 1366      return WriteAsync (conn => conn.Execute (query, args));
 1367    }
 1368
 1369    /// <summary>
 1370    /// Inserts all specified objects.
 1371    /// </summary>
 1372    /// <param name="objects">
 1373    /// An <see cref="IEnumerable"/> of the objects to insert.
 1374    /// </param>
 1375    /// <param name="runInTransaction">
 1376    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1377    /// </param>
 1378    /// <returns>
 1379    /// The number of rows added to the table.
 1380    /// </returns>
 1381#if NET8_0_OR_GREATER
 1382    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 1383#endif
 1384    public Task<int> InsertAllAsync (IEnumerable objects, bool runInTransaction = true)
 1385    {
 1386      return WriteAsync (conn => conn.InsertAll (objects, runInTransaction));
 1387    }
 1388
 1389    /// <summary>
 1390    /// Inserts all specified objects.
 1391    /// </summary>
 1392    /// <param name="objects">
 1393    /// An <see cref="IEnumerable"/> of the objects to insert.
 1394    /// </param>
 1395    /// <param name="extra">
 1396    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 1397    /// </param>
 1398    /// <param name="runInTransaction">
 1399    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1400    /// </param>
 1401    /// <returns>
 1402    /// The number of rows added to the table.
 1403    /// </returns>
 1404#if NET8_0_OR_GREATER
 1405    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 1406#endif
 1407    public Task<int> InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true)
 1408    {
 1409      return WriteAsync (conn => conn.InsertAll (objects, extra, runInTransaction));
 1410    }
 1411
 1412    /// <summary>
 1413    /// Inserts all specified objects.
 1414    /// </summary>
 1415    /// <param name="objects">
 1416    /// An <see cref="IEnumerable"/> of the objects to insert.
 1417    /// </param>
 1418    /// <param name="objType">
 1419    /// The type of object to insert.
 1420    /// </param>
 1421    /// <param name="runInTransaction">
 1422    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1423    /// </param>
 1424    /// <returns>
 1425    /// The number of rows added to the table.
 1426    /// </returns>
 1427#if NET8_0_OR_GREATER
 1428    [RequiresUnreferencedCode ("This method requires ''DynamicallyAccessedMemberTypes.All' on the runtime type of all ob
 1429#endif
 1430    public Task<int> InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true)
 1431    {
 1432      return WriteAsync (conn => conn.InsertAll (objects, objType, runInTransaction));
 1433    }
 1434
 1435    /// <summary>
 1436    /// Executes <paramref name="action"/> within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an
 1437    /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception
 1438    /// is rethrown.
 1439    /// </summary>
 1440    /// <param name="action">
 1441    /// The <see cref="Action"/> to perform within a transaction. <paramref name="action"/> can contain any number
 1442    /// of operations on the connection but should never call <see cref="SQLiteConnection.Commit"/> or
 1443    /// <see cref="SQLiteConnection.Commit"/>.
 1444    /// </param>
 1445    public Task RunInTransactionAsync (Action<SQLiteConnection> action)
 1446    {
 1447      return TransactAsync<object> (conn => {
 1448        conn.BeginTransaction ();
 1449        try {
 1450          action (conn);
 1451          conn.Commit ();
 1452          return null;
 1453        }
 1454        catch (Exception) {
 1455          conn.Rollback ();
 1456          throw;
 1457        }
 1458      });
 1459    }
 1460
 1461    /// <summary>
 1462    /// Returns a queryable interface to the table represented by the given type.
 1463    /// </summary>
 1464    /// <returns>
 1465    /// A queryable object that is able to translate Where, OrderBy, and Take
 1466    /// queries into native SQL.
 1467    /// </returns>
 1468    public AsyncTableQuery<T> Table<
 1469#if NET8_0_OR_GREATER
 1470      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1471#endif
 1472      T> ()
 1473      where T : new()
 1474    {
 1475      //
 1476      // This isn't async as the underlying connection doesn't go out to the database
 1477      // until the query is performed. The Async methods are on the query iteself.
 1478      //
 1479      var conn = GetConnection ();
 1480      return new AsyncTableQuery<T> (conn.Table<T> ());
 1481    }
 1482
 1483    /// <summary>
 1484    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1485    /// in the command text for each of the arguments and then executes that command.
 1486    /// Use this method when return primitive values.
 1487    /// You can set the Trace or TimeExecution properties of the connection
 1488    /// to profile execution.
 1489    /// </summary>
 1490    /// <param name="query">
 1491    /// The fully escaped SQL.
 1492    /// </param>
 1493    /// <param name="args">
 1494    /// Arguments to substitute for the occurences of '?' in the query.
 1495    /// </param>
 1496    /// <returns>
 1497    /// The number of rows modified in the database as a result of this execution.
 1498    /// </returns>
 1499    public Task<T> ExecuteScalarAsync<T> (string query, params object[] args)
 1500    {
 1501      return WriteAsync (conn => {
 1502        var command = conn.CreateCommand (query, args);
 1503        return command.ExecuteScalar<T> ();
 1504      });
 1505    }
 1506
 1507    /// <summary>
 1508    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1509    /// in the command text for each of the arguments and then executes that command.
 1510    /// It returns each row of the result using the mapping automatically generated for
 1511    /// the given type.
 1512    /// </summary>
 1513    /// <param name="query">
 1514    /// The fully escaped SQL.
 1515    /// </param>
 1516    /// <param name="args">
 1517    /// Arguments to substitute for the occurences of '?' in the query.
 1518    /// </param>
 1519    /// <returns>
 1520    /// A list with one result for each row returned by the query.
 1521    /// </returns>
 1522    public Task<List<T>> QueryAsync<
 1523#if NET8_0_OR_GREATER
 1524      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1525#endif
 1526      T> (string query, params object[] args)
 1527      where T : new()
 1528    {
 1529      return ReadAsync (conn => conn.Query<T> (query, args));
 1530    }
 1531
 1532    /// <summary>
 1533    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1534    /// in the command text for each of the arguments and then executes that command.
 1535    /// It returns the first column of each row of the result.
 1536    /// </summary>
 1537    /// <param name="query">
 1538    /// The fully escaped SQL.
 1539    /// </param>
 1540    /// <param name="args">
 1541    /// Arguments to substitute for the occurences of '?' in the query.
 1542    /// </param>
 1543    /// <returns>
 1544    /// A list with one result for the first column of each row returned by the query.
 1545    /// </returns>
 1546    public Task<List<T>> QueryScalarsAsync<T> (string query, params object[] args)
 1547    {
 1548      return ReadAsync (conn => conn.QueryScalars<T> (query, args));
 1549    }
 1550
 1551    /// <summary>
 1552    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1553    /// in the command text for each of the arguments and then executes that command.
 1554    /// It returns each row of the result using the specified mapping. This function is
 1555    /// only used by libraries in order to query the database via introspection. It is
 1556    /// normally not used.
 1557    /// </summary>
 1558    /// <param name="map">
 1559    /// A <see cref="TableMapping"/> to use to convert the resulting rows
 1560    /// into objects.
 1561    /// </param>
 1562    /// <param name="query">
 1563    /// The fully escaped SQL.
 1564    /// </param>
 1565    /// <param name="args">
 1566    /// Arguments to substitute for the occurences of '?' in the query.
 1567    /// </param>
 1568    /// <returns>
 1569    /// An enumerable with one result for each row returned by the query.
 1570    /// </returns>
 1571    public Task<List<object>> QueryAsync (TableMapping map, string query, params object[] args)
 1572    {
 1573      return ReadAsync (conn => conn.Query (map, query, args));
 1574    }
 1575
 1576    /// <summary>
 1577    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1578    /// in the command text for each of the arguments and then executes that command.
 1579    /// It returns each row of the result using the mapping automatically generated for
 1580    /// the given type.
 1581    /// </summary>
 1582    /// <param name="query">
 1583    /// The fully escaped SQL.
 1584    /// </param>
 1585    /// <param name="args">
 1586    /// Arguments to substitute for the occurences of '?' in the query.
 1587    /// </param>
 1588    /// <returns>
 1589    /// An enumerable with one result for each row returned by the query.
 1590    /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
 1591    /// connection must remain open for the lifetime of the enumerator.
 1592    /// </returns>
 1593    public Task<IEnumerable<T>> DeferredQueryAsync<
 1594#if NET8_0_OR_GREATER
 1595      [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1596#endif
 1597      T> (string query, params object[] args)
 1598      where T : new()
 1599    {
 1600      return ReadAsync (conn => (IEnumerable<T>)conn.DeferredQuery<T> (query, args).ToList ());
 1601    }
 1602
 1603    /// <summary>
 1604    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1605    /// in the command text for each of the arguments and then executes that command.
 1606    /// It returns each row of the result using the specified mapping. This function is
 1607    /// only used by libraries in order to query the database via introspection. It is
 1608    /// normally not used.
 1609    /// </summary>
 1610    /// <param name="map">
 1611    /// A <see cref="TableMapping"/> to use to convert the resulting rows
 1612    /// into objects.
 1613    /// </param>
 1614    /// <param name="query">
 1615    /// The fully escaped SQL.
 1616    /// </param>
 1617    /// <param name="args">
 1618    /// Arguments to substitute for the occurences of '?' in the query.
 1619    /// </param>
 1620    /// <returns>
 1621    /// An enumerable with one result for each row returned by the query.
 1622    /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
 1623    /// connection must remain open for the lifetime of the enumerator.
 1624    /// </returns>
 1625    public Task<IEnumerable<object>> DeferredQueryAsync (TableMapping map, string query, params object[] args)
 1626    {
 1627      return ReadAsync (conn => (IEnumerable<object>)conn.DeferredQuery (map, query, args).ToList ());
 1628    }
 1629
 1630    /// <summary>
 1631    /// Change the encryption key for a SQLCipher database with "pragma rekey = ...".
 1632    /// </summary>
 1633    /// <param name="key">Encryption key plain text that is converted to the real encryption key using PBKDF2 key deriva
 1634    public Task ReKeyAsync (string key)
 1635    {
 1636      return WriteAsync<object> (conn => {
 1637        conn.ReKey (key);
 1638        return null;
 1639      });
 1640    }
 1641
 1642    /// <summary>
 1643    /// Change the encryption key for a SQLCipher database.
 1644    /// </summary>
 1645    /// <param name="key">256-bit (32 byte) or 384-bit (48 bytes) encryption key data</param>
 1646    public Task ReKeyAsync (byte[] key)
 1647    {
 1648      return WriteAsync<object> (conn => {
 1649        conn.ReKey (key);
 1650        return null;
 1651      });
 1652    }
 1653  }
 1654
 1655  /// <summary>
 1656  /// Query to an asynchronous database connection.
 1657  /// </summary>
 1658  public class AsyncTableQuery<
 1659#if NET8_0_OR_GREATER
 1660    [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
 1661#endif
 1662  T>
 1663    where T : new()
 1664  {
 1665    TableQuery<T> _innerQuery;
 1666
 1667    /// <summary>
 1668    /// Creates a new async query that uses given the synchronous query.
 1669    /// </summary>
 10491670    public AsyncTableQuery (TableQuery<T> innerQuery)
 10491671    {
 10491672      _innerQuery = innerQuery;
 10491673    }
 1674
 1675    Task<U> ReadAsync<U> (Func<SQLiteConnectionWithLock, U> read)
 5321676    {
 10641677      return Task.Factory.StartNew (() => {
 10641678        var conn = (SQLiteConnectionWithLock)_innerQuery.Connection;
 15961679        using (conn.Lock ()) {
 10641680          return read (conn);
 5321681        }
 10621682      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 5321683    }
 1684
 1685    Task<U> WriteAsync<U> (Func<SQLiteConnectionWithLock, U> write)
 01686    {
 01687      return Task.Factory.StartNew (() => {
 01688        var conn = (SQLiteConnectionWithLock)_innerQuery.Connection;
 01689        using (conn.Lock ()) {
 01690          return write (conn);
 01691        }
 01692      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 01693    }
 1694
 1695    /// <summary>
 1696    /// Filters the query based on a predicate.
 1697    /// </summary>
 1698    public AsyncTableQuery<T> Where (Expression<Func<T, bool>> predExpr)
 5131699    {
 5131700      return new AsyncTableQuery<T> (_innerQuery.Where (predExpr));
 5131701    }
 1702
 1703    /// <summary>
 1704    /// Skips a given number of elements from the query and then yields the remainder.
 1705    /// </summary>
 1706    public AsyncTableQuery<T> Skip (int n)
 11707    {
 11708      return new AsyncTableQuery<T> (_innerQuery.Skip (n));
 11709    }
 1710
 1711    /// <summary>
 1712    /// Yields a given number of elements from the query and then skips the remainder.
 1713    /// </summary>
 1714    public AsyncTableQuery<T> Take (int n)
 11715    {
 11716      return new AsyncTableQuery<T> (_innerQuery.Take (n));
 11717    }
 1718
 1719    /// <summary>
 1720    /// Order the query results according to a key.
 1721    /// </summary>
 1722    public AsyncTableQuery<T> OrderBy<U> (Expression<Func<T, U>> orderExpr)
 41723    {
 41724      return new AsyncTableQuery<T> (_innerQuery.OrderBy<U> (orderExpr));
 41725    }
 1726
 1727    /// <summary>
 1728    /// Order the query results according to a key.
 1729    /// </summary>
 1730    public AsyncTableQuery<T> OrderByDescending<U> (Expression<Func<T, U>> orderExpr)
 11731    {
 11732      return new AsyncTableQuery<T> (_innerQuery.OrderByDescending<U> (orderExpr));
 11733    }
 1734
 1735    /// <summary>
 1736    /// Order the query results according to a key.
 1737    /// </summary>
 1738    public AsyncTableQuery<T> ThenBy<U> (Expression<Func<T, U>> orderExpr)
 01739    {
 01740      return new AsyncTableQuery<T> (_innerQuery.ThenBy<U> (orderExpr));
 01741    }
 1742
 1743    /// <summary>
 1744    /// Order the query results according to a key.
 1745    /// </summary>
 1746    public AsyncTableQuery<T> ThenByDescending<U> (Expression<Func<T, U>> orderExpr)
 01747    {
 01748      return new AsyncTableQuery<T> (_innerQuery.ThenByDescending<U> (orderExpr));
 01749    }
 1750
 1751    /// <summary>
 1752    /// Queries the database and returns the results as a List.
 1753    /// </summary>
 1754    public Task<List<T>> ToListAsync ()
 5181755    {
 10361756      return ReadAsync (conn => _innerQuery.ToList ());
 5181757    }
 1758
 1759    /// <summary>
 1760    /// Queries the database and returns the results as an array.
 1761    /// </summary>
 1762    public Task<T[]> ToArrayAsync ()
 01763    {
 01764      return ReadAsync (conn => _innerQuery.ToArray ());
 01765    }
 1766
 1767    /// <summary>
 1768    /// Execute SELECT COUNT(*) on the query
 1769    /// </summary>
 1770    public Task<int> CountAsync ()
 91771    {
 181772      return ReadAsync (conn => _innerQuery.Count ());
 91773    }
 1774
 1775    /// <summary>
 1776    /// Execute SELECT COUNT(*) on the query with an additional WHERE clause.
 1777    /// </summary>
 1778    public Task<int> CountAsync (Expression<Func<T, bool>> predExpr)
 01779    {
 01780      return ReadAsync (conn => _innerQuery.Count (predExpr));
 01781    }
 1782
 1783    /// <summary>
 1784    /// Returns the element at a given index
 1785    /// </summary>
 1786    public Task<T> ElementAtAsync (int index)
 11787    {
 21788      return ReadAsync (conn => _innerQuery.ElementAt (index));
 11789    }
 1790
 1791    /// <summary>
 1792    /// Returns the first element of this query.
 1793    /// </summary>
 1794    public Task<T> FirstAsync ()
 21795    {
 41796      return ReadAsync (conn => _innerQuery.First ());
 21797    }
 1798
 1799    /// <summary>
 1800    /// Returns the first element of this query, or null if no element is found.
 1801    /// </summary>
 1802    public Task<T> FirstOrDefaultAsync ()
 21803    {
 41804      return ReadAsync (conn => _innerQuery.FirstOrDefault ());
 21805    }
 1806
 1807    /// <summary>
 1808    /// Returns the first element of this query that matches the predicate.
 1809    /// </summary>
 1810    public Task<T> FirstAsync (Expression<Func<T, bool>> predExpr)
 01811    {
 01812      return ReadAsync (conn => _innerQuery.First (predExpr));
 01813    }
 1814
 1815    /// <summary>
 1816    /// Returns the first element of this query that matches the predicate.
 1817    /// </summary>
 1818    public Task<T> FirstOrDefaultAsync (Expression<Func<T, bool>> predExpr)
 01819    {
 01820      return ReadAsync (conn => _innerQuery.FirstOrDefault (predExpr));
 01821    }
 1822
 1823    /// <summary>
 1824    /// Delete all the rows that match this query and the given predicate.
 1825    /// </summary>
 1826    public Task<int> DeleteAsync (Expression<Func<T, bool>> predExpr)
 01827    {
 01828      return WriteAsync (conn => _innerQuery.Delete (predExpr));
 01829    }
 1830
 1831    /// <summary>
 1832    /// Delete all the rows that match this query.
 1833    /// </summary>
 1834    public Task<int> DeleteAsync ()
 01835    {
 01836      return WriteAsync (conn => _innerQuery.Delete ());
 01837    }
 1838  }
 1839
 1840  class SQLiteConnectionPool
 1841  {
 1842    class Entry
 1843    {
 1844      public SQLiteConnectionWithLock Connection { get; private set; }
 1845
 1846      public SQLiteConnectionString ConnectionString { get; }
 1847
 1848      public object TransactionLock { get; } = new object ();
 1849
 1850      public Entry (SQLiteConnectionString connectionString)
 1851      {
 1852        ConnectionString = connectionString;
 1853        Connection = new SQLiteConnectionWithLock (ConnectionString);
 1854
 1855        // If the database is FullMutex, then we don't need to bother locking
 1856        if (ConnectionString.OpenFlags.HasFlag (SQLiteOpenFlags.FullMutex)) {
 1857          Connection.SkipLock = true;
 1858        }
 1859      }
 1860
 1861      public void Close ()
 1862      {
 1863        var wc = Connection;
 1864        Connection = null;
 1865        if (wc != null) {
 1866          wc.Close ();
 1867        }
 1868      }
 1869    }
 1870
 1871    readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry> ();
 1872    readonly object _entriesLock = new object ();
 1873
 1874    static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool ();
 1875
 1876    /// <summary>
 1877    /// Gets the singleton instance of the connection tool.
 1878    /// </summary>
 1879    public static SQLiteConnectionPool Shared {
 1880      get {
 1881        return _shared;
 1882      }
 1883    }
 1884
 1885    public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString)
 1886    {
 1887      return GetConnectionAndTransactionLock (connectionString, out var _);
 1888    }
 1889
 1890    public SQLiteConnectionWithLock GetConnectionAndTransactionLock (SQLiteConnectionString connectionString, out object
 1891    {
 1892      var key = connectionString.UniqueKey;
 1893      Entry entry;
 1894      lock (_entriesLock) {
 1895        if (!_entries.TryGetValue (key, out entry)) {
 1896          // The opens the database while we're locked
 1897          // This is to ensure another thread doesn't get an unopened database
 1898          entry = new Entry (connectionString);
 1899          _entries[key] = entry;
 1900        }
 1901        transactionLock = entry.TransactionLock;
 1902        return entry.Connection;
 1903      }
 1904    }
 1905
 1906    public void CloseConnection (SQLiteConnectionString connectionString)
 1907    {
 1908      var key = connectionString.UniqueKey;
 1909      Entry entry;
 1910      lock (_entriesLock) {
 1911        if (_entries.TryGetValue (key, out entry)) {
 1912          _entries.Remove (key);
 1913        }
 1914      }
 1915      entry?.Close ();
 1916    }
 1917
 1918    /// <summary>
 1919    /// Closes all connections managed by this pool.
 1920    /// </summary>
 1921    public void Reset ()
 1922    {
 1923      List<Entry> entries;
 1924      lock (_entriesLock) {
 1925        entries = new List<Entry> (_entries.Values);
 1926        _entries.Clear ();
 1927      }
 1928
 1929      foreach (var e in entries) {
 1930        e.Close ();
 1931      }
 1932    }
 1933  }
 1934
 1935  /// <summary>
 1936  /// This is a normal connection except it contains a Lock method that
 1937  /// can be used to serialize access to the database
 1938  /// in lieu of using the sqlite's FullMutex support.
 1939  /// </summary>
 1940  public class SQLiteConnectionWithLock : SQLiteConnection
 1941  {
 1942    readonly object _lockPoint = new object ();
 1943
 1944    /// <summary>
 1945    /// Initializes a new instance of the <see cref="T:SQLite.SQLiteConnectionWithLock"/> class.
 1946    /// </summary>
 1947    /// <param name="connectionString">Connection string containing the DatabasePath.</param>
 1948    public SQLiteConnectionWithLock (SQLiteConnectionString connectionString)
 1949      : base (connectionString)
 1950    {
 1951    }
 1952
 1953    /// <summary>
 1954    /// Gets or sets a value indicating whether this <see cref="T:SQLite.SQLiteConnectionWithLock"/> skip lock.
 1955    /// </summary>
 1956    /// <value><c>true</c> if skip lock; otherwise, <c>false</c>.</value>
 1957    public bool SkipLock { get; set; }
 1958
 1959    /// <summary>
 1960    /// Lock the database to serialize access to it. To unlock it, call Dispose
 1961    /// on the returned object.
 1962    /// </summary>
 1963    /// <returns>The lock.</returns>
 1964    public IDisposable Lock ()
 1965    {
 1966      return SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper (_lockPoint);
 1967    }
 1968
 1969    class LockWrapper : IDisposable
 1970    {
 1971      object _lockPoint;
 1972
 1973      public LockWrapper (object lockPoint)
 1974      {
 1975        _lockPoint = lockPoint;
 1976        Monitor.Enter (_lockPoint);
 1977      }
 1978
 1979      public void Dispose ()
 1980      {
 1981        Monitor.Exit (_lockPoint);
 1982      }
 1983    }
 1984    class FakeLockWrapper : IDisposable
 1985    {
 1986      public void Dispose ()
 1987      {
 1988      }
 1989    }
 1990  }
 1991}
 1992