Skip to content

Commit 5530fcd

Browse files
authored
Merge pull request #12 from cajuncoding/feature/add_support_to_manually_set_identity_values_on_insert
Feature/add support to manually set identity values on insert
2 parents 1e2c718 + dfc9e88 commit 5530fcd

File tree

14 files changed

+802
-219
lines changed

14 files changed

+802
-219
lines changed

NetStandard.SqlBulkHelpers/CustomExtensions/SqlBulkHelpersCustomExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace SqlBulkHelpers
99
{
1010
public static class SqlBulkHelpersCustomExtensions
1111
{
12+
private const int MaxTableNameLength = 116;
13+
1214
public static T AssertArgumentIsNotNull<T>(this T arg, string argName)
1315
{
1416
if (arg == null) throw new ArgumentNullException(argName);
@@ -78,13 +80,33 @@ public static string TrimTableNameTerm(this string term)
7880
return trimmedTerm;
7981
}
8082

83+
public static string EnforceUnderscoreTableNameTerm(this string term)
84+
=> term?.Replace(" ", "_");
85+
8186
public static string QualifySqlTerm(this string term)
8287
{
8388
return string.IsNullOrWhiteSpace(term)
8489
? null
8590
: $"[{term.TrimTableNameTerm()}]";
8691
}
8792

93+
public static IEnumerable<string> QualifySqlTerms(this IEnumerable<string> terms)
94+
=> terms.Select(t => t.QualifySqlTerm());
95+
96+
public static string MakeTableNameUnique(this string tableNameToMakeUnique, int uniqueTokenLength = 10)
97+
{
98+
if (string.IsNullOrWhiteSpace(tableNameToMakeUnique))
99+
throw new ArgumentNullException(nameof(tableNameToMakeUnique));
100+
101+
var uniqueTokenSuffix = string.Concat("_", IdGenerator.NewId(uniqueTokenLength));
102+
var uniqueName = string.Concat(tableNameToMakeUnique, uniqueTokenSuffix);
103+
104+
if (uniqueName.Length > MaxTableNameLength)
105+
uniqueName = string.Concat(tableNameToMakeUnique.Substring(0, MaxTableNameLength - uniqueTokenSuffix.Length), uniqueTokenSuffix);
106+
107+
return uniqueName;
108+
}
109+
88110
public static string ToCsv(this IEnumerable<string> enumerableList)
89111
=> string.Join(", ", enumerableList);
90112

NetStandard.SqlBulkHelpers/MaterializedData/MaterializeDataHelper.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Data;
4+
using System.Data.SqlTypes;
35
using System.Linq;
46
using System.Threading.Tasks;
57
using Microsoft.Data.SqlClient;
@@ -354,6 +356,104 @@ await sqlTransaction
354356
return tableNameTermsList.AsArray();
355357
}
356358

359+
#endregion
360+
361+
#region Table Identity Column API Methods
362+
363+
/// <summary>
364+
/// Retrieve the Current Identity Value for the specified Table
365+
/// </summary>
366+
/// <param name="sqlConnection"></param>
367+
/// <param name="sqlTransaction"></param>
368+
/// <param name="tableNameOverride"></param>
369+
/// <returns></returns>
370+
public async Task<long> GetTableCurrentIdentityValueAsync(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
371+
{
372+
using (var sqlCmd = CreateGetTableIdentityValueSqlCommand(sqlConnection, sqlTransaction, tableNameOverride))
373+
{
374+
var identityResult = await sqlCmd.ExecuteScalarAsync().ConfigureAwait(false);
375+
376+
if (identityResult == null)
377+
throw new ArgumentException($"The table specified [{GetMappedTableNameTerm(tableNameOverride).FullyQualifiedTableName}] does not contain an Identity column; current identity value is null.");
378+
379+
var currentIdentityValue = Convert.ToInt64(identityResult);
380+
return currentIdentityValue;
381+
}
382+
}
383+
384+
/// <summary>
385+
/// Retrieve the Current Identity Value for the specified Table
386+
/// </summary>
387+
/// <param name="sqlConnection"></param>
388+
/// <param name="sqlTransaction"></param>
389+
/// <param name="tableNameOverride"></param>
390+
/// <returns></returns>
391+
public long GetTableCurrentIdentityValue(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
392+
{
393+
using (var sqlCmd = CreateGetTableIdentityValueSqlCommand(sqlConnection, sqlTransaction, tableNameOverride))
394+
{
395+
var identityResult = sqlCmd.ExecuteScalar();
396+
397+
if (identityResult == null)
398+
throw new ArgumentException($"The table specified [{GetMappedTableNameTerm(tableNameOverride).FullyQualifiedTableName}] does not contain an Identity column; current identity value is null.");
399+
400+
var currentIdentityValue = Convert.ToInt64(identityResult);
401+
return currentIdentityValue;
402+
}
403+
}
404+
405+
private SqlCommand CreateGetTableIdentityValueSqlCommand(SqlConnection sqlConnection, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
406+
{
407+
var fullyQualifiedTableName = GetMappedTableNameTerm(tableNameOverride).FullyQualifiedTableName;
408+
var sqlCmd = new SqlCommand("SELECT CURRENT_IDENTITY_VALUE = IDENT_CURRENT(@TableName)", sqlConnection, sqlTransaction);
409+
sqlCmd.CommandTimeout = BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds;
410+
sqlCmd.Parameters.Add("@TableName", SqlDbType.NVarChar).Value = fullyQualifiedTableName;
411+
return sqlCmd;
412+
}
413+
414+
/// <summary>
415+
/// Sets / Re-seeds the Current Identity Value for the specified Table.
416+
/// </summary>
417+
/// <param name="sqlConnection"></param>
418+
/// <param name="newIdentitySeedValue"></param>
419+
/// <param name="sqlTransaction"></param>
420+
/// <param name="tableNameOverride"></param>
421+
/// <returns></returns>
422+
public async Task ReSeedTableIdentityValueAsync(SqlConnection sqlConnection, long newIdentitySeedValue, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
423+
{
424+
using (var sqlCmd = CreateReSeedTableIdentityValueSqlCommand(sqlConnection, newIdentitySeedValue, sqlTransaction, tableNameOverride))
425+
{
426+
await sqlCmd.ExecuteNonQueryAsync().ConfigureAwait(false);
427+
}
428+
}
429+
430+
/// <summary>
431+
/// Sets / Re-seeds the Current Identity Value for the specified Table.
432+
/// </summary>
433+
/// <param name="sqlConnection"></param>
434+
/// <param name="newIdentitySeedValue"></param>
435+
/// <param name="sqlTransaction"></param>
436+
/// <param name="tableNameOverride"></param>
437+
/// <returns></returns>
438+
public void ReSeedTableIdentityValue(SqlConnection sqlConnection, long newIdentitySeedValue, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
439+
{
440+
using (var sqlCmd = CreateReSeedTableIdentityValueSqlCommand(sqlConnection, newIdentitySeedValue, sqlTransaction, tableNameOverride))
441+
{
442+
sqlCmd.ExecuteNonQuery();
443+
}
444+
}
445+
446+
private SqlCommand CreateReSeedTableIdentityValueSqlCommand(SqlConnection sqlConnection, long newIdentitySeedValue, SqlTransaction sqlTransaction = null, string tableNameOverride = null)
447+
{
448+
var fullyQualifiedTableName = GetMappedTableNameTerm(tableNameOverride).FullyQualifiedTableName;
449+
var sqlCmd = new SqlCommand("DBCC CHECKIDENT(@TableName, RESEED, @NewIdentitySeedValue);", sqlConnection, sqlTransaction);
450+
sqlCmd.CommandTimeout = BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds;
451+
sqlCmd.Parameters.Add("@TableName", SqlDbType.NVarChar).Value = fullyQualifiedTableName;
452+
sqlCmd.Parameters.Add("@NewIdentitySeedValue", SqlDbType.BigInt).Value = newIdentitySeedValue;
453+
return sqlCmd;
454+
}
455+
456+
357457
#endregion
358458

359459
#region Full Text Index API Methods

NetStandard.SqlBulkHelpers/MaterializedData/MaterializedDataSqlClientExtensionsApi.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,119 @@ public static async Task<TableNameTerm[]> ClearTablesAsync(
198198

199199
#endregion
200200

201+
#region Table Identity Column Helper Methods (e.g. Get/Set Tables Current Identity Value)
202+
203+
public static Task<long> GetTableCurrentIdentityValueAsync(this SqlTransaction sqlTransaction, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
204+
=> GetTableCurrentIdentityValueAsync<ISkipMappingLookup>(sqlTransaction, tableName, bulkHelpersConfig);
205+
206+
public static Task<long> GetTableCurrentIdentityValueAsync<T>(this SqlTransaction sqlTransaction, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
207+
where T : class
208+
{
209+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
210+
return GetTableCurrentIdentityValueInternalAsync<T>(sqlTransaction.Connection, tableNameOverride, sqlTransaction, bulkHelpersConfig);
211+
}
212+
213+
public static Task<long> GetTableCurrentIdentityValueAsync(this SqlConnection sqlConnection, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
214+
=> GetTableCurrentIdentityValueInternalAsync<ISkipMappingLookup>(sqlConnection, tableName, bulkHelpersConfig: bulkHelpersConfig);
215+
216+
private static async Task<long> GetTableCurrentIdentityValueInternalAsync<T>(
217+
this SqlConnection sqlConnection,
218+
string tableNameOverride = null,
219+
SqlTransaction sqlTransaction = null,
220+
ISqlBulkHelpersConfig bulkHelpersConfig = null
221+
) where T : class
222+
{
223+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
224+
225+
var currentIdentityValue = await new MaterializeDataHelper<T>(bulkHelpersConfig)
226+
.GetTableCurrentIdentityValueAsync(sqlConnection, sqlTransaction, tableNameOverride)
227+
.ConfigureAwait(false);
228+
229+
return currentIdentityValue;
230+
}
231+
232+
public static long GetTableCurrentIdentityValue(this SqlTransaction sqlTransaction, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
233+
=> GetTableCurrentIdentityValue<ISkipMappingLookup>(sqlTransaction, tableName, bulkHelpersConfig);
234+
235+
public static long GetTableCurrentIdentityValue<T>(this SqlTransaction sqlTransaction, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
236+
where T : class
237+
{
238+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
239+
return GetTableCurrentIdentityValueInternal<T>(sqlTransaction.Connection, tableNameOverride, sqlTransaction, bulkHelpersConfig);
240+
}
241+
242+
public static long GetTableCurrentIdentityValue(this SqlConnection sqlConnection, string tableName, ISqlBulkHelpersConfig bulkHelpersConfig = null)
243+
=> GetTableCurrentIdentityValueInternal<ISkipMappingLookup>(sqlConnection, tableName, bulkHelpersConfig: bulkHelpersConfig);
244+
245+
private static long GetTableCurrentIdentityValueInternal<T>(
246+
this SqlConnection sqlConnection,
247+
string tableNameOverride = null,
248+
SqlTransaction sqlTransaction = null,
249+
ISqlBulkHelpersConfig bulkHelpersConfig = null
250+
) where T : class
251+
{
252+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
253+
254+
var currentIdentityValue = new MaterializeDataHelper<T>(bulkHelpersConfig).GetTableCurrentIdentityValue(sqlConnection, sqlTransaction, tableNameOverride);
255+
return currentIdentityValue;
256+
}
257+
258+
public static Task ReSeedTableIdentityValueAsync(this SqlTransaction sqlTransaction, string tableName, long newIdentitySeedValue, ISqlBulkHelpersConfig bulkHelpersConfig = null)
259+
=> ReSeedTableIdentityValueAsync<ISkipMappingLookup>(sqlTransaction, newIdentitySeedValue, tableName, bulkHelpersConfig);
260+
261+
public static Task ReSeedTableIdentityValueAsync<T>(this SqlTransaction sqlTransaction, long newIdentitySeedValue, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
262+
where T : class
263+
{
264+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
265+
return ReSeedTableIdentityValueInternalAsync<T>(sqlTransaction.Connection, newIdentitySeedValue, tableNameOverride, sqlTransaction, bulkHelpersConfig);
266+
}
267+
268+
public static Task ReSeedTableIdentityValueAsync(this SqlConnection sqlConnection, string tableName, long newIdentitySeedValue, ISqlBulkHelpersConfig bulkHelpersConfig = null)
269+
=> ReSeedTableIdentityValueInternalAsync<ISkipMappingLookup>(sqlConnection, newIdentitySeedValue, tableName, bulkHelpersConfig: bulkHelpersConfig);
270+
271+
private static async Task ReSeedTableIdentityValueInternalAsync<T>(
272+
this SqlConnection sqlConnection,
273+
long newIdentitySeedValue,
274+
string tableNameOverride = null,
275+
SqlTransaction sqlTransaction = null,
276+
ISqlBulkHelpersConfig bulkHelpersConfig = null
277+
) where T : class
278+
{
279+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
280+
281+
await new MaterializeDataHelper<T>(bulkHelpersConfig)
282+
.ReSeedTableIdentityValueAsync(sqlConnection, newIdentitySeedValue, sqlTransaction, tableNameOverride)
283+
.ConfigureAwait(false);
284+
}
285+
286+
287+
public static void ReSeedTableIdentityValue(this SqlTransaction sqlTransaction, string tableName, long newIdentitySeedValue, ISqlBulkHelpersConfig bulkHelpersConfig = null)
288+
=> ReSeedTableIdentityValueAsync<ISkipMappingLookup>(sqlTransaction, newIdentitySeedValue, tableName, bulkHelpersConfig);
289+
290+
public static void ReSeedTableIdentityValue<T>(this SqlTransaction sqlTransaction, long newIdentitySeedValue, string tableNameOverride = null, ISqlBulkHelpersConfig bulkHelpersConfig = null)
291+
where T : class
292+
{
293+
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));
294+
ReSeedTableIdentityValueInternal<T>(sqlTransaction.Connection, newIdentitySeedValue, tableNameOverride, sqlTransaction, bulkHelpersConfig);
295+
}
296+
297+
public static void ReSeedTableIdentityValue(this SqlConnection sqlConnection, string tableName, long newIdentitySeedValue, ISqlBulkHelpersConfig bulkHelpersConfig = null)
298+
=> ReSeedTableIdentityValueInternal<ISkipMappingLookup>(sqlConnection, newIdentitySeedValue, tableName, bulkHelpersConfig: bulkHelpersConfig);
299+
300+
private static void ReSeedTableIdentityValueInternal<T>(
301+
this SqlConnection sqlConnection,
302+
long newIdentitySeedValue,
303+
string tableNameOverride = null,
304+
SqlTransaction sqlTransaction = null,
305+
ISqlBulkHelpersConfig bulkHelpersConfig = null
306+
) where T : class
307+
{
308+
sqlConnection.AssertArgumentIsNotNull(nameof(sqlConnection));
309+
new MaterializeDataHelper<T>(bulkHelpersConfig).ReSeedTableIdentityValue(sqlConnection, newIdentitySeedValue, sqlTransaction, tableNameOverride);
310+
}
311+
312+
#endregion
313+
201314
#region Full Text Index (cannot be altered within a Transaction)...
202315

203316
/// <summary>

NetStandard.SqlBulkHelpers/NetStandard.SqlBulkHelpers.csproj

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
<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>
1414
<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>
1515
<PackageReleaseNotes>
16-
- Added additional convenience methods to the `MaterializationContext` to retreive loading table info for models mapped via annotations (ModelType; vs only ordinal or string name).
17-
- 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.
18-
- Fixed small configuration initialization bugs when manually setting the `IsFullTextIndexHandlingEnabled` flag.
19-
- Fixed small bug where default configuration was not being used as the fallback.
16+
- Added support for other Identity column data types including (INT, BIGINT, SMALLINT, &amp; TINYINT); per feature request (https://github.com/cajuncoding/SqlBulkHelpers/issues/10).
17+
- Added support to explicitly set Identity Values (aka SET IDENTITY_INSERT ON) via new `enableIdentityInsert` api parameter.
18+
- Added support to retreive and re-seed (aka set) the current Identity Value on a given table via new apis in the MaterializedData helpers.
19+
- Additional small bug fixes and optimiaztions.
2020

2121
Prior Relese Notes:
22+
- Added additional convenience methods to the `MaterializationContext` to retreive loading table info for models mapped via annotations (ModelType; vs only ordinal or string name).
23+
- 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.
24+
- Fixed small configuration initialization bugs when manually setting the `IsFullTextIndexHandlingEnabled` flag.
25+
- Fixed small bug where default configuration was not being used as the fallback.
2226
- Improve configuration of Timeouts and add support for Default DB Schema Loader timeout setting.
2327
- 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!
2428
- 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.
@@ -43,7 +47,7 @@
4347
- Added more Integration Tests for Constructors and Connections, as well as the new DB Schema Loader caching implementation.
4448
- 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.
4549
</PackageReleaseNotes>
46-
<Version>2.1</Version>
50+
<Version>2.2</Version>
4751
</PropertyGroup>
4852

4953
<ItemGroup>

0 commit comments

Comments
 (0)