From b37d7007cba74433c768bc0f4b141d75e69619d8 Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Wed, 2 Oct 2024 16:16:42 -0700 Subject: [PATCH] v2.6.0 - *Enhancement:* Database code-generation defaults to the use of [JSON](https://learn.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server)-serialized parameters versus UDT/TVP to minimize the need for additional database objects; specifically [User-Defined Types](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-type-transact-sql) (UDT). - This will now require a SQL Server version of 2016 or later; use earlier _DbEx_ versions that use UDT/TVP which are supported on earlier SQL Server versions. - *Enhancement:* All code-generated stored procedures now use `CREATE OR ALTER`; again, requires SQL Server 2016 or later. --- CHANGELOG.md | 5 ++++ Common.targets | 2 +- .../ContactSync.NewApp.Database.csproj | 2 +- .../ContactSync.NewApp.Subscriber.csproj | 8 +++---- .../ContactSync.OldApp.Database.csproj | 2 +- .../Generated/spContactBatchComplete.sql | 8 ++++--- .../Generated/spContactBatchExecute.sql | 2 +- .../Generated/spContactBatchReset.sql | 2 +- .../Generated/udtVersionTrackingList.sql | 8 ------- .../Generated/spEventOutboxDequeue.sql | 2 +- .../Generated/spEventOutboxEnqueue.sql | 24 ++++++++++++++++--- .../Generated/udtEventOutboxList.sql | 21 ---------------- .../ContactSync.OldApp.Publisher.csproj | 4 ++-- .../Data/Generated/EventOutboxEnqueue.cs | 3 --- .../Generated/spContactBatchComplete.sql | 8 ++++--- .../Generated/spContactBatchExecute.sql | 2 +- .../Generated/spContactBatchReset.sql | 2 +- .../Generated/spCustomerBatchComplete.sql | 8 ++++--- .../Generated/spCustomerBatchExecute.sql | 2 +- .../Generated/spCustomerBatchReset.sql | 2 +- .../Generated/spIdentifierMappingCreate.sql | 11 +++++---- .../Generated/spPostsBatchComplete.sql | 8 ++++--- .../Generated/spPostsBatchExecute.sql | 2 +- .../Generated/spPostsBatchReset.sql | 2 +- .../Generated/udtIdentifierMappingList.sql | 10 -------- .../Generated/udtVersionTrackingList.sql | 8 ------- .../Generated/spEventOutboxDequeue.sql | 2 +- .../Generated/spEventOutboxEnqueue.sql | 24 ++++++++++++++++--- .../Generated/udtEventOutboxList.sql | 21 ---------------- .../SqlServerDemo.Database.csproj | 2 +- .../Data/Generated/EventOutboxEnqueue.cs | 3 --- .../SqlServerDemo.Publisher.csproj | 2 +- .../SqlServerDemo.Test.csproj | 2 +- src/NTangle/Cdc/EntityOrchestrator.cs | 5 ++-- src/NTangle/Cdc/EntityOrchestratorBase.cs | 8 +++---- src/NTangle/Cdc/EntityOrchestratorT.cs | 7 +++--- src/NTangle/Data/IDatabaseTvp.cs | 22 ----------------- src/NTangle/Data/IIdentifierMappingTvp.cs | 22 ----------------- .../Data/IdentifierMappingMapperBase.cs | 22 +---------------- src/NTangle/Data/VersionTrackerMapperBase.cs | 20 +--------------- src/NTangle/NTangle.csproj | 2 +- tests/NTangle.Test/TemplateTest.cs | 2 +- tools/NTangle.CodeGen/NTangle.CodeGen.csproj | 4 ++-- .../Scripts/SqlServerDatabase.yaml | 3 --- .../Templates/SpCompleteBatch_sql.hbs | 8 ++++--- .../Templates/SpExecuteBatch_sql.hbs | 2 +- .../SpIdentifierMappingCreate_sql.hbs | 11 +++++---- .../Templates/SpResetBatch_sql.hbs | 2 +- .../Templates/UdtIdentifierMapping_sql.hbs | 11 --------- .../Templates/UdtVersionTracking_sql.hbs | 9 ------- .../AppName.CodeGen/AppName.CodeGen.csproj | 2 +- .../AppName.Database/AppName.Database.csproj | 2 +- .../AppName.Publisher.csproj | 4 ++-- 53 files changed, 128 insertions(+), 254 deletions(-) delete mode 100644 samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql delete mode 100644 samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql delete mode 100644 samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtIdentifierMappingList.sql delete mode 100644 samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql delete mode 100644 samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql delete mode 100644 src/NTangle/Data/IDatabaseTvp.cs delete mode 100644 src/NTangle/Data/IIdentifierMappingTvp.cs delete mode 100644 tools/NTangle.CodeGen/Templates/UdtIdentifierMapping_sql.hbs delete mode 100644 tools/NTangle.CodeGen/Templates/UdtVersionTracking_sql.hbs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2653552..29651ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Represents the **NuGet** versions. +## v2.6.0 +- *Enhancement:* Database code-generation defaults to the use of [JSON](https://learn.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server)-serialized parameters versus UDT/TVP to minimize the need for additional database objects; specifically [User-Defined Types](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-type-transact-sql) (UDT). + - This will now require a SQL Server version of 2016 or later; use earlier _DbEx_ versions that use UDT/TVP which are supported on earlier SQL Server versions. +- *Enhancement:* All code-generated stored procedures now use `CREATE OR ALTER`; again, requires SQL Server 2016 or later. + ## v2.5.2 - *Fixed:* Updated `CoreEx` (`v3.15.0`) and other dependencies. - *Fixed:* Simplify event outbox C# code-generation templates for primary constructor usage. diff --git a/Common.targets b/Common.targets index 4e0e9be..ae8f214 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 2.5.2 + 2.6.0 true NTangle Developers Avanade diff --git a/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Database/ContactSync.NewApp.Database.csproj b/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Database/ContactSync.NewApp.Database.csproj index 06ba6fb..c6f4a75 100644 --- a/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Database/ContactSync.NewApp.Database.csproj +++ b/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Database/ContactSync.NewApp.Database.csproj @@ -14,7 +14,7 @@ - + diff --git a/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Subscriber/ContactSync.NewApp.Subscriber.csproj b/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Subscriber/ContactSync.NewApp.Subscriber.csproj index 3470d51..e3acb41 100644 --- a/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Subscriber/ContactSync.NewApp.Subscriber.csproj +++ b/samples/ContactSync/ContactSync.NewApp/ContactSync.NewApp.Subscriber/ContactSync.NewApp.Subscriber.csproj @@ -26,11 +26,11 @@ PreserveNewest - - - + + + - + diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/ContactSync.OldApp.Database.csproj b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/ContactSync.OldApp.Database.csproj index cf5a722..46a1693 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/ContactSync.OldApp.Database.csproj +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/ContactSync.OldApp.Database.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql index d6730c7..2eb3dea 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [NTangle].[spContactBatchComplete] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchComplete] @BatchTrackingId BIGINT, - @VersionTrackingList AS [NTangle].[udtVersionTrackingList] READONLY + @VersionTrackingList AS NVARCHAR(MAX) AS BEGIN /* @@ -35,6 +35,8 @@ BEGIN DECLARE @Timestamp DATETIME2 SET @Timestamp = GETUTCDATE() + SELECT * into #versionTrackingList FROM OPENJSON(@VersionTrackingList) WITH ([Key] NVARCHAR(255) '$.key', [Hash] NVARCHAR(127) '$.hash') + UPDATE [_batch] SET [_batch].[IsComplete] = 1, [_batch].[CompletedDate] = @Timestamp @@ -42,7 +44,7 @@ BEGIN WHERE BatchTrackingId = @BatchTrackingId MERGE INTO [NTangle].[VersionTracking] WITH (HOLDLOCK) AS [_vt] - USING @VersionTrackingList AS [_list] ON ([_vt].[Schema] = N'old' AND [_vt].[Table] = N'Contact' AND [_vt].[Key] = [_list].[Key]) + USING #versionTrackingList AS [_list] ON ([_vt].[Schema] = N'old' AND [_vt].[Table] = N'Contact' AND [_vt].[Key] = [_list].[Key]) WHEN MATCHED AND EXISTS ( SELECT [_list].[Key], [_list].[Hash] EXCEPT diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql index 7fb1e6b..4f54fc9 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spContactBatchExecute] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchExecute] @MaxQuerySize BIGINT = 100, -- Maximum size of query to limit the number of changes to a manageable batch (performance vs failure trade-off). @ContinueWithDataLoss BIT = 0 -- Ignores data loss and continues; versus throwing an error. AS diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql index d75661f..4c43c5d 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spContactBatchReset] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchReset] AS BEGIN /* diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql deleted file mode 100644 index 1a158f0..0000000 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TYPE [NTangle].[udtVersionTrackingList] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [Key] NVARCHAR(255) NOT NULL, - [Hash] NVARCHAR(127) NOT NULL -) \ No newline at end of file diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql index 3bc4462..530006f 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [Outbox].[spEventOutboxDequeue] +CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxDequeue] @MaxDequeueSize INT = 10, -- Maximum number of events to dequeue. @PartitionKey NVARCHAR(127) NULL = NULL, -- Partition key; null indicates all. @Destination NVARCHAR(127) NULL = NULL -- Destination (queue or topic); null indicates all. diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql index c6130f0..5858e9a 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [Outbox].[spEventOutboxEnqueue] +CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxEnqueue] @SetEventsAsDequeued AS BIT = 0, - @EventList AS [Outbox].[udtEventOutboxList] READONLY + @EventList AS NVARCHAR(MAX) AS BEGIN /* @@ -24,6 +24,24 @@ BEGIN -- Enqueued outbox resultant identifier. DECLARE @enqueuedId TABLE([EventOutboxId] BIGINT) + -- Convert the JSON to a temporary table. + SELECT * INTO #eventList FROM OPENJSON(@EventList) WITH ( + [EventId] NVARCHAR(127) '$.EventId', + [EventDequeued] BIT '$.EventDequeued', + [Destination] NVARCHAR(127) '$.Destination', + [Subject] NVARCHAR(511) '$.Subject', + [Action] NVARCHAR(255) '$.Action', + [Type] NVARCHAR(1023) '$.Type', + [Source] NVARCHAR(1023) '$.Source', + [Timestamp] DATETIMEOFFSET '$.Timestamp', + [CorrelationId] NVARCHAR(127) '$.CorrelationId', + [Key] NVARCHAR(1023) '$.Key', + [TenantId] NVARCHAR(127) '$.TenantId', + [PartitionKey] NVARCHAR(127) '$.PartitionKey', + [ETag] NVARCHAR(127) '$.ETag', + [Attributes] VARBINARY(MAX) '$.Attributes', + [Data] VARBINARY(MAX) '$.Data') + -- Cursor output variables. DECLARE @eventId NVARCHAR(127), @eventDequeued BIT, @@ -43,7 +61,7 @@ BEGIN -- Declare, open, and fetch first event from cursor. DECLARE c CURSOR FORWARD_ONLY - FOR SELECT [EventId], [EventDequeued], [Destination], [Subject], [Action], [Type], [Source], [Timestamp], [CorrelationId], [Key], [TenantId], [PartitionKey], [ETag], [Attributes], [Data] FROM @EventList + FOR SELECT [EventId], [EventDequeued], [Destination], [Subject], [Action], [Type], [Source], [Timestamp], [CorrelationId], [Key], [TenantId], [PartitionKey], [ETag], [Attributes], [Data] FROM #eventList OPEN c FETCH NEXT FROM c INTO @eventId, @eventDequeued, @destination, @subject, @action, @type, @source, @timestamp, @correlationId, @key, @tenantId, @partitionKey, @etag, @attributes, @data diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql deleted file mode 100644 index 28537ef..0000000 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TYPE [Outbox].[udtEventOutboxList] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [EventId] NVARCHAR(127), - [EventDequeued] BIT NULL, -- Indicates whether to set the event as dequeued; i.e. already sent/processed. - [Destination] NVARCHAR(127) NULL, -- For example, queue/topic name. - [Subject] NVARCHAR(511) NULL, - [Action] NVARCHAR(255) NULL, - [Type] NVARCHAR(1023) NULL, - [Source] NVARCHAR(1023) NULL, - [Timestamp] DATETIMEOFFSET, - [CorrelationId] NVARCHAR(127), - [Key] NVARCHAR(1023) NULL, - [TenantId] NVARCHAR(127), - [PartitionKey] NVARCHAR(127), - [ETag] NVARCHAR(127), - [Attributes] VARBINARY(MAX) NULL, - [Data] VARBINARY(MAX) NULL -) \ No newline at end of file diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/ContactSync.OldApp.Publisher.csproj b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/ContactSync.OldApp.Publisher.csproj index d81c3be..2198531 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/ContactSync.OldApp.Publisher.csproj +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/ContactSync.OldApp.Publisher.csproj @@ -23,9 +23,9 @@ - + - + diff --git a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/Data/Generated/EventOutboxEnqueue.cs b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/Data/Generated/EventOutboxEnqueue.cs index 005b5cb..da13941 100644 --- a/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/Data/Generated/EventOutboxEnqueue.cs +++ b/samples/ContactSync/ContactSync.OldApp/ContactSync.OldApp.Publisher/Data/Generated/EventOutboxEnqueue.cs @@ -11,9 +11,6 @@ namespace ContactSync.OldApp.Publisher.Data; /// The . public sealed class EventOutboxEnqueue(IDatabase database, ILogger logger) : EventOutboxEnqueueBase(database, logger) { - /// - protected override string DbTvpTypeName => "[Outbox].[udtEventOutboxList]"; - /// protected override string EnqueueStoredProcedure => "[Outbox].[spEventOutboxEnqueue]"; } \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql index 4d816ad..296eca2 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchComplete.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [NTangle].[spContactBatchComplete] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchComplete] @BatchTrackingId BIGINT, - @VersionTrackingList AS [NTangle].[udtVersionTrackingList] READONLY + @VersionTrackingList AS NVARCHAR(MAX) AS BEGIN /* @@ -35,6 +35,8 @@ BEGIN DECLARE @Timestamp DATETIME2 SET @Timestamp = GETUTCDATE() + SELECT * into #versionTrackingList FROM OPENJSON(@VersionTrackingList) WITH ([Key] NVARCHAR(255) '$.key', [Hash] NVARCHAR(127) '$.hash') + UPDATE [_batch] SET [_batch].[IsComplete] = 1, [_batch].[CompletedDate] = @Timestamp @@ -42,7 +44,7 @@ BEGIN WHERE BatchTrackingId = @BatchTrackingId MERGE INTO [NTangle].[VersionTracking] WITH (HOLDLOCK) AS [_vt] - USING @VersionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Contact' AND [_vt].[Key] = [_list].[Key]) + USING #versionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Contact' AND [_vt].[Key] = [_list].[Key]) WHEN MATCHED AND EXISTS ( SELECT [_list].[Key], [_list].[Hash] EXCEPT diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql index 8433acd..219970e 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchExecute.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spContactBatchExecute] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchExecute] @MaxQuerySize BIGINT = 100, -- Maximum size of query to limit the number of changes to a manageable batch (performance vs failure trade-off). @ContinueWithDataLoss BIT = 0 -- Ignores data loss and continues; versus throwing an error. AS diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql index d75661f..4c43c5d 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spContactBatchReset.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spContactBatchReset] +CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchReset] AS BEGIN /* diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchComplete.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchComplete.sql index 2d96443..422b3f2 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchComplete.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchComplete.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [NTangle].[spCustomerBatchComplete] +CREATE OR ALTER PROCEDURE [NTangle].[spCustomerBatchComplete] @BatchTrackingId BIGINT, - @VersionTrackingList AS [NTangle].[udtVersionTrackingList] READONLY + @VersionTrackingList AS NVARCHAR(MAX) AS BEGIN /* @@ -35,6 +35,8 @@ BEGIN DECLARE @Timestamp DATETIME2 SET @Timestamp = GETUTCDATE() + SELECT * into #versionTrackingList FROM OPENJSON(@VersionTrackingList) WITH ([Key] NVARCHAR(255) '$.key', [Hash] NVARCHAR(127) '$.hash') + UPDATE [_batch] SET [_batch].[IsComplete] = 1, [_batch].[CompletedDate] = @Timestamp @@ -42,7 +44,7 @@ BEGIN WHERE BatchTrackingId = @BatchTrackingId MERGE INTO [NTangle].[VersionTracking] WITH (HOLDLOCK) AS [_vt] - USING @VersionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Customer' AND [_vt].[Key] = [_list].[Key]) + USING #versionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Customer' AND [_vt].[Key] = [_list].[Key]) WHEN MATCHED AND EXISTS ( SELECT [_list].[Key], [_list].[Hash] EXCEPT diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchExecute.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchExecute.sql index 1c1c3b2..dfd6121 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchExecute.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchExecute.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spCustomerBatchExecute] +CREATE OR ALTER PROCEDURE [NTangle].[spCustomerBatchExecute] @MaxQuerySize BIGINT = 100, -- Maximum size of query to limit the number of changes to a manageable batch (performance vs failure trade-off). @ContinueWithDataLoss BIT = 0 -- Ignores data loss and continues; versus throwing an error. AS diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchReset.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchReset.sql index f1751a8..93ddda6 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchReset.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spCustomerBatchReset.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spCustomerBatchReset] +CREATE OR ALTER PROCEDURE [NTangle].[spCustomerBatchReset] AS BEGIN /* diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spIdentifierMappingCreate.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spIdentifierMappingCreate.sql index 74c54a0..b8cbd82 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spIdentifierMappingCreate.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spIdentifierMappingCreate.sql @@ -1,5 +1,5 @@ -CREATE PROCEDURE [NTangle].[spIdentifierMappingCreate] - @IdentifierList AS [NTangle].[udtIdentifierMappingList] READONLY +CREATE OR ALTER PROCEDURE [NTangle].[spIdentifierMappingCreate] + @IdentifierList AS NVARCHAR(MAX) AS BEGIN /* @@ -12,18 +12,21 @@ BEGIN -- Wrap in a transaction. BEGIN TRANSACTION + -- Create a temporary table to hold the list of identifiers. + SELECT * INTO #identifierList FROM OPENJSON(@IdentifierList) WITH ([Schema] NVARCHAR(50) '$.schema', [Table] NVARCHAR(127) '$.table', [Key] NVARCHAR(255) '$.key', [GlobalId] NVARCHAR(127) '$.globalId') + -- Insert the Identifier only where a value does not already currently exist. INSERT INTO [NTangle].[IdentifierMapping] ([Schema], [Table], [Key], [GlobalId]) SELECT [n].[Schema], [n].[Table], [n].[Key], [n].[GlobalId] - FROM @IdentifierList AS [n] + FROM #identifierList AS [n] WHERE NOT EXISTS (SELECT 0 FROM [NTangle].[IdentifierMapping] AS [o] WHERE [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key]) -- Get the latest (current) values as some may already have had a global idenfifier created (i.e. not inserted above). SELECT [n].[Schema], [n].[Table], [n].[Key], [n].[GlobalId] FROM [NTangle].[IdentifierMapping] AS [n] - INNER JOIN @IdentifierList AS [o] ON [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key] + INNER JOIN #identifierList AS [o] ON [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key] -- Commit the transaction. COMMIT TRANSACTION diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchComplete.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchComplete.sql index e15fc5d..1769a36 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchComplete.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchComplete.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [NTangle].[spPostsBatchComplete] +CREATE OR ALTER PROCEDURE [NTangle].[spPostsBatchComplete] @BatchTrackingId BIGINT, - @VersionTrackingList AS [NTangle].[udtVersionTrackingList] READONLY + @VersionTrackingList AS NVARCHAR(MAX) AS BEGIN /* @@ -35,6 +35,8 @@ BEGIN DECLARE @Timestamp DATETIME2 SET @Timestamp = GETUTCDATE() + SELECT * into #versionTrackingList FROM OPENJSON(@VersionTrackingList) WITH ([Key] NVARCHAR(255) '$.key', [Hash] NVARCHAR(127) '$.hash') + UPDATE [_batch] SET [_batch].[IsComplete] = 1, [_batch].[CompletedDate] = @Timestamp @@ -42,7 +44,7 @@ BEGIN WHERE BatchTrackingId = @BatchTrackingId MERGE INTO [NTangle].[VersionTracking] WITH (HOLDLOCK) AS [_vt] - USING @VersionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Posts' AND [_vt].[Key] = [_list].[Key]) + USING #versionTrackingList AS [_list] ON ([_vt].[Schema] = N'Legacy' AND [_vt].[Table] = N'Posts' AND [_vt].[Key] = [_list].[Key]) WHEN MATCHED AND EXISTS ( SELECT [_list].[Key], [_list].[Hash] EXCEPT diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchExecute.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchExecute.sql index f48b7ca..cc31a66 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchExecute.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchExecute.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spPostsBatchExecute] +CREATE OR ALTER PROCEDURE [NTangle].[spPostsBatchExecute] @MaxQuerySize BIGINT = 100, -- Maximum size of query to limit the number of changes to a manageable batch (performance vs failure trade-off). @ContinueWithDataLoss BIT = 0 -- Ignores data loss and continues; versus throwing an error. AS diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchReset.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchReset.sql index 9e26f16..f0a0123 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchReset.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Stored Procedures/Generated/spPostsBatchReset.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [NTangle].[spPostsBatchReset] +CREATE OR ALTER PROCEDURE [NTangle].[spPostsBatchReset] AS BEGIN /* diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtIdentifierMappingList.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtIdentifierMappingList.sql deleted file mode 100644 index 4877db6..0000000 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtIdentifierMappingList.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TYPE [NTangle].[udtIdentifierMappingList] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [Schema] VARCHAR(50) NOT NULL, - [Table] VARCHAR(127) NOT NULL, - [Key] NVARCHAR(255) NOT NULL, - [GlobalId] NVARCHAR(127) NOT NULL -) \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql deleted file mode 100644 index 1a158f0..0000000 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/NTangle/Types/User-Defined Table Types/Generated/udtVersionTrackingList.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TYPE [NTangle].[udtVersionTrackingList] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [Key] NVARCHAR(255) NOT NULL, - [Hash] NVARCHAR(127) NOT NULL -) \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql index 3bc4462..530006f 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxDequeue.sql @@ -1,4 +1,4 @@ -CREATE PROCEDURE [Outbox].[spEventOutboxDequeue] +CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxDequeue] @MaxDequeueSize INT = 10, -- Maximum number of events to dequeue. @PartitionKey NVARCHAR(127) NULL = NULL, -- Partition key; null indicates all. @Destination NVARCHAR(127) NULL = NULL -- Destination (queue or topic); null indicates all. diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql index c6130f0..5858e9a 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql +++ b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Stored Procedures/Generated/spEventOutboxEnqueue.sql @@ -1,6 +1,6 @@ -CREATE PROCEDURE [Outbox].[spEventOutboxEnqueue] +CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxEnqueue] @SetEventsAsDequeued AS BIT = 0, - @EventList AS [Outbox].[udtEventOutboxList] READONLY + @EventList AS NVARCHAR(MAX) AS BEGIN /* @@ -24,6 +24,24 @@ BEGIN -- Enqueued outbox resultant identifier. DECLARE @enqueuedId TABLE([EventOutboxId] BIGINT) + -- Convert the JSON to a temporary table. + SELECT * INTO #eventList FROM OPENJSON(@EventList) WITH ( + [EventId] NVARCHAR(127) '$.EventId', + [EventDequeued] BIT '$.EventDequeued', + [Destination] NVARCHAR(127) '$.Destination', + [Subject] NVARCHAR(511) '$.Subject', + [Action] NVARCHAR(255) '$.Action', + [Type] NVARCHAR(1023) '$.Type', + [Source] NVARCHAR(1023) '$.Source', + [Timestamp] DATETIMEOFFSET '$.Timestamp', + [CorrelationId] NVARCHAR(127) '$.CorrelationId', + [Key] NVARCHAR(1023) '$.Key', + [TenantId] NVARCHAR(127) '$.TenantId', + [PartitionKey] NVARCHAR(127) '$.PartitionKey', + [ETag] NVARCHAR(127) '$.ETag', + [Attributes] VARBINARY(MAX) '$.Attributes', + [Data] VARBINARY(MAX) '$.Data') + -- Cursor output variables. DECLARE @eventId NVARCHAR(127), @eventDequeued BIT, @@ -43,7 +61,7 @@ BEGIN -- Declare, open, and fetch first event from cursor. DECLARE c CURSOR FORWARD_ONLY - FOR SELECT [EventId], [EventDequeued], [Destination], [Subject], [Action], [Type], [Source], [Timestamp], [CorrelationId], [Key], [TenantId], [PartitionKey], [ETag], [Attributes], [Data] FROM @EventList + FOR SELECT [EventId], [EventDequeued], [Destination], [Subject], [Action], [Type], [Source], [Timestamp], [CorrelationId], [Key], [TenantId], [PartitionKey], [ETag], [Attributes], [Data] FROM #eventList OPEN c FETCH NEXT FROM c INTO @eventId, @eventDequeued, @destination, @subject, @action, @type, @source, @timestamp, @correlationId, @key, @tenantId, @partitionKey, @etag, @attributes, @data diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql b/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql deleted file mode 100644 index 28537ef..0000000 --- a/samples/SqlServerDemo/SqlServerDemo.Database/Schema/Outbox/Types/User-Defined Table Types/Generated/udtEventOutboxList.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TYPE [Outbox].[udtEventOutboxList] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [EventId] NVARCHAR(127), - [EventDequeued] BIT NULL, -- Indicates whether to set the event as dequeued; i.e. already sent/processed. - [Destination] NVARCHAR(127) NULL, -- For example, queue/topic name. - [Subject] NVARCHAR(511) NULL, - [Action] NVARCHAR(255) NULL, - [Type] NVARCHAR(1023) NULL, - [Source] NVARCHAR(1023) NULL, - [Timestamp] DATETIMEOFFSET, - [CorrelationId] NVARCHAR(127), - [Key] NVARCHAR(1023) NULL, - [TenantId] NVARCHAR(127), - [PartitionKey] NVARCHAR(127), - [ETag] NVARCHAR(127), - [Attributes] VARBINARY(MAX) NULL, - [Data] VARBINARY(MAX) NULL -) \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Database/SqlServerDemo.Database.csproj b/samples/SqlServerDemo/SqlServerDemo.Database/SqlServerDemo.Database.csproj index f6caced..96dbc04 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Database/SqlServerDemo.Database.csproj +++ b/samples/SqlServerDemo/SqlServerDemo.Database/SqlServerDemo.Database.csproj @@ -17,7 +17,7 @@ - + \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Publisher/Data/Generated/EventOutboxEnqueue.cs b/samples/SqlServerDemo/SqlServerDemo.Publisher/Data/Generated/EventOutboxEnqueue.cs index 591508c..e05930f 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Publisher/Data/Generated/EventOutboxEnqueue.cs +++ b/samples/SqlServerDemo/SqlServerDemo.Publisher/Data/Generated/EventOutboxEnqueue.cs @@ -11,9 +11,6 @@ namespace SqlServerDemo.Publisher.Data; /// The . public sealed class EventOutboxEnqueue(IDatabase database, ILogger logger) : EventOutboxEnqueueBase(database, logger) { - /// - protected override string DbTvpTypeName => "[Outbox].[udtEventOutboxList]"; - /// protected override string EnqueueStoredProcedure => "[Outbox].[spEventOutboxEnqueue]"; } \ No newline at end of file diff --git a/samples/SqlServerDemo/SqlServerDemo.Publisher/SqlServerDemo.Publisher.csproj b/samples/SqlServerDemo/SqlServerDemo.Publisher/SqlServerDemo.Publisher.csproj index 06c0723..8b17dff 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Publisher/SqlServerDemo.Publisher.csproj +++ b/samples/SqlServerDemo/SqlServerDemo.Publisher/SqlServerDemo.Publisher.csproj @@ -23,7 +23,7 @@ - + diff --git a/samples/SqlServerDemo/SqlServerDemo.Test/SqlServerDemo.Test.csproj b/samples/SqlServerDemo/SqlServerDemo.Test/SqlServerDemo.Test.csproj index 115b026..de5e063 100644 --- a/samples/SqlServerDemo/SqlServerDemo.Test/SqlServerDemo.Test.csproj +++ b/samples/SqlServerDemo/SqlServerDemo.Test/SqlServerDemo.Test.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NTangle/Cdc/EntityOrchestrator.cs b/src/NTangle/Cdc/EntityOrchestrator.cs index d33bae4..61b8ed0 100644 --- a/src/NTangle/Cdc/EntityOrchestrator.cs +++ b/src/NTangle/Cdc/EntityOrchestrator.cs @@ -5,7 +5,6 @@ using CoreEx.Events; using CoreEx.Json; using Microsoft.Extensions.Logging; -using NTangle.Data; using System; using System.Collections.Generic; @@ -17,7 +16,7 @@ namespace NTangle.Cdc /// The root entity . /// The collection . /// The envelope . - /// The database and table-valued parameter mapper . + /// The database mapper . /// The . /// The name of the batch execute stored procedure. /// The name of the batch complete stored procedure. @@ -29,6 +28,6 @@ public abstract class EntityOrchestrator, new() where TEntityEnvelope : class, TEntity, IEntityEnvelope, new() - where TVersionTrackerMapper : IDatabaseMapper, IDatabaseTvp, new() + where TVersionTrackerMapper : IDatabaseMapper, new() { } } \ No newline at end of file diff --git a/src/NTangle/Cdc/EntityOrchestratorBase.cs b/src/NTangle/Cdc/EntityOrchestratorBase.cs index fc2acb8..80c19af 100644 --- a/src/NTangle/Cdc/EntityOrchestratorBase.cs +++ b/src/NTangle/Cdc/EntityOrchestratorBase.cs @@ -4,7 +4,6 @@ using CoreEx.Abstractions; using CoreEx.Configuration; using CoreEx.Database; -using CoreEx.Database.SqlServer; using CoreEx.Entities; using CoreEx.Events; using CoreEx.Json; @@ -27,12 +26,12 @@ namespace NTangle.Cdc /// The root entity . /// The collection . /// The envelope . - /// The database and table-valued parameter mapper . + /// The database mapper . public abstract class EntityOrchestratorBase : IEntityOrchestrator where TEntity : class, IEntity, new() where TEntityEnvelopeColl : List, new() where TEntityEnvelope : class, TEntity, IEntityEnvelope, new() - where TVersionTrackerMapper : IDatabaseMapper, IDatabaseTvp, new() + where TVersionTrackerMapper : IDatabaseMapper, new() { private const string MaxQuerySizeParamName = "MaxQuerySize"; private const string ContinueWithDataLossParamName = "ContinueWithDataLoss"; @@ -60,7 +59,6 @@ public abstract class EntityOrchestratorBase CompleteAsync(long batchTrackerId, L await Db.StoredProcedure(CompleteStoredProcedureName).Params(p => { p.AddParameter(BatchTrackingIdParamName, batchTrackerId); - p.AddTableValuedParameter(VersionTrackingListParamName, _versionTrackerMapper.CreateTableValuedParameter(versionTracking)); + p.AddJsonParameter(VersionTrackingListParamName, versionTracking); }).SelectMultiSetAsync(MultiSetArgs.Create(msa), cancellationToken).ConfigureAwait(false); } catch (BusinessException bex) // The known (converted) SQL Exception. diff --git a/src/NTangle/Cdc/EntityOrchestratorT.cs b/src/NTangle/Cdc/EntityOrchestratorT.cs index 535df5f..4d8cacf 100644 --- a/src/NTangle/Cdc/EntityOrchestratorT.cs +++ b/src/NTangle/Cdc/EntityOrchestratorT.cs @@ -23,13 +23,13 @@ namespace NTangle.Cdc /// The root entity . /// The collection . /// The envelope . - /// The database and table-valued parameter mapper . + /// The database mapper . /// The global identifier . public abstract class EntityOrchestrator : EntityOrchestratorBase, IEntityOrchestrator where TEntity : class, IEntity, new() where TEntityEnvelopeColl : List, new() where TEntityEnvelope : class, TEntity, IEntityEnvelope, new() - where TVersionTrackerMapper : IDatabaseMapper, IDatabaseTvp, new() + where TVersionTrackerMapper : IDatabaseMapper, new() { private const string IdentifierListParamName = "IdentifierList"; @@ -86,10 +86,9 @@ protected async Task AssignIdentityMappingAsync(TEntityEnvelopeColl coll, Cancel // There could be multiple references to same Schema/Table/Key; these need to filtered out; i.e. send only a distinct list. var imcd = new Dictionary<(string?, string?, string?), IdentifierMapping>(); vimc.ForEach(item => imcd.TryAdd((item.Schema, item.Table, item.Key), item)); - var tvp = IdentifierMappingMapper!.CreateTableValuedParameter(imcd.Values); // Execute the stored procedure and get the updated list. - var imc = await Db.StoredProcedure(IdentifierMappingStoredProcedureName!).Params(p => p.AddTableValuedParameter(IdentifierListParamName, tvp)).SelectQueryAsync(IdentifierMappingMapper, cancellationToken).ConfigureAwait(false); + var imc = await Db.StoredProcedure(IdentifierMappingStoredProcedureName!).Params(p => p.AddJsonParameter(IdentifierListParamName, imcd.Values)).SelectQueryAsync(IdentifierMappingMapper, cancellationToken).ConfigureAwait(false); if (imc.Count() != imcd.Count) throw new InvalidOperationException($"Stored procedure '{IdentifierMappingStoredProcedureName}' returned an unexpected result."); diff --git a/src/NTangle/Data/IDatabaseTvp.cs b/src/NTangle/Data/IDatabaseTvp.cs deleted file mode 100644 index b470173..0000000 --- a/src/NTangle/Data/IDatabaseTvp.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle - -using CoreEx.Database.SqlServer; -using System; -using System.Collections.Generic; - -namespace NTangle.Data -{ - /// - /// Defines the capability. - /// - /// The underlying list . - public interface IDatabaseTvp - { - /// - /// Creates a for the . - /// - /// The list of to include as the . - /// The Table-Valued Parameter. - TableValuedParameter CreateTableValuedParameter(IEnumerable list); - } -} \ No newline at end of file diff --git a/src/NTangle/Data/IIdentifierMappingTvp.cs b/src/NTangle/Data/IIdentifierMappingTvp.cs deleted file mode 100644 index 8269fff..0000000 --- a/src/NTangle/Data/IIdentifierMappingTvp.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle - -using CoreEx.Database.SqlServer; -using NTangle.Cdc; -using System.Collections.Generic; - -namespace NTangle.Data -{ - /// - /// Enables the capability. - /// - /// The global identifier . - public interface IIdentifierMappingTvp - { - /// - /// Creates a for the . - /// - /// The list. - /// The Table-Valued Parameter. - TableValuedParameter CreateTableValuedParameter(IEnumerable> list); - } -} \ No newline at end of file diff --git a/src/NTangle/Data/IdentifierMappingMapperBase.cs b/src/NTangle/Data/IdentifierMappingMapperBase.cs index 4abf398..2ea429c 100644 --- a/src/NTangle/Data/IdentifierMappingMapperBase.cs +++ b/src/NTangle/Data/IdentifierMappingMapperBase.cs @@ -6,8 +6,6 @@ using CoreEx.Mapping; using NTangle.Cdc; using System; -using System.Collections.Generic; -using System.Data; namespace NTangle.Data { @@ -16,7 +14,7 @@ namespace NTangle.Data /// /// The global identifier . /// The database type name for the . - public abstract class IdentifierMappingMapperBase(string dbTypeName) : IDatabaseMapper>, IIdentifierMappingTvp + public abstract class IdentifierMappingMapperBase(string dbTypeName) : IDatabaseMapper> { /// @@ -33,24 +31,6 @@ public abstract class IdentifierMappingMapperBase(string dbTypeName) : IDatab GlobalId = record.GetValue(nameof(IdentifierMapping.GlobalId)) }; - /// - public TableValuedParameter CreateTableValuedParameter(IEnumerable> list) - { - var dt = new DataTable(); - dt.Columns.Add(nameof(IdentifierMapping.Schema), typeof(string)); - dt.Columns.Add(nameof(IdentifierMapping.Table), typeof(string)); - dt.Columns.Add(nameof(IdentifierMapping.Key), typeof(string)); - dt.Columns.Add(nameof(IdentifierMapping.GlobalId), typeof(T)); - - var tvp = new TableValuedParameter(DbTypeName, dt); - foreach (var item in list) - { - tvp.AddRow(item.Schema, item.Table, item.Key, item.GlobalId); - } - - return tvp; - } - /// void IDatabaseMapper>.MapToDb(IdentifierMapping? value, DatabaseParameterCollection parameters, OperationTypes operationType) => throw new NotImplementedException(); } diff --git a/src/NTangle/Data/VersionTrackerMapperBase.cs b/src/NTangle/Data/VersionTrackerMapperBase.cs index a07349a..1e4eda8 100644 --- a/src/NTangle/Data/VersionTrackerMapperBase.cs +++ b/src/NTangle/Data/VersionTrackerMapperBase.cs @@ -6,8 +6,6 @@ using CoreEx.Mapping; using NTangle.Cdc; using System; -using System.Collections.Generic; -using System.Data; namespace NTangle.Data { @@ -15,7 +13,7 @@ namespace NTangle.Data /// Represents the database mapper. /// /// The database type name for the . - public abstract class VersionTrackingMapperBase(string dbTypeName) : IDatabaseMapper, IDatabaseTvp + public abstract class VersionTrackingMapperBase(string dbTypeName) : IDatabaseMapper { /// /// Gets the database type name for the . @@ -29,22 +27,6 @@ public abstract class VersionTrackingMapperBase(string dbTypeName) : IDatabaseMa Hash = record.GetValue(nameof(VersionTracker.Hash)) }; - /// - public TableValuedParameter CreateTableValuedParameter(IEnumerable list) - { - var dt = new DataTable(); - dt.Columns.Add(nameof(VersionTracker.Key), typeof(string)); - dt.Columns.Add(nameof(VersionTracker.Hash), typeof(string)); - - var tvp = new TableValuedParameter(DbTypeName, dt); - foreach (var item in list) - { - tvp.AddRow(item.Key, item.Hash); - } - - return tvp; - } - /// void IDatabaseMapper.MapToDb(VersionTracker? value, DatabaseParameterCollection parameters, OperationTypes operationType) => throw new NotImplementedException(); } diff --git a/src/NTangle/NTangle.csproj b/src/NTangle/NTangle.csproj index d57684c..a49cce5 100644 --- a/src/NTangle/NTangle.csproj +++ b/src/NTangle/NTangle.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/NTangle.Test/TemplateTest.cs b/tests/NTangle.Test/TemplateTest.cs index 4fef7ec..b9ea2b6 100644 --- a/tests/NTangle.Test/TemplateTest.cs +++ b/tests/NTangle.Test/TemplateTest.cs @@ -106,7 +106,7 @@ public static void OneTimeSetUp() } // Build and package (nuget) - only local package, no deployment. - Assert.GreaterOrEqual(0, ExecuteCommand("powershell", $"{Path.Combine(_rootDir.FullName, "nuget-publish.ps1")} -configuration 'Debug' -IncludeSymbols -IncludeSource").exitCode, "nuget publish"); + Assert.GreaterOrEqual(0, ExecuteCommand("powershell", $"{Path.Combine(_rootDir.FullName, "nuget-publish.ps1")} -configuration 'Release' -IncludeSymbols -IncludeSource").exitCode, "nuget publish"); // Uninstall any previous nTangle templates (failure is ok here) ExecuteCommand("dotnet", "new uninstall NTangle.Template"); diff --git a/tools/NTangle.CodeGen/NTangle.CodeGen.csproj b/tools/NTangle.CodeGen/NTangle.CodeGen.csproj index 44bf4b8..e7c1701 100644 --- a/tools/NTangle.CodeGen/NTangle.CodeGen.csproj +++ b/tools/NTangle.CodeGen/NTangle.CodeGen.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/tools/NTangle.CodeGen/Scripts/SqlServerDatabase.yaml b/tools/NTangle.CodeGen/Scripts/SqlServerDatabase.yaml index c1c5f7b..e7329b6 100644 --- a/tools/NTangle.CodeGen/Scripts/SqlServerDatabase.yaml +++ b/tools/NTangle.CodeGen/Scripts/SqlServerDatabase.yaml @@ -3,12 +3,9 @@ generators: - { type: 'NTangle.CodeGen.Generators.TableCodeGenerator, NTangle.CodeGen', template: 'SpExecuteBatch_sql', file: '{{ExecuteStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Stored Procedures/Generated', text: 'TableCodeGenerator: Database/Schema/Xxx/Stored Procedures' } - { type: 'NTangle.CodeGen.Generators.TableCodeGenerator, NTangle.CodeGen', template: 'SpCompleteBatch_sql', file: '{{CompleteStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Stored Procedures/Generated', text: 'TableCodeGenerator: Database/Schema/Xxx/Stored Procedures' } - { type: 'NTangle.CodeGen.Generators.TableCodeGenerator, NTangle.CodeGen', template: 'SpResetBatch_sql', file: '{{ResetStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Stored Procedures/Generated', text: 'TableCodeGenerator: Database/Schema/Xxx/Stored Procedures' } -- { type: 'NTangle.CodeGen.Generators.RootCodeGenerator, NTangle.CodeGen', template: 'UdtVersionTracking_sql', file: 'udt{{VersionTrackingTable}}List.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Types/User-Defined Table Types/Generated', text: 'RootCodeGenerator: Database/Schema/Xxx/Types/User-Defined Table Types' } - { type: 'NTangle.CodeGen.Generators.IdentifierMappingCodeGenerator, NTangle.CodeGen', template: 'SpIdentifierMappingCreate_sql', file: '{{IdentifierMappingStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Stored Procedures/Generated', text: 'IdentifierMappingCodeGenerator: Database/Schema/Xxx/Types/Stored Procedures' } -- { type: 'NTangle.CodeGen.Generators.IdentifierMappingCodeGenerator, NTangle.CodeGen', template: 'UdtIdentifierMapping_sql', file: 'udt{{IdentifierMappingTable}}List.sql', directory: '{{Root.PathDatabaseSchema}}/{{CdcSchema}}/Types/User-Defined Table Types/Generated', text: 'IdentifierMappingCodeGenerator: Database/Schema/Xxx/Types/User-Defined Table Types' } - { type: 'NTangle.CodeGen.Generators.OutboxCodeGenerator, NTangle.CodeGen', template: 'SpEventOutboxEnqueue_sql', file: '{{OutboxEnqueueStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{OutboxSchema}}/Stored Procedures/Generated', text: 'OutboxCodeGenerator: Database/Schema/Xxx/Types/Stored Procedures' } - { type: 'NTangle.CodeGen.Generators.OutboxCodeGenerator, NTangle.CodeGen', template: 'SpEventOutboxDequeue_sql', file: '{{OutboxDequeueStoredProcedure}}.sql', directory: '{{Root.PathDatabaseSchema}}/{{OutboxSchema}}/Stored Procedures/Generated', text: 'OutboxCodeGenerator: Database/Schema/Xxx/Types/Stored Procedures' } -- { type: 'NTangle.CodeGen.Generators.OutboxCodeGenerator, NTangle.CodeGen', template: 'UdtEventOutbox_sql', file: 'udt{{OutboxTable}}List.sql', directory: '{{Root.PathDatabaseSchema}}/{{OutboxSchema}}/Types/User-Defined Table Types/Generated', text: 'OutboxCodeGenerator: Database/Schema/Xxx/Types/User-Defined Table Types' } - { type: 'NTangle.CodeGen.Generators.RootCodeGenerator, NTangle.CodeGen', template: 'VersionTrackingMapper_cs', file: 'VersionTrackingMapper.cs', directory: '{{Root.PathDotNetPublisher}}/Data/Generated', text: 'RootCodeGenerator: Publisher/Data' } - { type: 'NTangle.CodeGen.Generators.IdentifierMappingCodeGenerator, NTangle.CodeGen', template: 'IdentifierMappingMapper_cs', file: 'IdentifierMappingMapper.cs', directory: '{{Root.PathDotNetPublisher}}/Data/Generated', text: 'IdentifierMappingCodeGenerator: Publisher/Data' } - { type: 'NTangle.CodeGen.Generators.TableCodeGenerator, NTangle.CodeGen', template: 'Entity_cs', file: '{{Model}}Cdc.cs', directory: '{{Root.PathDotNetPublisher}}/Entities/Generated', text: 'TableCodeGenerator: Publisher/Entities' } diff --git a/tools/NTangle.CodeGen/Templates/SpCompleteBatch_sql.hbs b/tools/NTangle.CodeGen/Templates/SpCompleteBatch_sql.hbs index 76ee532..a604b5a 100644 --- a/tools/NTangle.CodeGen/Templates/SpCompleteBatch_sql.hbs +++ b/tools/NTangle.CodeGen/Templates/SpCompleteBatch_sql.hbs @@ -1,7 +1,7 @@ {{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE PROCEDURE [{{CdcSchema}}].[{{CompleteStoredProcedure}}] +CREATE OR ALTER PROCEDURE [{{CdcSchema}}].[{{CompleteStoredProcedure}}] @BatchTrackingId BIGINT, - @VersionTrackingList AS [{{Root.CdcSchema}}].[udt{{Root.VersionTrackingTable}}List] READONLY + @VersionTrackingList AS NVARCHAR(MAX) AS BEGIN /* @@ -36,6 +36,8 @@ BEGIN DECLARE @Timestamp DATETIME2 SET @Timestamp = GETUTCDATE() + SELECT * into #versionTrackingList FROM OPENJSON(@VersionTrackingList) WITH ([Key] NVARCHAR(255) '$.key', [Hash] NVARCHAR(127) '$.hash') + UPDATE [_batch] SET [_batch].[IsComplete] = 1, [_batch].[CompletedDate] = @Timestamp @@ -43,7 +45,7 @@ BEGIN WHERE BatchTrackingId = @BatchTrackingId MERGE INTO [{{Root.CdcSchema}}].[{{Root.VersionTrackingTable}}] WITH (HOLDLOCK) AS [_vt] - USING @VersionTrackingList AS [_list] ON ([_vt].[Schema] = N'{{Schema}}' AND [_vt].[Table] = N'{{Name}}' AND [_vt].[Key] = [_list].[Key]) + USING #versionTrackingList AS [_list] ON ([_vt].[Schema] = N'{{Schema}}' AND [_vt].[Table] = N'{{Name}}' AND [_vt].[Key] = [_list].[Key]) WHEN MATCHED AND EXISTS ( SELECT [_list].[Key], [_list].[Hash] EXCEPT diff --git a/tools/NTangle.CodeGen/Templates/SpExecuteBatch_sql.hbs b/tools/NTangle.CodeGen/Templates/SpExecuteBatch_sql.hbs index 51f4a09..df80908 100644 --- a/tools/NTangle.CodeGen/Templates/SpExecuteBatch_sql.hbs +++ b/tools/NTangle.CodeGen/Templates/SpExecuteBatch_sql.hbs @@ -1,5 +1,5 @@ {{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE PROCEDURE [{{CdcSchema}}].[{{ExecuteStoredProcedure}}] +CREATE OR ALTER PROCEDURE [{{CdcSchema}}].[{{ExecuteStoredProcedure}}] @MaxQuerySize BIGINT = 100, -- Maximum size of query to limit the number of changes to a manageable batch (performance vs failure trade-off). @ContinueWithDataLoss BIT = 0 -- Ignores data loss and continues; versus throwing an error. AS diff --git a/tools/NTangle.CodeGen/Templates/SpIdentifierMappingCreate_sql.hbs b/tools/NTangle.CodeGen/Templates/SpIdentifierMappingCreate_sql.hbs index d48f888..7b54c29 100644 --- a/tools/NTangle.CodeGen/Templates/SpIdentifierMappingCreate_sql.hbs +++ b/tools/NTangle.CodeGen/Templates/SpIdentifierMappingCreate_sql.hbs @@ -1,6 +1,6 @@ {{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE PROCEDURE [{{CdcSchema}}].[{{IdentifierMappingStoredProcedure}}] - @IdentifierList AS [{{CdcSchema}}].[udt{{IdentifierMappingTable}}List] READONLY +CREATE OR ALTER PROCEDURE [{{CdcSchema}}].[{{IdentifierMappingStoredProcedure}}] + @IdentifierList AS NVARCHAR(MAX) AS BEGIN /* @@ -13,18 +13,21 @@ BEGIN -- Wrap in a transaction. BEGIN TRANSACTION + -- Create a temporary table to hold the list of identifiers. + SELECT * INTO #identifierList FROM OPENJSON(@IdentifierList) WITH ([Schema] NVARCHAR(50) '$.schema', [Table] NVARCHAR(127) '$.table', [Key] NVARCHAR(255) '$.key', [GlobalId] {{IdentifierMappingSqlType}} '$.globalId') + -- Insert the Identifier only where a value does not already currently exist. INSERT INTO [{{CdcSchema}}].[{{IdentifierMappingTable}}] ([Schema], [Table], [Key], [GlobalId]) SELECT [n].[Schema], [n].[Table], [n].[Key], [n].[GlobalId] - FROM @IdentifierList AS [n] + FROM #identifierList AS [n] WHERE NOT EXISTS (SELECT 0 FROM [{{CdcSchema}}].[{{IdentifierMappingTable}}] AS [o] WHERE [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key]) -- Get the latest (current) values as some may already have had a global idenfifier created (i.e. not inserted above). SELECT [n].[Schema], [n].[Table], [n].[Key], [n].[GlobalId] FROM [{{CdcSchema}}].[{{IdentifierMappingTable}}] AS [n] - INNER JOIN @IdentifierList AS [o] ON [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key] + INNER JOIN #identifierList AS [o] ON [n].[Schema] = [o].[Schema] AND [n].[Table] = [o].[Table] AND [n].[Key] = [o].[Key] -- Commit the transaction. COMMIT TRANSACTION diff --git a/tools/NTangle.CodeGen/Templates/SpResetBatch_sql.hbs b/tools/NTangle.CodeGen/Templates/SpResetBatch_sql.hbs index 8e6745a..87efa9f 100644 --- a/tools/NTangle.CodeGen/Templates/SpResetBatch_sql.hbs +++ b/tools/NTangle.CodeGen/Templates/SpResetBatch_sql.hbs @@ -1,5 +1,5 @@ {{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE PROCEDURE [{{CdcSchema}}].[{{ResetStoredProcedure}}] +CREATE OR ALTER PROCEDURE [{{CdcSchema}}].[{{ResetStoredProcedure}}] AS BEGIN /* diff --git a/tools/NTangle.CodeGen/Templates/UdtIdentifierMapping_sql.hbs b/tools/NTangle.CodeGen/Templates/UdtIdentifierMapping_sql.hbs deleted file mode 100644 index 29181e0..0000000 --- a/tools/NTangle.CodeGen/Templates/UdtIdentifierMapping_sql.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE TYPE [{{CdcSchema}}].[udt{{IdentifierMappingTable}}List] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [Schema] VARCHAR(50) NOT NULL, - [Table] VARCHAR(127) NOT NULL, - [Key] NVARCHAR(255) NOT NULL, - [GlobalId] {{IdentifierMappingSqlType}} NOT NULL -) \ No newline at end of file diff --git a/tools/NTangle.CodeGen/Templates/UdtVersionTracking_sql.hbs b/tools/NTangle.CodeGen/Templates/UdtVersionTracking_sql.hbs deleted file mode 100644 index 0812a67..0000000 --- a/tools/NTangle.CodeGen/Templates/UdtVersionTracking_sql.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{! Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/NTangle }} -CREATE TYPE [{{CdcSchema}}].[udt{{VersionTrackingTable}}List] AS TABLE ( - /* - * This is automatically generated; any changes will be lost. - */ - - [Key] NVARCHAR(255) NOT NULL, - [Hash] NVARCHAR(127) NOT NULL -) \ No newline at end of file diff --git a/tools/NTangle.Template/content/AppName.CodeGen/AppName.CodeGen.csproj b/tools/NTangle.Template/content/AppName.CodeGen/AppName.CodeGen.csproj index 66293ec..790a56e 100644 --- a/tools/NTangle.Template/content/AppName.CodeGen/AppName.CodeGen.csproj +++ b/tools/NTangle.Template/content/AppName.CodeGen/AppName.CodeGen.csproj @@ -5,7 +5,7 @@ enable - + diff --git a/tools/NTangle.Template/content/AppName.Database/AppName.Database.csproj b/tools/NTangle.Template/content/AppName.Database/AppName.Database.csproj index 436e712..ac8ebaa 100644 --- a/tools/NTangle.Template/content/AppName.Database/AppName.Database.csproj +++ b/tools/NTangle.Template/content/AppName.Database/AppName.Database.csproj @@ -7,7 +7,7 @@ - + diff --git a/tools/NTangle.Template/content/AppName.Publisher/AppName.Publisher.csproj b/tools/NTangle.Template/content/AppName.Publisher/AppName.Publisher.csproj index f933fcc..4c10b1d 100644 --- a/tools/NTangle.Template/content/AppName.Publisher/AppName.Publisher.csproj +++ b/tools/NTangle.Template/content/AppName.Publisher/AppName.Publisher.csproj @@ -8,7 +8,7 @@ - + @@ -29,7 +29,7 @@ - +