From 77588da1d4eb54936dd83b0fd2721a6d4e833624 Mon Sep 17 00:00:00 2001 From: "Eric Sibly [chullybun]" Date: Sat, 9 Jul 2022 10:22:13 -0700 Subject: [PATCH] =?UTF-8?q?Remove=20generic=20database=20functionality=20a?= =?UTF-8?q?s=20this=20has=20been=20ported=20to=20Core=E2=80=A6=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove generic database functionality as this has been ported to CoreEx.Database. DbEx is now focused on tooling onlt. * Update version and change log. --- CHANGELOG.md | 3 + README.md | 38 +-- src/DbEx/DatabaseCommand.cs | 292 ------------------ .../{Database.cs => DatabaseExtensions.cs} | 128 ++++---- src/DbEx/DatabaseParameterCollection.cs | 61 ---- src/DbEx/DatabaseRecord.cs | 62 ---- src/DbEx/DatabaseRecordMapper.cs | 27 -- src/DbEx/DatabaseRecordMapperT.cs | 24 -- src/DbEx/DbEx.csproj | 6 +- src/DbEx/IDatabase.cs | 52 ---- src/DbEx/IDatabaseMapper.cs | 20 -- src/DbEx/IMultiSetArgs.cs | 36 --- src/DbEx/IMultiSetArgsT.cs | 19 -- src/DbEx/Migration/DatabaseMigratorBase.cs | 1 + .../Migration/SqlServer/SqlServerMigrator.cs | 6 +- src/DbEx/MultiSetCollArgs.cs | 54 ---- src/DbEx/MultiSetCollArgsT.cs | 65 ---- src/DbEx/MultiSetSingleArgs.cs | 52 ---- src/DbEx/MultiSetSingleArgsT.cs | 50 --- src/DbEx/SqlServer/EventOutboxDequeueBase.cs | 156 ---------- src/DbEx/SqlServer/EventOutboxEnqueueBase.cs | 169 ---------- src/DbEx/SqlServer/SqlServerExtensions.cs | 50 --- src/DbEx/SqlServer/TableValuedParameter.cs | 49 --- .../SqlServer/EventOutboxDequeue_cs.hbs | 4 +- .../SqlServer/EventOutboxEnqueue_cs.hbs | 4 +- .../Generated/EventOutboxDequeue.cs | 4 +- .../Generated/EventOutboxEnqueue.cs | 4 +- tests/DbEx.Test.OutboxConsole/Program.cs | 2 +- tests/DbEx.Test/DatabaseSchemaTest.cs | 5 +- tests/DbEx.Test/DbEx.Test.csproj | 7 +- tests/DbEx.Test/SqlServerMigratorTest.cs | 12 +- tests/DbEx.Test/SqlServerOutboxTest.cs | 26 +- 32 files changed, 106 insertions(+), 1382 deletions(-) delete mode 100644 src/DbEx/DatabaseCommand.cs rename src/DbEx/{Database.cs => DatabaseExtensions.cs} (64%) delete mode 100644 src/DbEx/DatabaseParameterCollection.cs delete mode 100644 src/DbEx/DatabaseRecord.cs delete mode 100644 src/DbEx/DatabaseRecordMapper.cs delete mode 100644 src/DbEx/DatabaseRecordMapperT.cs delete mode 100644 src/DbEx/IDatabase.cs delete mode 100644 src/DbEx/IDatabaseMapper.cs delete mode 100644 src/DbEx/IMultiSetArgs.cs delete mode 100644 src/DbEx/IMultiSetArgsT.cs delete mode 100644 src/DbEx/MultiSetCollArgs.cs delete mode 100644 src/DbEx/MultiSetCollArgsT.cs delete mode 100644 src/DbEx/MultiSetSingleArgs.cs delete mode 100644 src/DbEx/MultiSetSingleArgsT.cs delete mode 100644 src/DbEx/SqlServer/EventOutboxDequeueBase.cs delete mode 100644 src/DbEx/SqlServer/EventOutboxEnqueueBase.cs delete mode 100644 src/DbEx/SqlServer/SqlServerExtensions.cs delete mode 100644 src/DbEx/SqlServer/TableValuedParameter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 39493c9..f36a363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Represents the **NuGet** versions. +## v1.0.10 +- *Enhancement:* Removed generic database functionality as this has been ported to `CoreEx.Database`. `DbEx` is now focused on tooling only. + ## v1.0.9 - *Enhancement:* Updated the `EventOoutboxEnqueueBase` to handle new `EventSendException` and enqueue each individual message as either sent or unsent within the outbox. - *Fixed:* Updated to `CoreEx` version `1.0.5`. diff --git a/README.md b/README.md index cf144b4..92927ce 100644 --- a/README.md +++ b/README.md @@ -267,39 +267,11 @@ dotnet run execute ./schema/createscehma.sql
-## ADO.NET - -To simplify generic access to an [ADO.NET](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/) enabled database the following are provided. - -Class | Description --|- -[`Database`](./src/DbEx/Database.cs) | Enables database provider agnostic access to invoke a `StoredProcedure` or `SqlStatement`. -[`DatabaseParameterCollection`](./src/DbEx/DatabaseParameterCollection.cs) | Encapsulates the underlying [`DbParameterCollection`](https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbparametercollection) to simplify. -[`DatabaseCommand`](./src/DbEx/DatabaseCommand.cs) | Encapsulates the underlying [`DbCommand`](https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbcommand) to simplify. -[`IMultiSetArgs`](./src/DbEx/IMultiSetArgs.cs) | Provides a simplified means to manage queries which return one or more result sets using a [`DbDataReader`](https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbdatareader). See also [`MultiSetSingleArgs`](./src/DbEx/MultiSetSingleArgsT.cs) and [`MultiSetCollArgs`](./src/DbEx/MultiSetCollArgsT.cs) -[`DatabaseRecord`](./src/DbEx/DatabaseRecord.cs) | Encapsulates the underlying [`DbDataReader`](https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbdatareader) to simplify column value access for a given record. -[`IDatabaseMapper`](./src/DbEx/IDatabaseMapper.cs) | Provides a standardized approach for mapping between a [`DatabaseRecord`](./src/DbEx/DatabaseRecord.cs) and a corresponding .NET object. - -The [`DatabaseCommand`](./src/DbEx/DatabaseCommand.cs) provides the following key methods to access data. - -Method | Description --|- -`NonQueryAsync` | Executes a non-query command. -`ScalarAsync` | Executes the query and returns the first column of the first row in the result set returned by the query. -`SelectAsync` | Selects none or more items from the first result set. -`SelectMultiSetAsync` | Executes a multi-dataset query command with one or more [`IMultiSetArgs`](./src/DbEx/IMultiSetArgs.cs). -`SelectSingleAsync` | Selects a single item. -`SelectSingleOrDefaultAsync` | Selects a single item or default. -`SelectFirstAsync` | Selects first item. -`SelectFirstOrDefaultAsync` | Selects first item or default. - -
- -### Infer database schema +## Infer database schema Within a code-generation, or other context, the database schema may need to be inferred to understand the basic schema for all tables and their corresponding columns. -The [`Database`](./src/DbEx/Database.cs) class provides a `SelectSchemaAsync` method to return a [`DbTableSchema`](./src/DbEx/Schema/DbTableSchema.cs) list, including the respective columns for each table (see [`DbColumnSchema`](./src/DbEx/Schema/DbColumnSchema.cs)). +The [`Database`](./src/DbEx/DatabaseExtensions.cs) class provides a `SelectSchemaAsync` method to return a [`DbTableSchema`](./src/DbEx/Schema/DbTableSchema.cs) list, including the respective columns for each table (see [`DbColumnSchema`](./src/DbEx/Schema/DbColumnSchema.cs)).
@@ -317,14 +289,14 @@ Template | Description | Example [`UdtEventOutbox_sql.hbs`](./src/DbEx/Templates/SqlServer/UdtEventOutbox_sql.hbs) | Event outbox user-defined table type. | See [example](./tests/DbEx.Test.OutboxConsole/Schema/Outbox/Types/User-Defined%20Table%20Types/Generated/udtEventOutboxList.sql). [`SpEventOutboxEnqueue_sql.hbs`](./src/DbEx/Templates/SqlServer/SpEventOutboxEnqueue_sql.hbs) | Event outbox enqueue stored procedure. | See [example](./tests/DbEx.Test.OutboxConsole/Schema/Outbox/Stored%20Procedures/Generated/spEventOutboxEnqueue.sql). [`SpEventOutboxDequeue_sql.hbs`](./src/DbEx/Templates/SqlServer/SpEventOutboxDequeue_sql.hbs) | Event outbox dequeue stored procedure. | See [example](./tests/DbEx.Test.OutboxConsole/Schema/Outbox/Stored%20Procedures/Generated/spEventOutboxDequeue.sql). -[`EventOutboxEnqueue_cs.hbs`](./src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs) | Event outbox enqueue (.NET C#); inherits capabilities from [`EventOutboxEnqueueBase`](./src/DbEx/SqlServer/EventOutboxEnqueueBase.cs). | See [example](./tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs). -[`EventOutboxEnqueue_cs.hbs`](./src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs) | Event outbox dequeue (.NET C#); inherits capabilities from [`EventOutboxDequeueBase`](./src/DbEx/SqlServer/EventOutboxDequeueBase.cs). | See [example](./tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs). +[`EventOutboxEnqueue_cs.hbs`](./src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs) | Event outbox enqueue (.NET C#); inherits capabilities from [`EventOutboxEnqueueBase`](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Database/SqlServer/EventOutboxEnqueueBase.cs). | See [example](./tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs). +[`EventOutboxEnqueue_cs.hbs`](./src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs) | Event outbox dequeue (.NET C#); inherits capabilities from [`EventOutboxDequeueBase`](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Database/SqlServer/EventOutboxDequeueBase.cs). | See [example](./tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs).
### Event Outbox Enqueue -The [`EventOutboxDequeueBase`](./src/DbEx/SqlServer/EventOutboxEnqueueBase.cs) provides the base [`IEventSender`](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx/Events/IEventSender.cs) send/enqueue capabilities. +The [`EventOutboxDequeueBase`](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx.Database/SqlServer/EventOutboxEnqueueBase.cs) provides the base [`IEventSender`](https://github.com/Avanade/CoreEx/blob/main/src/CoreEx/Events/IEventSender.cs) send/enqueue capabilities. By default the events are first sent/enqueued to the datatbase outbox, then a secondary out-of-process dequeues and sends. This can however introduce unwanted latency depending on the frequency in which the secondary process performs the dequeue and send, as this is essentially a polling-based operation. To improve (minimize) latency, the primary `IEventSender` can be specified using the `SetPrimaryEventSender` method. This will then be used to send the events immediately, and where successful, they will be audited in the database as dequeued event(s); versus on error (as a backup), where they will be enqueued for the out-of-process dequeue and send (as per default). diff --git a/src/DbEx/DatabaseCommand.cs b/src/DbEx/DatabaseCommand.cs deleted file mode 100644 index 25cde74..0000000 --- a/src/DbEx/DatabaseCommand.cs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Threading.Tasks; - -namespace DbEx -{ - /// - /// Provides extended database command capabilities. - /// - public sealed class DatabaseCommand - { - private readonly Action? _parameters; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The command text. - /// An optional delegate to update the for the command. - public DatabaseCommand(IDatabase db, CommandType commandType, string commandText, Action? parameters = null) - { - Database = db ?? throw new ArgumentNullException(nameof(db)); - CommandType = commandType; - CommandText = commandText ?? throw new ArgumentNullException(nameof(commandText)); - _parameters = parameters; - } - - /// - /// Gets the underlying . - /// - public IDatabase Database { get; } - - /// - /// Gets the . - /// - public CommandType CommandType { get; } - - /// - /// Gets the command text. - /// - public string CommandText { get; } - - /// - /// Creates the corresponding . - /// - /// - private async Task CreateDbCommandAsync() - { - var cmd = (await Database.GetConnectionAsync().ConfigureAwait(false)).CreateCommand(); - cmd.CommandType = CommandType; - cmd.CommandText = CommandText; - - // Action the parameters. - _parameters?.Invoke(new DatabaseParameterCollection(cmd)); - - return cmd; - } - - /// - /// Executes a multi-dataset query command with one or more . - /// - /// One or more . - /// The number of specified must match the number of returned datasets. A null dataset indicates to ignore (skip) a dataset. - public async Task SelectMultiSetAsync(params IMultiSetArgs[] multiSetArgs) - { - if (multiSetArgs == null || multiSetArgs.Length == 0) - throw new ArgumentException($"At least one {nameof(IMultiSetArgs)} must be supplied.", nameof(multiSetArgs)); - - try - { - // Create and execute the command. - using var cmd = await CreateDbCommandAsync().ConfigureAwait(false); - using var dr = await cmd.ExecuteReaderAsync().ConfigureAwait(false); - - // Iterate through the dataset(s). - var index = 0; - var records = 0; - IMultiSetArgs? multiSetArg = null; - do - { - if (index >= multiSetArgs.Length) - throw new InvalidOperationException($"{nameof(SelectMultiSetAsync)} has returned more record sets than expected ({multiSetArgs.Length})."); - - if (multiSetArgs[index] != null) - { - records = 0; - multiSetArg = multiSetArgs[index]; - while (await dr.ReadAsync().ConfigureAwait(false)) - { - records++; - if (multiSetArg.MaxRows.HasValue && records > multiSetArg.MaxRows.Value) - throw new InvalidOperationException($"{nameof(SelectMultiSetAsync)} (multiSetArgs[{index}]) has returned more records than expected ({multiSetArg.MaxRows.Value})."); - - multiSetArg.DatasetRecord(new DatabaseRecord(dr)); - } - - if (records < multiSetArg.MinRows) - throw new InvalidOperationException($"{nameof(SelectMultiSetAsync)} (multiSetArgs[{index}]) has returned less records ({records}) than expected ({multiSetArg.MinRows})."); - - if (records == 0 && multiSetArg.StopOnNull) - return; - - multiSetArg.InvokeResult(); - } - - index++; - } while (dr.NextResult()); - - if (index < multiSetArgs.Length && !multiSetArgs[index].StopOnNull) - throw new InvalidOperationException($"{nameof(SelectMultiSetAsync)} has returned less ({index}) record sets than expected ({multiSetArgs.Length})."); - } - catch (DbException dbex) - { - ThrowIfError(dbex); - throw; - } - } - - /// - /// Executes a non-query command. - /// - /// The post-execution delegate to enable parameter access. - /// The number of rows affected. - public async Task NonQueryAsync(Action? parameters = null) => await ExecuteWrapper(async () => - { - using var cmd = await CreateDbCommandAsync().ConfigureAwait(false); - var result = await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); - parameters?.Invoke(cmd.Parameters); - return result; - }); - - /// - /// Executes the query and returns the first column of the first row in the result set returned by the query. - /// - /// The result . - /// The post-execution delegate to enable parameter access. - /// The value of the first column of the first row in the result set. - public async Task ScalarAsync(Action? parameters = null) => await ExecuteWrapper(async () => - { - using var cmd = await CreateDbCommandAsync().ConfigureAwait(false); - var result = await cmd.ExecuteScalarAsync().ConfigureAwait(false); - T value = result is null ? default! : result is DBNull ? default! : (T)result; - parameters?.Invoke(cmd.Parameters); - return value; - }); - - /// - /// Selects none or more items from the first result set using a . - /// - /// The item . - /// The . - /// The item sequence. - public async Task> SelectAsync(IDatabaseMapper mapper) => (await SelectInternal(mapper, false, false).ConfigureAwait(false)) ?? new List(); - - /// - /// Selects none or more items from the first result set. - /// - /// The item . - /// The mapping function. - /// - public async Task> SelectAsync(Func func) - { - var list = new List(); - await SelectInternal(new DatabaseRecordMapper(dr => - { - list.Add(func(dr)); - }), false, false).ConfigureAwait(false); - return list; - } - - /// - /// Selects a single item. - /// - /// The resultant . - /// The . - /// The single item. - public async Task SelectSingleAsync(IDatabaseMapper mapper) - { - T item = await SelectSingleFirstAsync(mapper, true).ConfigureAwait(false); - if (Comparer.Default.Compare(item, default!) == 0) - throw new InvalidOperationException("SelectSingle request has not returned a row."); - - return item; - } - - /// - /// Selects a single item or default. - /// - /// The resultant . - /// The . - /// The single item or default. - public async Task SelectSingleOrDefaultAsync(IDatabaseMapper mapper) => await SelectSingleFirstAsync(mapper, false).ConfigureAwait(false); - - /// - /// Selects first item. - /// - /// The resultant . - /// The . - /// The single item. - public async Task SelectFirstAsync(IDatabaseMapper mapper) - { - T item = await SelectSingleFirstAsync(mapper, false).ConfigureAwait(false); - if (Comparer.Default.Compare(item, default!) == 0) - throw new InvalidOperationException("SelectFirst request has not returned a row."); - - return item; - } - - /// - /// Selects first item or default. - /// - /// The resultant . - /// The . - /// The single item or default. - public async Task SelectFirstOrDefaultAsync(IDatabaseMapper mapper) => await SelectSingleFirstAsync(mapper, false).ConfigureAwait(false); - - /// - /// Select first row result only (where exists). - /// - private async Task SelectSingleFirstAsync(IDatabaseMapper mapper, bool throwWhereMulti) - { - var list = await SelectInternal(mapper, throwWhereMulti, true).ConfigureAwait(false); - return list == null ? default! : list[0]; - } - - /// - /// Wrap the execution and manage any . - /// - private async Task ExecuteWrapper(Func> func) - { - try - { - return await func().ConfigureAwait(false); - } - catch (DbException dbex) - { - ThrowIfError(dbex); - throw; - } - } - - /// - /// Check the and get underlying error and throw if a known DbEx database error. - /// - private void ThrowIfError(DbException dbex) => Database.OnDbException(dbex); - - /// - /// Select the rows from the query. - /// - private async Task?> SelectInternal(IDatabaseMapper mapper, bool throwWhereMulti, bool stopAfterOneRow) - { - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - return await ExecuteWrapper(async () => - { - List? list = default; - int i = 0; - - using var cmd = await CreateDbCommandAsync().ConfigureAwait(false); - using var dr = await cmd.ExecuteReaderAsync().ConfigureAwait(false); - - while (await dr.ReadAsync().ConfigureAwait(false)) - { - if (i++ > 0) - { - if (throwWhereMulti) - throw new InvalidOperationException("SelectSingle request has returned more than one row."); - - if (stopAfterOneRow) - return list; - } - - if (list == null) - list = new List(); - - list.Add(mapper.MapFromDb(new DatabaseRecord(dr))); - if (!throwWhereMulti && stopAfterOneRow) - return list; - } - - return list; - - }).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/DbEx/Database.cs b/src/DbEx/DatabaseExtensions.cs similarity index 64% rename from src/DbEx/Database.cs rename to src/DbEx/DatabaseExtensions.cs index 566e940..b86e49c 100644 --- a/src/DbEx/Database.cs +++ b/src/DbEx/DatabaseExtensions.cs @@ -1,11 +1,12 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx +using CoreEx.Database; +using CoreEx.Entities; +using CoreEx.Mapping; using DbEx.Schema; using OnRamp.Utility; using System; using System.Collections.Generic; -using System.Data; -using System.Data.Common; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -13,63 +14,29 @@ namespace DbEx { /// - /// Provides the common/base database access functionality. + /// extensions. /// - /// The . - public class Database : IDatabase, IDisposable where TConnection : DbConnection + public static class DatabaseExtensions { - private readonly Func _dbConnCreate; - private TConnection? _dbConn; - /// - /// Initializes a new instance of the class. + /// Selects all the table and column schema details from the database. /// - /// The function to create the . - public Database(Func create) => _dbConnCreate = create ?? throw new ArgumentNullException(nameof(create)); - - /// - /// Gets the . - /// - /// The connection is created and opened on first use, and closed on . - public async Task GetConnectionAsync() - { - if (_dbConn == null) - { - _dbConn = _dbConnCreate() ?? throw new InvalidOperationException($"The create function must create a valid {nameof(TConnection)} instance."); - await _dbConn.OpenAsync().ConfigureAwait(false); - } - - return _dbConn; - } - - /// - async Task IDatabase.GetConnectionAsync() => await GetConnectionAsync().ConfigureAwait(false); - - /// - public DatabaseCommand StoredProcedure(string storedProcedure, Action? parameters = null) - => new(this, CommandType.StoredProcedure, storedProcedure ?? throw new ArgumentNullException(nameof(storedProcedure)), parameters); - - /// - public DatabaseCommand SqlStatement(string sqlStatement, Action? parameters = null) - => new(this, CommandType.Text, sqlStatement ?? throw new ArgumentNullException(nameof(sqlStatement)), parameters); - - /// - public virtual void OnDbException(DbException dbex) { } - - /// + /// The . + /// The reference data predicate used to determine whether a is considered a reference data table (sets ). + /// A list of all the table and column schema details. /// The where not specified will default to checking whether the has any non-primary key -based columns named 'Code' and 'Text'. - public virtual async Task> SelectSchemaAsync(Func? refDataPredicate = null) + public static async Task> SelectSchemaAsync(this IDatabase database, Func? refDataPredicate = null) { var tables = new List(); DbTableSchema? table = null; // Get all the tables and their columns. - using var sr = StreamLocator.GetResourcesStreamReader("SelectTableAndColumns.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - await SqlStatement(await sr.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(new DatabaseRecordMapper(dr => + using var sr = StreamLocator.GetResourcesStreamReader("SelectTableAndColumns.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + await database.SqlStatement(await sr.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => { var dt = new DbTableSchema(dr.GetValue("TABLE_SCHEMA"), dr.GetValue("TABLE_NAME")) - { - IsAView = dr.GetValue("TABLE_TYPE") == "VIEW" + { + IsAView = dr.GetValue("TABLE_TYPE") == "VIEW" }; if (table == null || table.Schema != dt.Schema || table.Name != dt.Name) @@ -85,7 +52,8 @@ await SqlStatement(await sr.ReadToEndAsync().ConfigureAwait(false)).SelectAsync( }; table.Columns.Add(dc); - })).ConfigureAwait(false); + return 0; + }).ConfigureAwait(false); // Exit where no tables initially found. if (tables.Count == 0) @@ -99,8 +67,8 @@ await SqlStatement(await sr.ReadToEndAsync().ConfigureAwait(false)).SelectAsync( } // Configure all the single column primary and unique constraints. - using var sr2 = StreamLocator.GetResourcesStreamReader("SelectTablePrimaryKey.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - var pks = await SqlStatement(await sr2.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(dr => new + using var sr2 = StreamLocator.GetResourcesStreamReader("SelectTablePrimaryKey.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + var pks = await database.SqlStatement(await sr2.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => new { ConstraintName = dr.GetValue("CONSTRAINT_NAME"), TableSchema = dr.GetValue("TABLE_SCHEMA"), @@ -134,8 +102,8 @@ from c in t.Columns } // Configure all the single column foreign keys. - using var sr3 = StreamLocator.GetResourcesStreamReader("SelectTableForeignKeys.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - var fks = await SqlStatement(await sr3.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(dr => new + using var sr3 = StreamLocator.GetResourcesStreamReader("SelectTableForeignKeys.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + var fks = await database.SqlStatement(await sr3.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => new { ConstraintName = dr.GetValue("FK_CONSTRAINT_NAME"), TableSchema = dr.GetValue("FK_SCHEMA_NAME"), @@ -145,7 +113,7 @@ from c in t.Columns ForeignTable = dr.GetValue("UQ_TABLE_NAME"), ForiegnColumn = dr.GetValue("UQ_COLUMN_NAME") }).ConfigureAwait(false); - + foreach (var grp in fks.GroupBy(x => x.ConstraintName).Where(x => x.Count() == 1)) { var fk = grp.Single(); @@ -161,33 +129,36 @@ from c in t.Columns } // Select the table identity columns. - using var sr4 = StreamLocator.GetResourcesStreamReader("SelectTableIdentityColumns.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - await SqlStatement(await sr4.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(new DatabaseRecordMapper(dr => + using var sr4 = StreamLocator.GetResourcesStreamReader("SelectTableIdentityColumns.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + await database.SqlStatement(await sr4.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => { var t = tables.Single(x => x.Schema == dr.GetValue("TABLE_SCHEMA") && x.Name == dr.GetValue("TABLE_NAME")); var c = t.Columns.Single(x => x.Name == dr.GetValue("COLUMN_NAME")); c.IsIdentity = true; c.IdentitySeed = 1; c.IdentityIncrement = 1; - })).ConfigureAwait(false); + return 0; + }).ConfigureAwait(false); // Select the "always" generated columns. - using var sr5 = StreamLocator.GetResourcesStreamReader("SelectTableAlwaysGeneratedColumns.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - await SqlStatement(await sr5.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(new DatabaseRecordMapper(dr => + using var sr5 = StreamLocator.GetResourcesStreamReader("SelectTableAlwaysGeneratedColumns.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + await database.SqlStatement(await sr5.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => { var t = tables.Single(x => x.Schema == dr.GetValue("TABLE_SCHEMA") && x.Name == dr.GetValue("TABLE_NAME")); var c = t.Columns.Single(x => x.Name == dr.GetValue("COLUMN_NAME")); t.Columns.Remove(c); - })).ConfigureAwait(false); + return 0; + }).ConfigureAwait(false); // Select the generated columns. - using var sr6 = StreamLocator.GetResourcesStreamReader("SelectTableGeneratedColumns.sql", new Assembly[] { typeof(IDatabase).Assembly }).StreamReader!; - await SqlStatement(await sr6.ReadToEndAsync().ConfigureAwait(false)).SelectAsync(new DatabaseRecordMapper(dr => + using var sr6 = StreamLocator.GetResourcesStreamReader("SelectTableGeneratedColumns.sql", new Assembly[] { typeof(DatabaseExtensions).Assembly }).StreamReader!; + await database.SqlStatement(await sr6.ReadToEndAsync().ConfigureAwait(false)).SelectQueryAsync(dr => { var t = tables.Single(x => x.Schema == dr.GetValue("TABLE_SCHEMA") && x.Name == dr.GetValue("TABLE_NAME")); var c = t.Columns.Single(x => x.Name == dr.GetValue("COLUMN_NAME")); c.IsComputed = true; - })).ConfigureAwait(false); + return 0; + }).ConfigureAwait(false); // Attempt to infer foreign key reference data relationship where not explicitly specified. foreach (var t in tables) @@ -212,23 +183,28 @@ await SqlStatement(await sr6.ReadToEndAsync().ConfigureAwait(false)).SelectAsync return tables; } - /// - public void Dispose() + private class DatabaseRecordMapper : IDatabaseMapper { - Dispose(true); - GC.SuppressFinalize(this); - } + public Type SourceType => throw new NotImplementedException(); - /// - /// Dispose of the resources. - /// - /// Indicates whether to dispose. - protected virtual void Dispose(bool disposing) - { - if (disposing && _dbConn != null) + public object? MapFromDb(DatabaseRecord record, OperationTypes operationType = OperationTypes.Unspecified) + { + throw new NotImplementedException(); + } + + public void MapPrimaryKeyParameters(DatabaseParameterCollection parameters, OperationTypes operationType, object? value) + { + throw new NotImplementedException(); + } + + public void MapPrimaryKeyParameters(DatabaseParameterCollection parameters, OperationTypes operationType, CompositeKey key) + { + throw new NotImplementedException(); + } + + public void MapToDb(object? value, DatabaseParameterCollection parameters, OperationTypes operationType = OperationTypes.Unspecified) { - _dbConn.Dispose(); - _dbConn = null; + throw new NotImplementedException(); } } } diff --git a/src/DbEx/DatabaseParameterCollection.cs b/src/DbEx/DatabaseParameterCollection.cs deleted file mode 100644 index e3c2c14..0000000 --- a/src/DbEx/DatabaseParameterCollection.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; -using System.Data; -using System.Data.Common; - -namespace DbEx -{ - /// - /// Encapsulates the creation for a . - /// - public sealed class DatabaseParameterCollection - { - /// - /// Initializes a new instance of the . - /// - /// - public DatabaseParameterCollection(DbCommand command) => Command = command ?? throw new ArgumentNullException(nameof(command)); - - /// - /// Gets the underlying . - /// - public DbCommand Command { get; } - - /// - /// Adds the named parameter and value, using the specified and , to the . - /// - /// The parameter name. - /// The parameter value. - /// The parameter . - /// The (default to ). - /// A . - public DbParameter AddParameter(string name, object? value, DbType? dbType = null, ParameterDirection direction = ParameterDirection.Input) - { - var p = Command.CreateParameter(); - p.ParameterName = name ?? throw new ArgumentNullException(nameof(name)); - if (dbType.HasValue) - p.DbType = dbType.Value; - - p.Value = value; - p.Direction = direction; - - Command.Parameters.Add(p); - return p; - } - - /// - /// Adds the named parameter and value, using the specified and , to the . - /// - /// The parameter name. - /// The parameter value. - /// The parameter . - /// The (default to ). - /// The to support fluent-style method-chaining. - public DatabaseParameterCollection Param(string name, object? value, DbType? dbType = null, ParameterDirection direction = ParameterDirection.Input) - { - AddParameter(name, value, dbType, direction); - return this; - } - } -} \ No newline at end of file diff --git a/src/DbEx/DatabaseRecord.cs b/src/DbEx/DatabaseRecord.cs deleted file mode 100644 index 74bb912..0000000 --- a/src/DbEx/DatabaseRecord.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; -using System.Data.Common; - -namespace DbEx -{ - /// - /// Encapsulates the to provide requisite column value capabilities. - /// - public class DatabaseRecord - { - /// - /// Initializes a new instance of the class. - /// - /// The underlying . - public DatabaseRecord(DbDataReader dataReader) => DataReader = dataReader ?? throw new ArgumentNullException(nameof(dataReader)); - - /// - /// Gets the underlying . - /// - public DbDataReader DataReader { get; private set; } - - /// - /// Gets the named column value. - /// - /// The value . - /// The column name. - /// The value. - public T GetValue(string columnName) - { - var i = DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName))); - if (DataReader.IsDBNull(i)) - return default!; - - return DataReader.GetFieldValue(i); - } - - /// - /// Indicates whether the named column is . - /// - /// The column name. - /// true indicates that the column value has a value; otherwise, false. - public bool IsDBNull(string columnName) - { - var i = DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName))); - return DataReader.IsDBNull(i); - } - - /// - /// Gets the named RowVersion column as a . - /// - /// The name of the column. - /// The resultant value. - /// The RowVersion array will be converted to an encoded value. - public string GetRowVersion(string columnName) - { - var i = DataReader.GetOrdinal(columnName ?? throw new ArgumentNullException(nameof(columnName))); - return Convert.ToBase64String(DataReader.GetFieldValue(i)); - } - } -} \ No newline at end of file diff --git a/src/DbEx/DatabaseRecordMapper.cs b/src/DbEx/DatabaseRecordMapper.cs deleted file mode 100644 index 59e533c..0000000 --- a/src/DbEx/DatabaseRecordMapper.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Provides a per mapper that simulates a by invoking the action passed via the . - /// - public class DatabaseRecordMapper : IDatabaseMapper - { - private readonly Action _record; - - /// - /// Initializes a new instance of the class. - /// - /// The action. - public DatabaseRecordMapper(Action record) => _record = record ?? throw new ArgumentNullException(nameof(record)); - - /// - object? IDatabaseMapper.MapFromDb(DatabaseRecord record) - { - _record(record); - return null; - } - } -} \ No newline at end of file diff --git a/src/DbEx/DatabaseRecordMapperT.cs b/src/DbEx/DatabaseRecordMapperT.cs deleted file mode 100644 index d4138b9..0000000 --- a/src/DbEx/DatabaseRecordMapperT.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Provides a runtime generic . - /// - /// The resulting . - public class DatabaseRecordMapper : IDatabaseMapper - { - private readonly Func _record; - - /// - /// Initializes a new instance of the class. - /// - /// The function. - public DatabaseRecordMapper(Func record) => _record = record ?? throw new ArgumentNullException(nameof(record)); - - /// - public T MapFromDb(DatabaseRecord record) => _record(record); - } -} \ No newline at end of file diff --git a/src/DbEx/DbEx.csproj b/src/DbEx/DbEx.csproj index d0bd2d0..3c8ee76 100644 --- a/src/DbEx/DbEx.csproj +++ b/src/DbEx/DbEx.csproj @@ -3,7 +3,7 @@ netstandard2.1 DbEx - 1.0.9 + 1.0.10 true DbEx Developers Avanade @@ -89,8 +89,8 @@ - - + + diff --git a/src/DbEx/IDatabase.cs b/src/DbEx/IDatabase.cs deleted file mode 100644 index a2f8237..0000000 --- a/src/DbEx/IDatabase.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using DbEx.Schema; -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.Threading.Tasks; - -namespace DbEx -{ - /// - /// Defines the database access. - /// - public interface IDatabase : IDisposable - { - /// - /// Gets the . - /// - /// The connection is created and opened on first use, and closed on . - Task GetConnectionAsync(); - - /// - /// Creates a stored procedure . - /// - /// The stored procedure name. - /// An optional delegate to update the for the command. - /// The . - DatabaseCommand StoredProcedure(string storedProcedure, Action? parameters = null); - - /// - /// Creates a SQL statement . - /// - /// The SQL statement. - /// An optional delegate to update the for the command. - /// The . - DatabaseCommand SqlStatement(string sqlStatement, Action? parameters = null); - - /// - /// Invoked where a has been thrown. - /// - /// The . - /// Provides an opportunity to inspect and handle the exception before it bubbles up. - void OnDbException(DbException dbex); - - /// - /// Selects all the table and column schema details from the database. - /// - /// The reference data predicate used to determine whether a is considered a reference data table (sets ). - /// A list of all the table and column schema details. - Task> SelectSchemaAsync(Func? refDataPredicate = null); - } -} \ No newline at end of file diff --git a/src/DbEx/IDatabaseMapper.cs b/src/DbEx/IDatabaseMapper.cs deleted file mode 100644 index 9784a40..0000000 --- a/src/DbEx/IDatabaseMapper.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Defines a database mapper. - /// - /// The resulting . - public interface IDatabaseMapper - { - /// - /// Maps from a creating a corresponding instance of . - /// - /// The . - /// The corresponding instance of . - T MapFromDb(DatabaseRecord record); - } -} \ No newline at end of file diff --git a/src/DbEx/IMultiSetArgs.cs b/src/DbEx/IMultiSetArgs.cs deleted file mode 100644 index d7a19a9..0000000 --- a/src/DbEx/IMultiSetArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -namespace DbEx -{ - /// - /// Enables the Database multi-set arguments - /// - public interface IMultiSetArgs - { - /// - /// Gets the minimum number of rows allowed. - /// - int MinRows { get; } - - /// - /// Gets the maximum number of rows allowed. - /// - int? MaxRows { get; } - - /// - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - /// - bool StopOnNull { get; } - - /// - /// The method invoked for each record for its respective dataset. - /// - /// The . - void DatasetRecord(DatabaseRecord dr); - - /// - /// Invokes the corresponding result function. - /// - void InvokeResult(); - } -} \ No newline at end of file diff --git a/src/DbEx/IMultiSetArgsT.cs b/src/DbEx/IMultiSetArgsT.cs deleted file mode 100644 index 0a7248b..0000000 --- a/src/DbEx/IMultiSetArgsT.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Enables the Database multi-set arguments with a . - /// - /// The item . - public interface IMultiSetArgs : IMultiSetArgs - where T : class, new() - { - /// - /// Gets the for the . - /// - IDatabaseMapper Mapper { get; } - } -} \ No newline at end of file diff --git a/src/DbEx/Migration/DatabaseMigratorBase.cs b/src/DbEx/Migration/DatabaseMigratorBase.cs index b5f3501..4c61969 100644 --- a/src/DbEx/Migration/DatabaseMigratorBase.cs +++ b/src/DbEx/Migration/DatabaseMigratorBase.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx +using CoreEx.Database; using DbEx.Migration.Data; using DbUp.Engine; using Microsoft.Extensions.Logging; diff --git a/src/DbEx/Migration/SqlServer/SqlServerMigrator.cs b/src/DbEx/Migration/SqlServer/SqlServerMigrator.cs index fb6acd4..88f6b75 100644 --- a/src/DbEx/Migration/SqlServer/SqlServerMigrator.cs +++ b/src/DbEx/Migration/SqlServer/SqlServerMigrator.cs @@ -1,5 +1,7 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx +using CoreEx.Database; +using CoreEx.Database.SqlServer; using DbEx.Migration.Data; using DbEx.Migration.SqlServer.Internal; using DbUp; @@ -116,7 +118,7 @@ protected override async Task DatabaseDataAsync(IDatabase database, List DatabaseDataAsync(IDatabase database, List - protected override IDatabase CreateDatabase(string connectionString) => new Database(() => new SqlConnection(connectionString)); + protected override IDatabase CreateDatabase(string connectionString) => new SqlServerDatabase(() => new SqlConnection(connectionString)); /// public override async Task ExecuteScriptsAsync(IEnumerable scripts, bool includeExecutionLogging) diff --git a/src/DbEx/MultiSetCollArgs.cs b/src/DbEx/MultiSetCollArgs.cs deleted file mode 100644 index 83e4931..0000000 --- a/src/DbEx/MultiSetCollArgs.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Provides the base Database multi-set arguments when expecting a collection of items/records. - /// - public abstract class MultiSetCollArgs : IMultiSetArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The minimum number of rows allowed. - /// The maximum number of rows allowed. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - protected MultiSetCollArgs(int minRows = 0, int? maxRows = null, bool stopOnNull = false) - { - if (maxRows.HasValue && minRows <= maxRows.Value) - throw new ArgumentException("Max Rows is less than Min Rows.", nameof(maxRows)); - - MinRows = minRows; - MaxRows = maxRows; - StopOnNull = stopOnNull; - } - - /// - /// Gets or sets the minimum number of rows allowed. - /// - public int MinRows { get; } - - /// - /// Gets or sets the maximum number of rows allowed. - /// - public int? MaxRows { get; } - - /// - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - /// - public bool StopOnNull { get; set; } - - /// - /// The method invoked for each record for its respective dataset. - /// - /// The . - public abstract void DatasetRecord(DatabaseRecord dr); - - /// - /// Invokes the corresponding result function. - /// - public virtual void InvokeResult() { } - } -} \ No newline at end of file diff --git a/src/DbEx/MultiSetCollArgsT.cs b/src/DbEx/MultiSetCollArgsT.cs deleted file mode 100644 index 8313786..0000000 --- a/src/DbEx/MultiSetCollArgsT.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; -using System.Collections.Generic; - -namespace DbEx -{ - /// - /// Provides the Database multi-set arguments when expecting a collection of items/records. - /// - /// The collection . - /// The item . - public class MultiSetCollArgs : MultiSetCollArgs, IMultiSetArgs - where TItem : class, new() - where TColl : class, ICollection, new() - { - private TColl? _coll; - private readonly Action _result; - - /// - /// Initializes a new instance of the class. - /// - /// The for the . - /// The action that will be invoked with the result of the set. - /// The minimum number of rows allowed. - /// The maximum number of rows allowed. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - public MultiSetCollArgs(IDatabaseMapper mapper, Action result, int minRows = 0, int? maxRows = null, bool stopOnNull = false) : base(minRows, maxRows, stopOnNull) - { - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _result = result ?? throw new ArgumentNullException(nameof(result)); - } - - /// - /// Gets the for the . - /// - public IDatabaseMapper Mapper { get; private set; } - - /// - /// The method invoked for each record for its respective dataset. - /// - /// The . - public override void DatasetRecord(DatabaseRecord dr) - { - if (dr == null) - throw new ArgumentNullException(nameof(dr)); - - if (_coll == null) - _coll = new TColl(); - - var item = Mapper.MapFromDb(dr); - if (item != null) - _coll.Add(item); - } - - /// - /// Invokes the corresponding result function. - /// - public override void InvokeResult() - { - if (_coll != null) - _result(_coll); - } - } -} \ No newline at end of file diff --git a/src/DbEx/MultiSetSingleArgs.cs b/src/DbEx/MultiSetSingleArgs.cs deleted file mode 100644 index 6086869..0000000 --- a/src/DbEx/MultiSetSingleArgs.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -namespace DbEx -{ - /// - /// Provides the base Database multi-set arguments when expecting a single item/record only. - /// - public abstract class MultiSetSingleArgs : IMultiSetArgs - { - /// - /// Initializes a new instance of the class. - /// - /// Indicates whether the value is mandatory; defaults to true. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - protected MultiSetSingleArgs(bool isMandatory = true, bool stopOnNull = false) - { - IsMandatory = isMandatory; - StopOnNull = stopOnNull; - } - - /// - /// Indicates whether the value is mandatory; i.e. a corresponding record must be read. - /// - public bool IsMandatory { get; set; } - - /// - /// Gets or sets the minimum number of rows allowed. - /// - public int MinRows => IsMandatory ? 1 : 0; - - /// - /// Gets or sets the maximum number of rows allowed. - /// - public int? MaxRows => 1; - - /// - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - /// - public bool StopOnNull { get; set; } - - /// - /// The method invoked for each record for its respective dataset. - /// - /// The . - public abstract void DatasetRecord(DatabaseRecord dr); - - /// - /// Invokes the corresponding result function. - /// - public virtual void InvokeResult() { } - } -} \ No newline at end of file diff --git a/src/DbEx/MultiSetSingleArgsT.cs b/src/DbEx/MultiSetSingleArgsT.cs deleted file mode 100644 index 4108033..0000000 --- a/src/DbEx/MultiSetSingleArgsT.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; - -namespace DbEx -{ - /// - /// Provides the Database multi-set arguments when expecting a single item/record only. - /// - /// The item . - public class MultiSetSingleArgs : MultiSetSingleArgs, IMultiSetArgs - where T : class, new() - { - private T? _value; - private readonly Action _result; - - /// - /// Initializes a new instance of the class. - /// - /// The for the . - /// The action that will be invoked with the result of the set. - /// Indicates whether the value is mandatory; defaults to true. - /// Indicates whether to stop further query result set processing where the current set has resulted in a null (i.e. no records). - public MultiSetSingleArgs(IDatabaseMapper mapper, Action result, bool isMandatory = true, bool stopOnNull = false) : base(isMandatory, stopOnNull) - { - Mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _result = result ?? throw new ArgumentNullException(nameof(result)); - } - - /// - /// Gets the for the . - /// - public IDatabaseMapper Mapper { get; private set; } - - /// - /// The method invoked for each record for its respective dataset. - /// - /// The . - public override void DatasetRecord(DatabaseRecord dr) => _value = Mapper.MapFromDb(dr); - - /// - /// Invokes the corresponding result function. - /// - public override void InvokeResult() - { - if (_value != null) - _result(_value); - } - } -} \ No newline at end of file diff --git a/src/DbEx/SqlServer/EventOutboxDequeueBase.cs b/src/DbEx/SqlServer/EventOutboxDequeueBase.cs deleted file mode 100644 index 4b1a84f..0000000 --- a/src/DbEx/SqlServer/EventOutboxDequeueBase.cs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using CoreEx.Events; -using CoreEx.Json; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Transactions; - -namespace DbEx.SqlServer -{ - /// - /// Provides the base database outbox dequeue and corresponding . - /// - public abstract class EventOutboxDequeueBase : IDatabaseMapper - { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The . - public EventOutboxDequeueBase(IDatabase database, IEventSender eventSender, ILogger logger) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - EventSender = eventSender ?? throw new ArgumentNullException(nameof(eventSender)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// Gets the . - /// - protected IDatabase Database { get; } - - /// - /// Gets the . - /// - protected IEventSender EventSender { get; } - - /// - /// Gets the . - /// - protected ILogger Logger { get; } - - /// - /// Gets the event outbox dequeue stored procedure name. - /// - protected abstract string DequeueStoredProcedure { get; } - - /// - /// Gets the column name for the property within the event outbox table. - /// - /// Defaults to 'EventId'. - protected virtual string EventIdColumnName => "EventId"; - - /// - /// Gets or sets the default partition key. - /// - /// Defaults to '$default'. This will ensure that value is nullified when reading from the database. - public string DefaultPartitionKey { get; set; } = "$default"; - - /// - /// Gets or sets the default destination name. - /// - /// Defaults to '$default'. This will ensure that value is nullified when reading from the database. - public string DefaultDestination { get; set; } = "$default"; - - /// - /// Performs the dequeue of the events (up to ) from the database outbox and then sends (via ). - /// - /// The maximum dequeue size. Defaults to 50. - /// The partition key. - /// The destination name (i.e. queue or topic). - /// The . - /// The number of dequeued and sent events. - public async Task DequeueAndSendAsync(int maxDequeueSize = 50, string? partitionKey = null, string? destination = null, CancellationToken cancellationToken = default) - { - Stopwatch sw; - maxDequeueSize = maxDequeueSize > 0 ? maxDequeueSize : 1; - - // Keep executing until unsuccessful or reached end of event outbox stream. - while (true) - { - // Where a cancel has been requested then this is a convenient time to do it. - if (cancellationToken.IsCancellationRequested) - return 0; - - // Manage a transaction to ensure that the dequeue only commits after successful publish. - using var txn = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); - - // Dequeue the events; where there are none to send, then simply exit and try again later. - Logger.LogTrace("Dequeue events. [MaxDequeueSize={MaxDequeueSize}, PartitionKey={PartitionKey}, Destination={Destination}]", maxDequeueSize, partitionKey, destination); - - sw = Stopwatch.StartNew(); - var events = await DequeueAsync(maxDequeueSize, partitionKey, destination).ConfigureAwait(false); - sw.Stop(); - - if (events == null || !events.Any()) - { - txn.Complete(); - return 0; - } - - Logger.LogInformation("{EventCount} event(s) were dequeued. [Elapsed={Elapsed}ms]", events.Count(), sw.Elapsed.TotalMilliseconds); - - // Send the events. - sw = Stopwatch.StartNew(); - await EventSender.SendAsync(events.ToArray(), cancellationToken).ConfigureAwait(false); - sw.Stop(); - Logger.LogInformation("{EventCount} event(s) were sent successfully. [Sender={Sender}, Elapsed={Elapsed}ms]", events.Count(), EventSender.GetType().Name, sw.Elapsed.TotalMilliseconds); - - // Commit the transaction. - txn.Complete(); - return events.Count(); - } - } - - /// - /// Dequeues the list. - /// - private async Task> DequeueAsync(int maxDequeueSize, string? partitionKey, string? destination) - => await Database.StoredProcedure(DequeueStoredProcedure, p => p.Param("@MaxDequeueSize", maxDequeueSize).Param("@PartitionKey", partitionKey).Param("@Destination", destination)) - .SelectAsync(this).ConfigureAwait(false); - - /// - public EventSendData MapFromDb(DatabaseRecord record) - { - var source = record.GetValue(nameof(EventSendData.Source)); - var attributes = record.GetValue(nameof(EventSendData.Attributes)); - var data = record.GetValue(nameof(EventSendData.Data)); - var destination = record.GetValue(nameof(EventSendData.Destination)); - var partitionKey = record.GetValue(nameof(EventSendData.PartitionKey)); - - return new() - { - Id = record.GetValue(EventIdColumnName), - Destination = destination == DefaultDestination ? null : destination, - Subject = record.GetValue(nameof(EventSendData.Subject)), - Action = record.GetValue(nameof(EventSendData.Action)), - Type = record.GetValue(nameof(EventSendData.Type)), - Source = string.IsNullOrEmpty(source) ? null : new Uri(source, UriKind.RelativeOrAbsolute), - Timestamp = record.GetValue(nameof(EventSendData.Timestamp)), - CorrelationId = record.GetValue(nameof(EventSendData.CorrelationId)), - TenantId = record.GetValue(nameof(EventSendData.TenantId)), - PartitionKey = partitionKey == DefaultPartitionKey ? null : partitionKey, - ETag = record.GetValue(nameof(EventSendData.ETag)), - Attributes = attributes == null || attributes.Length == 0 ? null : JsonSerializer.Default.Deserialize>(new BinaryData(attributes)), - Data = data == null || data.Length == 0 ? null : new BinaryData(data) - }; - } - } -} \ No newline at end of file diff --git a/src/DbEx/SqlServer/EventOutboxEnqueueBase.cs b/src/DbEx/SqlServer/EventOutboxEnqueueBase.cs deleted file mode 100644 index c7fd5d6..0000000 --- a/src/DbEx/SqlServer/EventOutboxEnqueueBase.cs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using CoreEx.Events; -using CoreEx.Json; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace DbEx.SqlServer -{ - /// - /// Provides the base database outbox enqueue . - /// - /// By default the events are first sent/enqueued to the datatbase outbox, then a secondary out-of-process dequeues and sends. This can however introduce unwanted latency depending on the frequency in which the secondary process - /// performs the dequeue and send, as this is essentially a polling-based operation. To improve (minimize) latency, the primary can be specified using . This will - /// then be used to send the events immediately, and where successful, they will be audited in the database as dequeued event(s); versus on error (as a backup), where they will be enqueued for the out-of-process dequeue and send (as per default). - public abstract class EventOutboxEnqueueBase : IEventSender - { - private IEventSender? _eventSender; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - public EventOutboxEnqueueBase(IDatabase database, ILogger logger) - { - Database = database ?? throw new ArgumentNullException(nameof(database)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// Gets the . - /// - protected IDatabase Database { get; } - - /// - /// Gets the . - /// - protected ILogger Logger { get; } - - /// - /// Gets the database type name for the . - /// - protected abstract string DbTvpTypeName { get; } - - /// - /// Gets the event outbox enqueue stored procedure name. - /// - protected abstract string EnqueueStoredProcedure { get; } - - /// - /// Gets the column name for the property within the event outbox table. - /// - /// Defaults to 'EventId'. - protected virtual string EventIdColumnName => "EventId"; - - /// - /// Gets or sets the default partition key. - /// - /// Defaults to '$default'. This will ensure that there is always a value recorded in the database. - public string DefaultPartitionKey { get; set; } = "$default"; - - /// - /// Gets or sets the default destination name. - /// - /// Defaults to '$default'. This will ensure that there is always a value recorded in the database. - public string DefaultDestination { get; set; } = "$default"; - - /// - /// Sets the optional to act as the primary where outbox enqueue is to provide backup/audit capabilities. - /// - public void SetPrimaryEventSender(IEventSender eventSender) - { - if (eventSender != null & eventSender is EventOutboxEnqueueBase) - throw new ArgumentException($"{nameof(SetPrimaryEventSender)} value must not be of Type {nameof(EventOutboxEnqueueBase)}.", nameof(eventSender)); - - _eventSender = eventSender; - } - - /// - /// - /// - /// - /// - /// Executes the to send / enqueue the to the underlying database outbox tables. - public async Task SendAsync(IEnumerable events, CancellationToken cancellationToken = default) - { - if (events == null || !events.Any()) - return; - - Stopwatch sw = Stopwatch.StartNew(); - var unsentEvents = new List(events); - - if (_eventSender != null) - { - try - { - await _eventSender!.SendAsync(events, cancellationToken).ConfigureAwait(false); - sw.Stop(); - unsentEvents.Clear(); - Logger.LogDebug("{EventCount} event(s) were sent successfully; will be forwarded (sent/enqueued) to the datatbase outbox as sent. [Sender={Sender}, Elapsed={Elapsed}ms]", - events.Count(), _eventSender.GetType().Name, sw.Elapsed.TotalMilliseconds); - } - catch (EventSendException esex) - { - sw.Stop(); - Logger.LogWarning(esex, "{UnsentCount} of {EventCount} event(s) were unable to be sent successfully; will be forwarded (sent/enqueued) to the datatbase outbox for an out-of-process send: {ErrorMessage} [Sender={Sender}, Elapsed={Elapsed}ms]", - esex.NotSentEvents?.Count() ?? unsentEvents.Count, events.Count(), esex.Message, _eventSender!.GetType().Name, sw.Elapsed.TotalMilliseconds); - - if (esex.NotSentEvents != null) - unsentEvents = esex.NotSentEvents.ToList(); - } - catch (Exception ex) - { - sw.Stop(); - Logger.LogWarning(ex, "{EventCount} event(s) were unable to be sent successfully; will be forwarded (sent/enqueued) to the datatbase outbox for an out-of-process send: {ErrorMessage} [Sender={Sender}, Elapsed={Elapsed}ms]", - events.Count(), ex.Message, _eventSender!.GetType().Name, sw.Elapsed.TotalMilliseconds); - } - } - - sw = Stopwatch.StartNew(); - await Database.StoredProcedure(EnqueueStoredProcedure, p => p.AddTableValuedParameter("@EventList", CreateTableValuedParameter(events, unsentEvents))).NonQueryAsync().ConfigureAwait(false); - sw.Stop(); - Logger.LogDebug("{EventCount} event(s) were enqueued; {SuccessCount} as sent, {ErrorCount} to be sent. [Sender={Sender}, Elapsed={Elapsed}ms]", - events.Count(), events.Count() - unsentEvents.Count, unsentEvents.Count, GetType().Name, sw.Elapsed.TotalMilliseconds); - } - - /// - /// Creates the TVP from the list. - /// - private TableValuedParameter CreateTableValuedParameter(IEnumerable list, IEnumerable unsentList) - { - var dt = new DataTable(); - dt.Columns.Add(EventIdColumnName, typeof(string)); - dt.Columns.Add("EventDequeued", typeof(bool)); - dt.Columns.Add(nameof(EventSendData.Destination), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Subject), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Action), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Type), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Source), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Timestamp), typeof(DateTimeOffset)); - dt.Columns.Add(nameof(EventSendData.CorrelationId), typeof(string)); - dt.Columns.Add(nameof(EventSendData.TenantId), typeof(string)); - dt.Columns.Add(nameof(EventSendData.PartitionKey), typeof(string)); - dt.Columns.Add(nameof(EventSendData.ETag), typeof(string)); - dt.Columns.Add(nameof(EventSendData.Attributes), typeof(byte[])); - dt.Columns.Add(nameof(EventSendData.Data), typeof(byte[])); - - var tvp = new TableValuedParameter(DbTvpTypeName, dt); - foreach (var item in list) - { - var attributes = item.Attributes == null || item.Attributes.Count == 0 ? new BinaryData(Array.Empty()) : JsonSerializer.Default.SerializeToBinaryData(item.Attributes); - tvp.AddRow(item.Id, !unsentList.Contains(item), - item.Destination ?? DefaultDestination ?? throw new InvalidOperationException($"The {nameof(DefaultDestination)} must have a non-null value."), - item.Subject, item.Action, item.Type, item.Source, item.Timestamp, item.CorrelationId, item.TenantId, - item.PartitionKey ?? DefaultPartitionKey ?? throw new InvalidOperationException($"The {nameof(DefaultPartitionKey)} must have a non-null value."), - item.ETag, attributes.ToArray(), item.Data?.ToArray()); - } - - return tvp; - } - } -} \ No newline at end of file diff --git a/src/DbEx/SqlServer/SqlServerExtensions.cs b/src/DbEx/SqlServer/SqlServerExtensions.cs deleted file mode 100644 index f3d9e1d..0000000 --- a/src/DbEx/SqlServer/SqlServerExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using Microsoft.Data.SqlClient; -using System; -using System.Data; -using System.Data.Common; - -namespace DbEx.SqlServer -{ - /// - /// Provides SQL Server extension methods. - /// - public static class SqlServerExtensions - { - /// - /// Adds the named value to the . - /// - /// The . - /// The parameter name. - /// The value. - /// A . - /// This specifically implies that the is being used; if not then an exception will be thrown. - public static SqlParameter AddTableValuedParameter(this DatabaseParameterCollection dpc, string name, TableValuedParameter tvp) - { - var p = (SqlParameter)(dpc ?? throw new ArgumentNullException(nameof(dpc))).Command.CreateParameter(); - p.ParameterName = name ?? throw new ArgumentNullException(nameof(name)); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = tvp.TypeName; - p.Value = tvp.Value; - p.Direction = ParameterDirection.Input; - - dpc.Command.Parameters.Add(p); - return p; - } - - /// - /// Adds the named value to the . - /// - /// The . - /// The parameter name. - /// The value. - /// The to support fluent-style method-chaining. - /// This specifically implies that the is being used; if not then an exception will be thrown. - public static DatabaseParameterCollection Param(this DatabaseParameterCollection dpc, string name, TableValuedParameter tvp) - { - (dpc ?? throw new ArgumentNullException(nameof(dpc))).AddTableValuedParameter(name, tvp); - return dpc; - } - } -} \ No newline at end of file diff --git a/src/DbEx/SqlServer/TableValuedParameter.cs b/src/DbEx/SqlServer/TableValuedParameter.cs deleted file mode 100644 index f0ce68e..0000000 --- a/src/DbEx/SqlServer/TableValuedParameter.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx - -using System; -using System.Data; - -namespace DbEx.SqlServer -{ - /// - /// Represents a SQL-Server table-valued parameter (see ). - /// - public class TableValuedParameter - { - /// - /// Initializes a new instance of the class. - /// - /// The SQL type name of the table-valued parameter. - /// The value. - public TableValuedParameter(string typeName, DataTable value) - { - TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - /// Gets or sets the SQL type name of the table-valued parameter. - /// - public string TypeName { get; } - - /// - /// Gets or sets the value. - /// - public DataTable Value { get; } - - /// - /// Adds a new to the using the specified . - /// - /// The column values. - public void AddRow(params object?[] columnValues) - { - var r = Value.NewRow(); - for (int i = 0; i < columnValues.Length; i++) - { - r[i] = columnValues[i] ?? DBNull.Value; - } - - Value.Rows.Add(r); - } - } -} \ No newline at end of file diff --git a/src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs b/src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs index 4e4277c..f5f2c8c 100644 --- a/src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs +++ b/src/DbEx/Templates/SqlServer/EventOutboxDequeue_cs.hbs @@ -6,9 +6,9 @@ #nullable enable #pragma warning disable +using CoreEx.Database; +using CoreEx.Database.SqlServer; using CoreEx.Events; -using DbEx; -using DbEx.SqlServer; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Data; diff --git a/src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs b/src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs index 92c05b1..9f361fb 100644 --- a/src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs +++ b/src/DbEx/Templates/SqlServer/EventOutboxEnqueue_cs.hbs @@ -6,8 +6,8 @@ #nullable enable #pragma warning disable -using DbEx; -using DbEx.SqlServer; +using CoreEx.Database; +using CoreEx.Database.SqlServer; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Data; diff --git a/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs b/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs index 05337ec..bb0d944 100644 --- a/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs +++ b/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxDequeue.cs @@ -5,9 +5,9 @@ #nullable enable #pragma warning disable +using CoreEx.Database; +using CoreEx.Database.SqlServer; using CoreEx.Events; -using DbEx; -using DbEx.SqlServer; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Data; diff --git a/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs b/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs index ddd15ac..02ca2dc 100644 --- a/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs +++ b/tests/DbEx.Test.OutboxConsole/Generated/EventOutboxEnqueue.cs @@ -5,8 +5,8 @@ #nullable enable #pragma warning disable -using DbEx; -using DbEx.SqlServer; +using CoreEx.Database; +using CoreEx.Database.SqlServer; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Data; diff --git a/tests/DbEx.Test.OutboxConsole/Program.cs b/tests/DbEx.Test.OutboxConsole/Program.cs index 0304b3a..29d579a 100644 --- a/tests/DbEx.Test.OutboxConsole/Program.cs +++ b/tests/DbEx.Test.OutboxConsole/Program.cs @@ -8,6 +8,6 @@ namespace DbEx.Test.OutboxConsole public class Program { internal static Task Main(string[] args) - => new CodeGenConsole(new CodeGeneratorArgs("Script.yaml", "Config.yaml") { OutputDirectory = new DirectoryInfo(CodeGenConsole.GetBaseExeDirectory()) }.AddAssembly(typeof(IDatabase).Assembly)).RunAsync(args); + => new CodeGenConsole(new CodeGeneratorArgs("Script.yaml", "Config.yaml") { OutputDirectory = new DirectoryInfo(CodeGenConsole.GetBaseExeDirectory()) }.AddAssembly(typeof(DatabaseExtensions).Assembly)).RunAsync(args); } } \ No newline at end of file diff --git a/tests/DbEx.Test/DatabaseSchemaTest.cs b/tests/DbEx.Test/DatabaseSchemaTest.cs index fdda4be..d50874d 100644 --- a/tests/DbEx.Test/DatabaseSchemaTest.cs +++ b/tests/DbEx.Test/DatabaseSchemaTest.cs @@ -1,4 +1,5 @@ -using DbEx.Migration.SqlServer; +using CoreEx.Database.SqlServer; +using DbEx.Migration.SqlServer; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using NUnit.Framework; @@ -20,7 +21,7 @@ public async Task SelectSchema() var r = await m.MigrateAsync().ConfigureAwait(false); Assert.IsTrue(r); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); var tables = await db.SelectSchemaAsync().ConfigureAwait(false); Assert.IsNotNull(tables); diff --git a/tests/DbEx.Test/DbEx.Test.csproj b/tests/DbEx.Test/DbEx.Test.csproj index 958db10..21533a5 100644 --- a/tests/DbEx.Test/DbEx.Test.csproj +++ b/tests/DbEx.Test/DbEx.Test.csproj @@ -12,9 +12,12 @@ - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/DbEx.Test/SqlServerMigratorTest.cs b/tests/DbEx.Test/SqlServerMigratorTest.cs index 7a4979f..161aed2 100644 --- a/tests/DbEx.Test/SqlServerMigratorTest.cs +++ b/tests/DbEx.Test/SqlServerMigratorTest.cs @@ -1,4 +1,6 @@ -using DbEx.Migration.Data; +using CoreEx.Database; +using CoreEx.Database.SqlServer; +using DbEx.Migration.Data; using DbEx.Migration.SqlServer; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; @@ -42,8 +44,8 @@ public async Task A120_MigrateAll_Console() var (cs, l, m) = await CreateConsoleDb().ConfigureAwait(false); // Check that the contact data was updated as expected. - using var db = new Database(() => new SqlConnection(cs)); - var res = (await db.SqlStatement("SELECT * FROM [Test].[Contact]").SelectAsync(dr => new + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); + var res = (await db.SqlStatement("SELECT * FROM [Test].[Contact]").SelectQueryAsync(dr => new { ContactId = dr.GetValue("ContactId"), Name = dr.GetValue("Name"), @@ -80,7 +82,7 @@ public async Task A120_MigrateAll_Console() Assert.AreEqual(2, row.GenderId); // Check that the person data was updated as expected - converted and auto-assigned id, plus createdby and createddate columns, and finally runtime variable. - var res2 = (await db.SqlStatement("SELECT * FROM [Test].[Person]").SelectAsync(dr => new + var res2 = (await db.SqlStatement("SELECT * FROM [Test].[Person]").SelectQueryAsync(dr => new { PersonId = dr.GetValue("PersonId"), Name = dr.GetValue("Name"), @@ -102,7 +104,7 @@ public async Task A120_MigrateAll_Console() Assert.AreEqual(m.ParserArgs.DateTimeNow, row2.CreatedDate); // Check that the stored procedure script was migrated and works! - res = (await db.StoredProcedure("[Test].[spGetContact]", p => p.Param("@ContactId", 2)).SelectAsync(dr => new + res = (await db.StoredProcedure("[Test].[spGetContact]").Param("@ContactId", 2).SelectQueryAsync(dr => new { ContactId = dr.GetValue("ContactId"), Name = dr.GetValue("Name"), diff --git a/tests/DbEx.Test/SqlServerOutboxTest.cs b/tests/DbEx.Test/SqlServerOutboxTest.cs index f15ffec..df116ea 100644 --- a/tests/DbEx.Test/SqlServerOutboxTest.cs +++ b/tests/DbEx.Test/SqlServerOutboxTest.cs @@ -1,14 +1,16 @@ -using NUnit.Framework; +using CoreEx.Database; +using CoreEx.Database.SqlServer; +using CoreEx.Events; +using DbEx.Migration.SqlServer; +using DbEx.Test.OutboxConsole.Data; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; -using System.Threading.Tasks; -using DbEx.Test.OutboxConsole.Data; -using CoreEx.Events; +using NUnit.Framework; using System; using System.Collections.Generic; -using DbEx.Migration.SqlServer; -using System.Threading; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace DbEx.Test { @@ -32,7 +34,7 @@ public async Task A100_EnqueueDequeue() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var esd = new EventSendData @@ -89,7 +91,7 @@ public async Task A110_EnqueueDequeue_MaxDequeueSize() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var eoe = new EventOutboxEnqueue(db, UnitTest.GetLogger()); @@ -129,7 +131,7 @@ public async Task A120_EnqueueDequeue_PartitionKey_Destination_Selection() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var eoe = new EventOutboxEnqueue(db, UnitTest.GetLogger()); @@ -184,7 +186,7 @@ public async Task B100_EnqueueDequeue_PrimarySender_Success() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var eoe = new EventOutboxEnqueue(db, UnitTest.GetLogger()); @@ -211,7 +213,7 @@ public async Task B110_EnqueueDequeue_PrimarySender_Error() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var eoe = new EventOutboxEnqueue(db, UnitTest.GetLogger()); @@ -234,7 +236,7 @@ public async Task B120_EnqueueDequeue_PrimarySender_EventSendException() var cs = UnitTest.GetConfig("DbEx_").GetConnectionString("ConsoleDb"); var l = UnitTest.GetLogger(); - using var db = new Database(() => new SqlConnection(cs)); + using var db = new SqlServerDatabase(() => new SqlConnection(cs)); await db.SqlStatement("DELETE FROM [Outbox].[EventOutbox]").NonQueryAsync().ConfigureAwait(false); var eoe = new EventOutboxEnqueue(db, UnitTest.GetLogger());