using System.Data; using System.Data.Common; using System.Linq.Expressions; using System.Text; using LinqToDB; using LinqToDB.Data; using LinqToDB.DataProvider; using LinqToDB.DataProvider.SQLite; using LinqToDB.Tools; using Microsoft.Data.Sqlite; using Nop.Core; using Nop.Core.ComponentModel; using Nop.Data; using Nop.Data.DataProviders; namespace Nop.Tests; /// /// Represents the SQLite data provider /// public partial class SqLiteNopDataProvider : BaseDataProvider, INopDataProvider { #region Consts //it's quite fast hash (to cheaply distinguish between objects) private const string HASH_ALGORITHM = "SHA1"; private static DataConnection _dataContext; private static readonly ReaderWriterLockSlim _locker = new(); #endregion #region Methods public void CreateDatabase(string collation, int triesToConnect = 10) { ExecuteNonQueryAsync("PRAGMA journal_mode=WAL;").Wait(); } /// /// Gets a connection to the database for a current data provider /// /// Connection string /// Connection to a database protected override DbConnection GetInternalDbConnection(string connectionString) { ArgumentException.ThrowIfNullOrEmpty(connectionString); return new SqliteConnection(string.IsNullOrEmpty(connectionString) ? DataSettingsManager.LoadSettings().ConnectionString : connectionString); } /// /// Inserts record into table. Returns inserted entity with identity /// /// /// /// Inserted entity public override TEntity InsertEntity(TEntity entity) { using (new ReaderWriteLockDisposable(_locker)) { entity.Id = DataContext.InsertWithInt32Identity(entity); return entity; } } /// /// Insert a new entity /// /// Entity type /// Entity /// Entity public override Task InsertEntityAsync(TEntity entity) { InsertEntity(entity); return Task.FromResult(entity); } /// /// Updates record in table, using values from entity parameter. /// Record to update identified by match on primary key value from obj value. /// /// Entity with data to update /// Entity type public override Task UpdateEntityAsync(TEntity entity) { using (new ReaderWriteLockDisposable(_locker)) DataContext.Update(entity); return Task.CompletedTask; } /// /// Updates records in table, using values from entity parameter. /// Records to update are identified by match on primary key value from obj value. /// /// Entities with data to update /// Entity type /// A task that represents the asynchronous operation public override async Task UpdateEntitiesAsync(IEnumerable entities) { foreach (var entity in entities) await UpdateEntityAsync(entity); } /// /// Updates records in table, using values from entity parameter. /// Records to update are identified by match on primary key value from obj value. /// /// Entities with data to update /// Entity type public override void UpdateEntities(IEnumerable entities) { foreach (var entity in entities) UpdateEntity(entity); } /// /// Deletes record in table. Record to delete identified /// by match on primary key value from obj value. /// /// Entity for delete operation /// Entity type public override Task DeleteEntityAsync(TEntity entity) { using (new ReaderWriteLockDisposable(_locker)) DataContext.Delete(entity); return Task.CompletedTask; } /// /// Performs delete records in a table /// /// Entities for delete operation /// Entity type public override Task BulkDeleteEntitiesAsync(IList entities) { using (new ReaderWriteLockDisposable(_locker)) { foreach (var entity in entities) DataContext.Delete(entity); } return Task.CompletedTask; } /// /// Performs delete records in a table by a condition /// /// A function to test each element for a condition. /// Entity type public override Task BulkDeleteEntitiesAsync(Expression> predicate) { return Task.FromResult(DataContext.GetTable() .Where(predicate).Delete()); } /// /// Performs bulk insert operation for entity collection. /// /// Entities for insert operation /// Entity type public override Task BulkInsertEntitiesAsync(IEnumerable entities) { using (new ReaderWriteLockDisposable(_locker)) DataContext.BulkCopy(new BulkCopyOptions(), entities.RetrieveIdentity(DataContext)); return Task.CompletedTask; } /// /// Gets the name of a foreign key /// /// Foreign key table /// Foreign key column name /// Primary table /// Primary key column name /// Name of a foreign key public string CreateForeignKeyName(string foreignTable, string foreignColumn, string primaryTable, string primaryColumn) { return "FK_" + HashHelper.CreateHash(Encoding.UTF8.GetBytes($"{foreignTable}_{foreignColumn}_{primaryTable}_{primaryColumn}"), HASH_ALGORITHM); } /// /// Gets the name of an index /// /// Target table name /// Target column name /// Name of an index public string GetIndexName(string targetTable, string targetColumn) { return "IX_" + HashHelper.CreateHash(Encoding.UTF8.GetBytes($"{targetTable}_{targetColumn}"), HASH_ALGORITHM); } /// /// Returns queryable source for specified mapping class for current connection, /// mapped to database table or view. /// /// Entity type /// Queryable source public override IQueryable GetTable() { using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read)) return DataContext.GetTable(); } /// /// Get the current identity value /// /// Entity /// Integer identity; null if cannot get the result public Task GetTableIdentAsync() where TEntity : BaseEntity { using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read)) { var tableName = DataContext.GetTable().TableName; var result = DataContext.Query($"select seq from sqlite_sequence where name = \"{tableName}\"") .FirstOrDefault(); return Task.FromResult(result ?? 1); } } /// /// Checks if the specified database exists, returns true if database exists /// /// Returns true if the database exists. public bool DatabaseExists() { return true; } /// /// Creates a backup of the database /// public virtual Task BackupDatabaseAsync(string fileName) { throw new DataException("This database provider does not support backup"); } /// /// Restores the database from a backup /// /// The name of the backup file public virtual Task RestoreDatabaseAsync(string backupFileName) { throw new DataException("This database provider does not support backup"); } /// /// Re-index database tables /// public Task ReIndexTablesAsync() { using (new ReaderWriteLockDisposable(_locker)) DataContext.Execute("VACUUM;"); return Task.CompletedTask; } /// /// Build the connection string /// /// Connection string info /// Connection string public string BuildConnectionString(INopConnectionStringInfo nopConnectionString) { ArgumentNullException.ThrowIfNull(nopConnectionString); if (nopConnectionString.IntegratedSecurity) throw new NopException("Data provider supports connection only with password"); var builder = new SqliteConnectionStringBuilder { DataSource = CommonHelper.DefaultFileProvider.MapPath($"~/App_Data/{nopConnectionString.DatabaseName}.sqlite"), Password = nopConnectionString.Password, Mode = SqliteOpenMode.ReadWrite, Cache = SqliteCacheMode.Shared }; return builder.ConnectionString; } /// /// Set table identity (is supported) /// /// Entity /// Identity value public Task SetTableIdentAsync(int ident) where TEntity : BaseEntity { using (new ReaderWriteLockDisposable(_locker)) { var tableName = DataContext.GetTable().TableName; DataContext.Execute($"update sqlite_sequence set seq = {ident} where name = \"{tableName}\""); } return Task.CompletedTask; } /// /// Executes command using System.Data.CommandType.StoredProcedure command type and /// returns results as collection of values of specified type /// /// Result record type /// Procedure name /// Command parameters /// Returns collection of query result records public override Task> QueryProcAsync(string procedureName, params DataParameter[] parameters) { //stored procedure is not support by SqLite return Task.FromResult>(new List()); } /// /// Executes SQL command and returns results as collection of values of specified type /// /// Type of result items /// SQL command text /// Parameters to execute the SQL command /// Collection of values of specified type public override Task> QueryAsync(string sql, params DataParameter[] parameters) { using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read)) return Task.FromResult>(DataContext.Query(sql, parameters).ToList()); } /// /// Executes command asynchronously and returns number of affected records /// /// Command text /// Command parameters /// Number of records, affected by command execution. public override Task ExecuteNonQueryAsync(string sql, params DataParameter[] dataParameters) { using (new ReaderWriteLockDisposable(_locker, ReaderWriteLockType.Read)) { using var dataConnection = CreateDataConnection(LinqToDbDataProvider); var command = new CommandInfo(dataConnection, sql, dataParameters); return command.ExecuteAsync(); } } /// /// Creates a new temporary storage and populate it using data from provided query /// /// Name of temporary storage /// Query to get records to populate created storage with initial data /// Storage record mapping class /// /// A task that represents the asynchronous operation /// The task result contains the iQueryable instance of temporary storage /// public override Task> CreateTempDataStorageAsync(string storeKey, IQueryable query) { return Task.FromResult>(new TempSqlDataStorage(storeKey, query, DataContext)); } public Task DatabaseExistsAsync() { return Task.FromResult(DatabaseExists()); } /// /// Truncates database table /// /// Performs reset identity column /// Entity type public override Task TruncateAsync(bool resetIdentity = false) { using (new ReaderWriteLockDisposable(_locker)) DataContext.GetTable().Truncate(resetIdentity); return Task.CompletedTask; } #endregion #region Properties protected DataConnection DataContext => _dataContext ??= CreateDataConnection(); /// /// Linq2Db data provider /// protected override IDataProvider LinqToDbDataProvider { get; } = SQLiteTools.GetDataProvider(ProviderName.SQLiteMS); /// /// Gets allowed a limit input value of the data for hashing functions, returns 0 if not limited /// public int SupportedLengthOfBinaryHash { get; } = 0; /// /// Gets a value indicating whether this data provider supports backup /// public bool BackupSupported { get; } = false; #endregion }