Skip to content

Commit d8a32d7

Browse files
committed
- Fixed a Bug where Identity Column Value was not correctly synced after Materialization Process is completing.
- Added new Helper API to quickly Sync the Identity column value with the current MAX Id value of the column (ensuring it's valid after populating additional data); This is useful if you override Identity values for a full Table refresh, but then want to later insert data into the table.
1 parent a0785d8 commit d8a32d7

File tree

9 files changed

+308
-15
lines changed

9 files changed

+308
-15
lines changed

NetStandard.SqlBulkHelpers/MaterializedData/MaterializeDataContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ public async Task FinishMaterializeDataProcessAsync()
194194
var liveTableDefinition = materializationTableInfo.LiveTableDefinition;
195195
var otherReferencingFKeyConstraints = liveTableDefinition.ReferencingForeignKeyConstraints.Where(rc => !allLiveTableFKeyConstraintLookup.Contains(rc.ToString()));
196196

197+
//FIRST handle Identity values to minimize risk of Changes.
198+
//If Enabled then we Automatically Sync the Identity Value of the newly switched Live table with the Max ID so that is is guaranteed to be a valid value
199+
// otherwise (if disabled) then the Identity may not change when Materializing data...
200+
if (BulkHelpersConfig.IsCloningIdentitySeedValueEnabled && liveTableDefinition.IdentityColumn != null)
201+
switchScriptBuilder.SyncIdentitySeedValue(materializationTableInfo.LoadingTable, materializationTableInfo.LiveTable);
202+
197203
switchScriptBuilder
198204
//HERE We enable all FKey Constraints and Trigger a Re-check to ensure Data Integrity (unless EXPLICITLY Overridden)!
199205
//NOTE: This is critical because the FKeys were added with NOCHECK status above so that we could safely switch
@@ -205,6 +211,7 @@ public async Task FinishMaterializeDataProcessAsync()
205211
//Finally cleanup the Loading and Discarding tables...
206212
.DropTable(materializationTableInfo.LoadingTable)
207213
.DropTable(materializationTableInfo.DiscardingTable);
214+
208215
}
209216

210217
//var timeoutConvertedToMinutes = Math.Max(1, (int)Math.Ceiling((decimal)BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds / 60));

NetStandard.SqlBulkHelpers/MaterializedData/MaterializeDataHelper.cs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ public async Task<TableNameTerm[]> ClearTablesAsync(SqlTransaction sqlTransactio
320320
lock (tablesToMaterializeAsEmpty) tablesToMaterializeAsEmpty.Add(tableNameTerm);
321321
else
322322
lock (tablesToProcessWithTruncation) tablesToProcessWithTruncation.Add(tableNameTerm);
323-
};
323+
}
324324

325325
if (tablesToMaterializeAsEmpty.Any())
326326
{
@@ -405,7 +405,7 @@ public long GetTableCurrentIdentityValue(SqlConnection sqlConnection, SqlTransac
405405
private SqlCommand CreateGetTableIdentityValueSqlCommand(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
406406
{
407407
var fullyQualifiedTableName = GetMappedTableNameTerm(tableNameOverride).FullyQualifiedTableName;
408-
var sqlCmd = new SqlCommand("SELECT CURRENT_IDENTITY_VALUE = IDENT_CURRENT(@TableName)", sqlConnection, sqlTransaction);
408+
var sqlCmd = new SqlCommand("SELECT CURRENT_IDENTITY_VALUE = IDENT_CURRENT(@TableName);", sqlConnection, sqlTransaction);
409409
sqlCmd.CommandTimeout = BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds;
410410
sqlCmd.Parameters.Add("@TableName", SqlDbType.NVarChar).Value = fullyQualifiedTableName;
411411
return sqlCmd;
@@ -453,6 +453,55 @@ private SqlCommand CreateReSeedTableIdentityValueSqlCommand(SqlConnection sqlCon
453453
return sqlCmd;
454454
}
455455

456+
/// <summary>
457+
/// Sets / Re-seeds the Current Identity Value with the current MAX() value of the Identity Column for the specified Table.
458+
/// </summary>
459+
/// <param name="sqlConnection"></param>
460+
/// <param name="sqlTransaction"></param>
461+
/// <param name="tableNameOverride"></param>
462+
/// <returns></returns>
463+
public async Task<long> ReSeedTableIdentityValueWithMaxIdAsync(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
464+
{
465+
var tableDef = await GetTableSchemaDefinitionInternalAsync(TableSchemaDetailLevel.BasicDetails, sqlConnection, sqlTransaction: sqlTransaction, tableNameOverride).ConfigureAwait(false);
466+
using (var sqlCmd = CreateReSeedTableIdentityValueToSyncWithMaxIdSqlCommand(sqlConnection, tableDef, sqlTransaction))
467+
{
468+
var result = await sqlCmd.ExecuteScalarAsync().ConfigureAwait(false);
469+
return Convert.ToInt64(result);
470+
}
471+
}
472+
473+
/// <summary>
474+
/// Sets / Re-seeds the Current Identity Value with the current MAX() value of the Identity Column for the specified Table.
475+
/// </summary>
476+
/// <param name="sqlConnection"></param>
477+
/// <param name="sqlTransaction"></param>
478+
/// <param name="tableNameOverride"></param>
479+
/// <returns></returns>
480+
public long ReSeedTableIdentityWithMaxId(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
481+
{
482+
var tableDef = GetTableSchemaDefinitionInternal(TableSchemaDetailLevel.BasicDetails, sqlConnection, sqlTransaction: sqlTransaction, tableNameOverride);
483+
using (var sqlCmd = CreateReSeedTableIdentityValueToSyncWithMaxIdSqlCommand(sqlConnection, tableDef, sqlTransaction))
484+
{
485+
var result = sqlCmd.ExecuteScalar();
486+
return Convert.ToInt64(result);
487+
}
488+
}
489+
490+
private SqlCommand CreateReSeedTableIdentityValueToSyncWithMaxIdSqlCommand(SqlConnection sqlConnection, SqlBulkHelpersTableDefinition tableDef, SqlTransaction sqlTransaction = null)
491+
{
492+
var tableNameTerm = tableDef.TableNameTerm;
493+
var maxIdVariable = $"@MaxId_{tableNameTerm.TableNameVariable}";
494+
495+
var sqlCmd = new SqlCommand($@"
496+
DECLARE {maxIdVariable} BIGINT = (SELECT MAX({tableDef.IdentityColumn.ColumnName.QualifySqlTerm()}) FROM {tableNameTerm.FullyQualifiedTableName});
497+
DBCC CHECKIDENT(@TableName, RESEED, {maxIdVariable});
498+
SELECT CURRENT_IDENTITY_VALUE = IDENT_CURRENT(@TableName);
499+
", sqlConnection, sqlTransaction);
500+
501+
sqlCmd.CommandTimeout = BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds;
502+
sqlCmd.Parameters.Add("@TableName", SqlDbType.NVarChar).Value = tableNameTerm.FullyQualifiedTableName;
503+
return sqlCmd;
504+
}
456505

457506
#endregion
458507

NetStandard.SqlBulkHelpers/MaterializedData/MaterializedDataScriptBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public MaterializedDataScriptBuilder SyncIdentitySeedValue(TableNameTerm sourceT
164164

165165
//Variables will be written out at the Top of the Script so they are initialized quickly and values used are consistent for the entire script...
166166
var currentIdentityVariable = $"@CurrentIdentity_{sourceTable.TableNameVariable}";
167-
Variables.TryAdd(currentIdentityVariable, $"DECLARE {currentIdentityVariable} int = IDENT_CURRENT('{sourceTable.FullyQualifiedTableName}');");
167+
Variables.TryAdd(currentIdentityVariable, $"DECLARE {currentIdentityVariable} BIGINT = IDENT_CURRENT('{sourceTable.FullyQualifiedTableName}');");
168168

169169
ScriptBuilder.Append($@"
170170
--Syncs the Identity Seed value of the Target Table with the current value of the Source Table (captured into Variable at top of script)

NetStandard.SqlBulkHelpers/MaterializedData/MaterializedDataSqlClientExtensionsApi.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ private static async Task ReSeedTableIdentityValueInternalAsync<T>(
285285

286286

287287
public static void ReSeedTableIdentityValue(this SqlTransaction sqlTransaction, string tableName, long newIdentitySeedValue, ISqlBulkHelpersConfig bulkHelpersConfig = null)
288-
=> ReSeedTableIdentityValueAsync<ISkipMappingLookup>(sqlTransaction, newIdentitySeedValue, tableName, bulkHelpersConfig);
288+
=> ReSeedTableIdentityValue<ISkipMappingLookup>(sqlTransaction, newIdentitySeedValue, tableName, bulkHelpersConfig);
289289

290290
public static void ReSeedTableIdentityValue<T>(this SqlTransaction sqlTransaction, long newIdentitySeedValue, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
291291
where T : class
@@ -309,6 +309,57 @@ private static void ReSeedTableIdentityValueInternal<T>(
309309
new MaterializeDataHelper<T>(bulkHelpersConfig).ReSeedTableIdentityValue(sqlConnection, newIdentitySeedValue, sqlTransaction, tableNameOverride);
310310
}
311311

312+
public static Task<long> ReSeedTableIdentityValueWithMaxIdAsync(this SqlTransaction sqlTransaction, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
313+
=> ReSeedTableIdentityValueWithMaxIdAsync<ISkipMappingLookup>(sqlTransaction, tableName, bulkHelpersConfig);
314+
315+
public static Task<long> ReSeedTableIdentityValueWithMaxIdAsync<T>(this SqlTransaction sqlTransaction, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
316+
where T : class
317+
{
318+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
319+
return ReSeedTableIdentityValueWithMaxIdInternalAsync<T>(sqlTransaction.Connection, tableNameOverride, sqlTransaction, bulkHelpersConfig);
320+
}
321+
322+
public static Task<long> ReSeedTableIdentityValueWithMaxIdAsync(this SqlConnection sqlConnection, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
323+
=> ReSeedTableIdentityValueWithMaxIdInternalAsync<ISkipMappingLookup>(sqlConnection, tableName, bulkHelpersConfig: bulkHelpersConfig);
324+
325+
private static async Task<long> ReSeedTableIdentityValueWithMaxIdInternalAsync<T>(
326+
this SqlConnection sqlConnection,
327+
string tableNameOverride = null,
328+
SqlTransaction sqlTransaction = null,
329+
ISqlBulkHelpersConfig bulkHelpersConfig = null
330+
) where T : class
331+
{
332+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
333+
334+
return await new MaterializeDataHelper<T>(bulkHelpersConfig)
335+
.ReSeedTableIdentityValueWithMaxIdAsync(sqlConnection, sqlTransaction, tableNameOverride)
336+
.ConfigureAwait(false);
337+
}
338+
339+
public static long ReSeedTableIdentityValueWithMaxId(this SqlTransaction sqlTransaction, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
340+
=> ReSeedTableIdentityValueWithMaxId<ISkipMappingLookup>(sqlTransaction, tableName, bulkHelpersConfig);
341+
342+
public static long ReSeedTableIdentityValueWithMaxId<T>(this SqlTransaction sqlTransaction, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
343+
where T : class
344+
{
345+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
346+
return ReSeedTableIdentityValueWithMaxIdInternal<T>(sqlTransaction.Connection, tableNameOverride, sqlTransaction, bulkHelpersConfig);
347+
}
348+
349+
public static long ReSeedTableIdentityValueWithMaxId(this SqlConnection sqlConnection, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
350+
=> ReSeedTableIdentityValueWithMaxIdInternal<ISkipMappingLookup>(sqlConnection, tableName, bulkHelpersConfig: bulkHelpersConfig);
351+
352+
private static long ReSeedTableIdentityValueWithMaxIdInternal<T>(
353+
this SqlConnection sqlConnection,
354+
string tableNameOverride = null,
355+
SqlTransaction sqlTransaction = null,
356+
ISqlBulkHelpersConfig bulkHelpersConfig = null
357+
) where T : class
358+
{
359+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
360+
return new MaterializeDataHelper<T>(bulkHelpersConfig).ReSeedTableIdentityWithMaxId(sqlConnection, sqlTransaction, tableNameOverride);
361+
}
362+
312363
#endregion
313364

314365
#region Full Text Index (cannot be altered within a Transaction)...

NetStandard.SqlBulkHelpers/NetStandard.SqlBulkHelpers.csproj

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,26 @@
88
<PackageLicenseExpression>MIT</PackageLicenseExpression>
99
<Authors>BBernard / CajunCoding</Authors>
1010
<Company>CajunCoding</Company>
11+
<Version>2.2.3</Version>
1112
<PackageProjectUrl>https://github.com/cajuncoding/SqlBulkHelpers</PackageProjectUrl>
1213
<RepositoryUrl>https://github.com/cajuncoding/SqlBulkHelpers</RepositoryUrl>
1314
<Description>A library for easy, efficient and high performance bulk insert and update of data, into a Sql Database, from .Net applications. By leveraging the power of the SqlBulkCopy classes with added support for Identity primary key table columns this library provides a greatly simplified interface to process Identity based Entities with Bulk Performance with the wide compatibility of .NetStandard 2.0.</Description>
1415
<PackageTags>sql server database table bulk insert update identity column sqlbulkcopy orm dapper linq2sql materialization materialized data view materialized-data materialized-view sync replication replica readonly</PackageTags>
1516
<PackageReleaseNotes>
17+
- Fixed a Bug where Identity Column Value was not correctly synced after Materialization Process is completing.
18+
- Added new Helper API to quickly Sync the Identity column value with the current MAX Id value of the column (ensuring it's valid after populating additional data); This is useful if you override Identity values for a full Table refresh, but then want to later insert data into the table.
19+
20+
Prior Relese Notes:
1621
- Improved namespace for SqlBulkHelpers.CustomExtensions to reduce risk of conflicts with similar existing extensions.
17-
- Restored support for SqlConnection Factory (simplified now as a Func&lt;SqlConnection&gt; when manually using the SqlDbSchemaLoader to dynamically retrieve Table Schema definitions for performance.
18-
- Added support for other Identity column data types including (INT, BIGINT, SMALLINT, &amp; TINYINT); per feature request (https://github.com/cajuncoding/SqlBulkHelpers/issues/10).
22+
- Restored support for SqlConnection Factory (simplified now as a Func&lt;SqlConnection&gt; when manually using the SqlDbSchemaLoader to dynamically retrieve Table Schema definitions for performance.
23+
- Added support for other Identity column data types including (INT, BIGINT, SMALLINT, &amp; TINYINT); per feature request (https://github.com/cajuncoding/SqlBulkHelpers/issues/10).
1924
- Added support to explicitly set Identity Values (aka SET IDENTITY_INSERT ON) via new `enableIdentityInsert` api parameter.
2025
- Added support to retreive and re-seed (aka set) the current Identity Value on a given table via new apis in the MaterializedData helpers.
2126
- Additional small bug fixes and optimiaztions.
22-
23-
Prior Relese Notes:
2427
- Added additional convenience methods to the `MaterializationContext` to retreive loading table info for models mapped via annotations (ModelType; vs only ordinal or string name).
25-
- Added support to cancel the materialization process via new `MaterializationContext.CancelMaterializationProcess()` method; allows passive cancelling without the need to throw an exception to safely stop the process.
26-
- Fixed small configuration initialization bugs when manually setting the `IsFullTextIndexHandlingEnabled` flag.
27-
- Fixed small bug where default configuration was not being used as the fallback.
28+
- Added support to cancel the materialization process via new `MaterializationContext.CancelMaterializationProcess()` method; allows passive cancelling without the need to throw an exception to safely stop the process.
29+
- Fixed small configuration initialization bugs when manually setting the `IsFullTextIndexHandlingEnabled` flag.
30+
- Fixed small bug where default configuration was not being used as the fallback.
2831
- Improve configuration of Timeouts and add support for Default DB Schema Loader timeout setting.
2932
- v2.0 provides a simplified and easier to access API as Extension Methods of the SqlTransaction class; this is a breaking change for Sql Bulk Insert/Update/etc, but shoudl be easy to migrate to!
3033
- v2.0 release also includes the NEW MaterializeData Helpers to make it significantly easier to implement highly efficient loading and publishing of materialized data via Sql Server much easier via an easy C# API.
@@ -49,7 +52,6 @@
4952
- Added more Integration Tests for Constructors and Connections, as well as the new DB Schema Loader caching implementation.
5053
- Fixed bug in dynamic initialization of SqlBulkHelpersConnectionProvider and SqlBulkHelpersDBSchemaLoader when not using the Default instances that automtically load the connection string from the application configuration setting.
5154
</PackageReleaseNotes>
52-
<Version>2.2.2</Version>
5355
</PropertyGroup>
5456

5557
<ItemGroup>

NetStandard.SqlBulkHelpers/SqlBulkHelpersConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static void ConfigureDefaults(Action<SqlBulkHelpersConfig> configAction)
5353
{
5454
DefaultConfig = Create(configAction);
5555

56-
//Validate tje Configuration!
56+
//Validate the Configuration!
5757
if (DefaultConfig.IsFullTextIndexHandlingEnabled && !DefaultConfig.IsConcurrentConnectionProcessingEnabled)
5858
throw new InvalidOperationException(
5959
$"Full Text Index Handling is currently enabled however Concurrent Connections are disabled and/or " +

0 commit comments

Comments
 (0)