Skip to content

Commit

Permalink
-Added new explicit CopyTableDataAsync() APIs which enable explicit c…
Browse files Browse the repository at this point in the history
…opying of data between two tables on matching columns (automatically detected by Name and Data Type).

-Added new Materialized Data Configuration value MaterializedDataLoadingTableDataCopyMode to control whether the materialized data process automatically copies data into the Loading Tables after cloning. This helps to greatly simplify new use cases where data must be merged (and preserved) during the materialization process.
cajuncoding committed Sep 11, 2023
1 parent 0d9de38 commit cac2c35
Showing 7 changed files with 268 additions and 43 deletions.
16 changes: 9 additions & 7 deletions NetStandard.SqlBulkHelpers/MaterializedData/CloneTableInfo.cs
Original file line number Diff line number Diff line change
@@ -7,8 +7,9 @@ public readonly struct CloneTableInfo
{
public TableNameTerm SourceTable { get; }
public TableNameTerm TargetTable { get; }
public bool CopyDataFromSource { get; }

public CloneTableInfo(TableNameTerm sourceTable, TableNameTerm? targetTable = null)
public CloneTableInfo(TableNameTerm sourceTable, TableNameTerm? targetTable = null, bool copyDataFromSource = false)
{
sourceTable.AssertArgumentIsNotNull(nameof(sourceTable));

@@ -20,6 +21,7 @@ public CloneTableInfo(TableNameTerm sourceTable, TableNameTerm? targetTable = nu

SourceTable = sourceTable;
TargetTable = validTargetTable;
CopyDataFromSource = copyDataFromSource;
}

/// <summary>
@@ -32,7 +34,7 @@ public CloneTableInfo MakeTargetTableNameUnique()
private static TableNameTerm MakeTableNameUniqueInternal(TableNameTerm tableNameTerm)
=> TableNameTerm.From(tableNameTerm.SchemaName, string.Concat(tableNameTerm.TableName, "_", IdGenerator.NewId(10)));

public static CloneTableInfo From<TSource, TTarget>(string sourceTableName = null, string targetTableName = null, string targetPrefix = null, string targetSuffix = null)
public static CloneTableInfo From<TSource, TTarget>(string sourceTableName = null, string targetTableName = null, string targetPrefix = null, string targetSuffix = null, bool copyDataFromSource = false)
{
//If the generic type is ISkipMappingLookup then we must have a valid sourceTableName specified as a param...
if (SqlBulkHelpersProcessingDefinition.SkipMappingLookupType.IsAssignableFrom(typeof(TSource)))
@@ -55,13 +57,13 @@ public static CloneTableInfo From<TSource, TTarget>(string sourceTableName = nul
}

var targetTable = TableNameTerm.From<TTarget>(targetTableName ?? sourceTableName).ApplyNamePrefixOrSuffix(targetPrefix, targetSuffix);
return new CloneTableInfo(sourceTable, targetTable);
return new CloneTableInfo(sourceTable, targetTable, copyDataFromSource);
}

public static CloneTableInfo From(string sourceTableName, string targetTableName = null, string targetPrefix = null, string targetSuffix = null)
=> From<ISkipMappingLookup, ISkipMappingLookup>(sourceTableName, targetTableName, targetPrefix, targetSuffix);
public static CloneTableInfo From(string sourceTableName, string targetTableName = null, string targetPrefix = null, string targetSuffix = null, bool copyDataFromSource = false)
=> From<ISkipMappingLookup, ISkipMappingLookup>(sourceTableName, targetTableName, targetPrefix, targetSuffix, copyDataFromSource);

public static CloneTableInfo ForNewSchema(TableNameTerm sourceTable, string targetSchemaName, string targetTablePrefix = null, string targetTableSuffix = null)
=> new CloneTableInfo(sourceTable, sourceTable.SwitchSchema(targetSchemaName).ApplyNamePrefixOrSuffix(targetTablePrefix, targetTableSuffix));
public static CloneTableInfo ForNewSchema(TableNameTerm sourceTable, string targetSchemaName, string targetTablePrefix = null, string targetTableSuffix = null, bool copyDataFromSource = false)
=> new CloneTableInfo(sourceTable, sourceTable.SwitchSchema(targetSchemaName).ApplyNamePrefixOrSuffix(targetTablePrefix, targetTableSuffix), copyDataFromSource);
}
}
Original file line number Diff line number Diff line change
@@ -163,7 +163,9 @@ protected async Task<MaterializationTableInfo[]> CloneTableStructuresForMaterial
originalTableNameTerm,
loadingTablesSchema,
BulkHelpersConfig.MaterializedDataLoadingTablePrefix,
BulkHelpersConfig.MaterializedDataLoadingTableSuffix
BulkHelpersConfig.MaterializedDataLoadingTableSuffix,
//For the Loading Table specify if CopyDataFromSource is enabled based on our configuration...
copyDataFromSource: BulkHelpersConfig.MaterializedDataLoadingTableDataCopyMode == DataCopyMode.CopySourceData
);

//Add Clones for Discarding tables (used for switching Live OUT for later cleanup)...
@@ -208,7 +210,6 @@ await CloneTablesInternalAsync(
sqlTransaction,
cloneInfoToExecuteList,
recreateIfExists: true,
copyDataFromSource: false,
includeFKeyConstraints: false
).ConfigureAwait(false);

@@ -279,14 +280,14 @@ public async Task<CloneTableInfo> CloneTableAsync(
string targetTableName = null,
bool recreateIfExists = false,
bool copyDataFromSource = false
) => (await CloneTablesAsync(sqlTransaction, tablesToClone: new[] { CloneTableInfo.From<T, T>(sourceTableName, targetTableName) }, recreateIfExists).ConfigureAwait(false)).FirstOrDefault();
) => (await CloneTablesAsync(sqlTransaction, tablesToClone: new[] { CloneTableInfo.From<T, T>(sourceTableName, targetTableName) }, recreateIfExists, copyDataFromSource).ConfigureAwait(false)).FirstOrDefault();

public Task<CloneTableInfo[]> CloneTablesAsync(
SqlTransaction sqlTransaction,
bool recreateIfExists,
bool copyDataFromSource,
params CloneTableInfo[] tablesToClone
) => CloneTablesAsync(sqlTransaction, tablesToClone, recreateIfExists, copyDataFromSource);
) => CloneTablesAsync(sqlTransaction, tablesToClone.ToList(), recreateIfExists, copyDataFromSource);

public Task<CloneTableInfo[]> CloneTablesAsync(
SqlTransaction sqlTransaction,
@@ -301,7 +302,6 @@ protected async Task<CloneTableInfo[]> CloneTablesInternalAsync(
SqlTransaction sqlTransaction,
IEnumerable<CloneTableInfo> tablesToClone,
bool recreateIfExists = false,
bool copyDataFromSource = false,
bool includeFKeyConstraints = false
)
{
@@ -334,7 +334,7 @@ protected async Task<CloneTableInfo[]> CloneTablesInternalAsync(
recreateIfExists ? IfExists.Recreate : IfExists.StopProcessingWithException,
cloneIdentitySeedValue: BulkHelpersConfig.IsCloningIdentitySeedValueEnabled,
includeFKeyConstraints: includeFKeyConstraints,
copyDataFromSource: copyDataFromSource
copyDataFromSource: cloneInfo.CopyDataFromSource
);

////TODO: Might (potentially if it doesn't impede performance too much) implement support for re-mapping FKey constraints to Materialization Context tables so data integrity issues will be caught sooner
@@ -356,6 +356,74 @@ await sqlTransaction

#endregion

#region Copy Table Data API Methods

public async Task<CloneTableInfo> CopyTableDataAsync(
SqlTransaction sqlTransaction,
string sourceTableName = null,
string targetTableName = null
) => (await CopyTableDataAsync(sqlTransaction, tablesToProcess: new[] { CloneTableInfo.From<T, T>(sourceTableName, targetTableName, copyDataFromSource: true) }).ConfigureAwait(false)).FirstOrDefault();

public Task<CloneTableInfo[]> CopyTableDataAsync(
SqlTransaction sqlTransaction,
params CloneTableInfo[] tablesToProcess
) => CopyTableDataAsync(sqlTransaction, tablesToProcess.ToList());

public Task<CloneTableInfo[]> CopyTableDataAsync(
SqlTransaction sqlTransaction,
IEnumerable<CloneTableInfo> tablesToProcess
) => CopyTableDataInternalAsync(sqlTransaction, tablesToProcess);

//Internal method with additional flags for normal cloning & materialized data cloning
//NOTE: Materialization process requires special handling such as No FKeys being added to Temp/Loading Tables until ready to Switch
protected async Task<CloneTableInfo[]> CopyTableDataInternalAsync(
SqlTransaction sqlTransaction,
IEnumerable<CloneTableInfo> tablesToClone
)
{
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));

var cloneInfoList = tablesToClone.ToList();

if (cloneInfoList.IsNullOrEmpty())
throw new ArgumentException("At least one source & target table pair must be specified.");

var sqlScriptBuilder = MaterializedDataScriptBuilder.NewSqlScript();
var cloneInfoResults = new List<CloneTableInfo>();
foreach (var cloneInfo in cloneInfoList)
{
var sourceTable = cloneInfo.SourceTable;
var targetTable = cloneInfo.TargetTable;

//If both Source & Target are the same (e.g. Target was not explicitly specified) then we adjust
// the Target to ensure we create a copy and append a unique Copy Id...
if (targetTable.EqualsIgnoreCase(sourceTable))
throw new InvalidOperationException($"The source table name {sourceTable.FullyQualifiedTableName} and target table name {targetTable.FullyQualifiedTableName} must be different.");

var sourceTableSchemaDefinition = await GetTableSchemaDefinitionInternalAsync(TableSchemaDetailLevel.BasicDetails, sqlTransaction.Connection, sqlTransaction, sourceTable);
if (sourceTableSchemaDefinition == null)
throw new ArgumentException($"Could not resolve the source table schema for {sourceTable.FullyQualifiedTableName} on the provided connection.");

var targetTableSchemaDefinition = await GetTableSchemaDefinitionInternalAsync(TableSchemaDetailLevel.BasicDetails, sqlTransaction.Connection, sqlTransaction, targetTable);
if (targetTableSchemaDefinition == null)
throw new ArgumentException($"Could not resolve the target table schema for {sourceTable.FullyQualifiedTableName} on the provided connection.");

sqlScriptBuilder.CopyTableData(sourceTableSchemaDefinition, targetTableSchemaDefinition);

cloneInfoResults.Add(new CloneTableInfo(sourceTable, targetTable));
}

//Execute the Script!
await sqlTransaction
.ExecuteMaterializedDataSqlScriptAsync(sqlScriptBuilder, BulkHelpersConfig.MaterializeDataStructureProcessingTimeoutSeconds)
.ConfigureAwait(false);

//If everything was successful then we can simply return the input values as they were all cloned...
return cloneInfoResults.AsArray();
}

#endregion

#region Drop Table API Methods

public async Task<TableNameTerm> DropTableAsync(SqlTransaction sqlTransaction, string tableNameOverride = null)
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ public MaterializedDataScriptBuilder CloneTableWithAllElements(
CloneTableWithColumnsOnly(sourceTableDefinition.TableNameTerm, targetTable, ifExists);

if (copyDataFromSource)
CopyTableData(sourceTableDefinition, targetTable);
CopyTableDataForClone(sourceTableDefinition, targetTable);

//GET the Seed Value immediately, since there is a small chance of it changing...
if (cloneIdentitySeedValue && sourceTableDefinition.IdentityColumn != null)
@@ -106,54 +106,84 @@ public MaterializedDataScriptBuilder CloneTableWithAllElements(
return this;
}

public MaterializedDataScriptBuilder CopyTableData(SqlBulkHelpersTableDefinition sourceTableDefinition, TableNameTerm targetTable)
public MaterializedDataScriptBuilder CopyTableData(SqlBulkHelpersTableDefinition sourceTableDefinition, SqlBulkHelpersTableDefinition targetTableDefinition)
{
sourceTableDefinition.AssertArgumentIsNotNull(nameof(sourceTableDefinition));
targetTable.AssertArgumentIsNotNull(nameof(targetTable));
targetTableDefinition.AssertArgumentIsNotNull(nameof(targetTableDefinition));

bool hasIdentityColumn = sourceTableDefinition.IdentityColumn != null;
var targetColLookup = targetTableDefinition.TableColumns.ToLookup(c => new { c.ColumnName, c.DataType });

//In this overload we handle the Source Table Definition and can dynamically determine if there is An Identity column we handle by enabling insertion for them...
if (hasIdentityColumn)
{
ScriptBuilder.Append($@"
--The Table {sourceTableDefinition.TableFullyQualifiedName} has an Identity Column {sourceTableDefinition.IdentityColumn.ColumnName.QualifySqlTerm()} so we must allow Insertion of IDENTITY values to copy raw table data...
SET IDENTITY_INSERT {targetTable.FullyQualifiedTableName} ON;
");
}
var matchingColumnDefs = sourceTableDefinition.TableColumns
.Where(c => targetColLookup.Contains(new { c.ColumnName, c.DataType }))
.ToArray();

//Now we can Copy data between the two tables...
CopyTableData(sourceTableDefinition.TableNameTerm, targetTable, sourceTableDefinition.TableColumns.AsArray());
if (!matchingColumnDefs.Any())
throw new ArgumentException("There are no matching column definitions between the source & target table schema definitions provided; there must be at least one column matching on name & data type.");

//In this overload we handle the Source Table Definition and can dynamically determine if there is An Identity column we handle by enabling insertion for them...
if (hasIdentityColumn)
{
ScriptBuilder.Append($@"
--We now disable IDENTITY Inserts once all data is copied into {targetTable}...
SET IDENTITY_INSERT {targetTable.FullyQualifiedTableName} OFF;
");
}
//Now we can Copy data between the two tables on the matching columns detected and enable Identity Insert if the
// Target has an Identity Column which might be explicitly copied...
CopyTableData(
sourceTable: sourceTableDefinition.TableNameTerm,
targetTable: targetTableDefinition.TableNameTerm,
enableIdentityInsertOnTarget: targetTableDefinition.IdentityColumn != null,
matchingColumnDefs
);

return this;
}

public MaterializedDataScriptBuilder CopyTableDataForClone(SqlBulkHelpersTableDefinition sourceTableDefinition, TableNameTerm targetTable, params TableColumnDefinition[] columnDefs)
{
sourceTableDefinition.AssertArgumentIsNotNull(nameof(sourceTableDefinition));
targetTable.AssertArgumentIsNotNull(nameof(targetTable));

//Now we can Copy data between the two tables and enable Identity Insert if the Source has an Identity Column (which was cloned)...
CopyTableData(
sourceTableDefinition.TableNameTerm,
targetTable,
enableIdentityInsertOnTarget: sourceTableDefinition.IdentityColumn != null,
sourceTableDefinition.TableColumns.AsArray()
);

return this;
}

public MaterializedDataScriptBuilder CopyTableData(TableNameTerm sourceTable, TableNameTerm targetTable, params TableColumnDefinition[] columnDefs)
public MaterializedDataScriptBuilder CopyTableData(TableNameTerm sourceTable, TableNameTerm targetTable, bool enableIdentityInsertOnTarget, params TableColumnDefinition[] columnDefs)
{
sourceTable.AssertArgumentIsNotNull(nameof(sourceTable));
targetTable.AssertArgumentIsNotNull(nameof(targetTable));

//Validate that we have Table Columns...
if (!columnDefs.HasAny())
throw new ArgumentException($"At least one valid column definition must be specified to denote what data to copy between tables.");
throw new ArgumentException("At least one valid column definition must be specified to denote what data to copy between tables.");

var columnNamesCsv = columnDefs.Select(c => c.ColumnName.QualifySqlTerm()).ToCsv();

//In this overload we handle the Source Table Definition and can dynamically determine if there is An Identity column we handle by enabling insertion for them...
if (enableIdentityInsertOnTarget)
{
ScriptBuilder.Append($@"
--The Table {targetTable.FullyQualifiedTableName} has an Identity Column so we must allow Insertion of IDENTITY values to copy raw table data...
SET IDENTITY_INSERT {targetTable.FullyQualifiedTableName} ON;
");
}

ScriptBuilder.Append($@"
--Syncs the Identity Seed value of the Target Table with the current value of the Source Table (captured into Variable at top of script)
INSERT INTO {targetTable.FullyQualifiedTableName} ({columnNamesCsv})
SELECT {columnNamesCsv}
FROM {sourceTable.FullyQualifiedTableName};
");

//In this overload we handle the Source Table Definition and can dynamically determine if there is An Identity column we handle by enabling insertion for them...
if (enableIdentityInsertOnTarget)
{
ScriptBuilder.Append($@"
--We now disable IDENTITY Inserts once all data is copied into {targetTable.FullyQualifiedTableName}...
SET IDENTITY_INSERT {targetTable.FullyQualifiedTableName} OFF;
");
}

return this;
}

Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ public static async Task<CloneTableInfo> CloneTableAsync<T>(
ISqlBulkHelpersConfig bulkHelpersConfig = null
) where T : class => (await CloneTablesAsync(
sqlTransaction,
new[] { CloneTableInfo.From<T, T>() },
new[] { CloneTableInfo.From<T, T>(copyDataFromSource: copyDataFromSource) },
recreateIfExists,
copyDataFromSource,
bulkHelpersConfig
@@ -82,7 +82,7 @@ public static async Task<CloneTableInfo> CloneTableAsync(
ISqlBulkHelpersConfig bulkHelpersConfig = null
) => (await CloneTablesAsync(
sqlTransaction,
new[] { CloneTableInfo.From(sourceTableName, targetTableName) },
new[] { CloneTableInfo.From(sourceTableName, targetTableName, copyDataFromSource: copyDataFromSource) },
recreateIfExists,
copyDataFromSource,
bulkHelpersConfig
@@ -96,7 +96,7 @@ public static Task<CloneTableInfo[]> CloneTablesAsync(
ISqlBulkHelpersConfig bulkHelpersConfig = null
) => CloneTablesAsync(
sqlTransaction,
tablesToClone.Select(t => CloneTableInfo.From(t.SourceTableName, t.TargetTableName)),
tablesToClone.Select(t => CloneTableInfo.From(t.SourceTableName, t.TargetTableName, copyDataFromSource: copyDataFromSource)),
recreateIfExists,
copyDataFromSource,
bulkHelpersConfig
@@ -121,6 +121,57 @@ public static async Task<CloneTableInfo[]> CloneTablesAsync(

#endregion

#region Copy Table Data Extensions

public static async Task<CloneTableInfo> CopyTableDataAsync<TSource, TTarget>(
this SqlTransaction sqlTransaction,
ISqlBulkHelpersConfig bulkHelpersConfig = null
)
where TSource : class
where TTarget : class => (await CopyTableDataAsync(
sqlTransaction,
new[] { CloneTableInfo.From<TSource, TTarget>(copyDataFromSource: true) },
bulkHelpersConfig
).ConfigureAwait(false)).FirstOrDefault();

public static async Task<CloneTableInfo> CopyTableDataAsync(
this SqlTransaction sqlTransaction,
string sourceTableName,
string targetTableName = null,
ISqlBulkHelpersConfig bulkHelpersConfig = null
) => (await CopyTableDataAsync(
sqlTransaction,
new[] { CloneTableInfo.From(sourceTableName, targetTableName, copyDataFromSource: true) },
bulkHelpersConfig
).ConfigureAwait(false)).FirstOrDefault();

public static Task<CloneTableInfo[]> CopyTableDataAsync(
this SqlTransaction sqlTransaction,
IEnumerable<(string SourceTableName, string TargetTableName)> tablesToProcess,
ISqlBulkHelpersConfig bulkHelpersConfig = null
) => CopyTableDataAsync(
sqlTransaction,
tablesToProcess.Select(t => CloneTableInfo.From(t.SourceTableName, t.TargetTableName, copyDataFromSource: true)),
bulkHelpersConfig
);

public static async Task<CloneTableInfo[]> CopyTableDataAsync(
this SqlTransaction sqlTransaction,
IEnumerable<CloneTableInfo> tablesToClone,
ISqlBulkHelpersConfig bulkHelpersConfig = null
)
{
sqlTransaction.AssertArgumentIsNotNull(nameof(sqlTransaction));

var results = await new MaterializeDataHelper<ISkipMappingLookup>(bulkHelpersConfig)
.CopyTableDataAsync(sqlTransaction, tablesToClone.AsArray())
.ConfigureAwait(false);

return results;
}

#endregion

#region Drop Table Extensions

public static async Task<TableNameTerm> DropTableAsync<T>(
@@ -535,7 +586,7 @@ public static async Task ExecuteMaterializeDataProcessAsync(
var materializationDataHelper = new MaterializeDataHelper<ISkipMappingLookup>(bulkHelpersConfig);
MaterializeDataContext materializeDataContext = null;

//Initialize our Schema outside the transaction to avoid Schema locks (now Default Behaviour as of v2.3.0)...
//Initialize our Schema outside the transaction to avoid Schema locks (now Default Behavior as of v2.3.0)...
if (sqlBulkHelpersConfig.MaterializedDataSchemaCopyMode == SchemaCopyMode.OutsideTransactionAvoidSchemaLocks)
{
#if NETSTANDARD2_0
7 changes: 5 additions & 2 deletions NetStandard.SqlBulkHelpers/NetStandard.SqlBulkHelpers.csproj
Original file line number Diff line number Diff line change
@@ -8,15 +8,18 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>BBernard / CajunCoding</Authors>
<Company>CajunCoding</Company>
<Version>2.3.1</Version>
<Version>2.4.0</Version>
<PackageProjectUrl>https://github.com/cajuncoding/SqlBulkHelpers</PackageProjectUrl>
<RepositoryUrl>https://github.com/cajuncoding/SqlBulkHelpers</RepositoryUrl>
<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>
<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>
<PackageReleaseNotes>
- Fixed bug with Sql Bulk Insert/Update processing with Model Properties that have mapped database names via mapping attribute (e.g. [SqlBulkColumn("")], [Map("")], [Column("")], etc.).
-Added new explicit CopyTableDataAsync() APIs which enable explicit copying of data between two tables on matching columns (automatically detected by column Name and Data Type).
-Added new Materialized Data Configuration value MaterializedDataLoadingTableDataCopyMode to control whether the materialized data process automatically copies data into the Loading Tables after cloning.
This helps to greatly simplify new use cases where data must be merged (and preserved) during the materialization process.

Prior Relese Notes:
- Fixed bug with Sql Bulk Insert/Update processing with Model Properties that have mapped database names via mapping attribute (e.g. [SqlBulkColumn("")], [Map("")], [Column("")], etc.).
- Changed default behaviour to no longer clone tables/schema inside a Transaction which creates a full Schema Lock -- as this greatly impacts Schema aware ORMs such as SqlBulkHelpers, RepoDb, etc.
- New separate methods is now added to handle the CleanupMaterializeDataProcessAsync() but must be explicitly called as it is no longer implicitly called with FinishMaterializeDataProcessAsync().
- Added new configuration value to control if Schema copying/cloning (for Loading Tables) is inside or outide the Transaction (e.g. SchemaCopyMode.InsideTransactionAllowSchemaLocks vs OutsideTransactionAvoidSchemaLocks).
8 changes: 8 additions & 0 deletions NetStandard.SqlBulkHelpers/SqlBulkHelpersConfig.cs
Original file line number Diff line number Diff line change
@@ -16,6 +16,12 @@ public enum SchemaCopyMode
InsideTransactionAllowSchemaLocks = 2,
}

public enum DataCopyMode
{
DoNotCopyData = 1,
CopySourceData = 2
}

public interface ISqlBulkHelpersConfig
{
int SqlBulkBatchSize { get; } //General guidance is that 2000-5000 is efficient enough.
@@ -38,6 +44,7 @@ public interface ISqlBulkHelpersConfig
string MaterializedDataDiscardingTableSuffix { get; }

SchemaCopyMode MaterializedDataSchemaCopyMode { get; }
DataCopyMode MaterializedDataLoadingTableDataCopyMode { get; }
bool MaterializedDataMakeSchemaCopyNamesUnique { get; }
bool IsCloningIdentitySeedValueEnabled { get; }

@@ -141,6 +148,7 @@ public bool IsSqlBulkTableLockEnabled
public int MaterializedDataSwitchTableWaitTimeoutMinutes { get; set; } = 1;
public SwitchWaitTimeoutAction MaterializedDataSwitchTimeoutAction { get; } = SwitchWaitTimeoutAction.Abort;
public SchemaCopyMode MaterializedDataSchemaCopyMode { get; set; } = SchemaCopyMode.OutsideTransactionAvoidSchemaLocks;
public DataCopyMode MaterializedDataLoadingTableDataCopyMode { get; set; } = DataCopyMode.DoNotCopyData;
public bool MaterializedDataMakeSchemaCopyNamesUnique { get; set; } = true;

public string MaterializedDataLoadingSchema { get; set; } = "materializing_load";
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using SqlBulkHelpers.Tests;
using SqlBulkHelpers.MaterializedData;
using Microsoft.Data.SqlClient;
using RepoDb;
using SqlBulkHelpers.CustomExtensions;
using SqlBulkHelpers.SqlBulkHelpers;

namespace SqlBulkHelpers.IntegrationTests
{
[TestClass]
public class MaterializeDataCopyTableDataTests : BaseTest
{

[TestMethod]
public async Task TestBasicCopyTableDataAsync()
{
var sqlConnectionProvider = SqlConnectionHelper.GetConnectionProvider();

//We can construct this multiple ways, so here we test the Parse and Switch Schema methods...
var targetTableNameTerm = TestHelpers.TestTableNameFullyQualified
.ParseAsTableNameTerm()
.ApplyNamePrefixOrSuffix(suffix: "_CopyTableDataTest");

await using (var sqlConn = await sqlConnectionProvider.NewConnectionAsync().ConfigureAwait(false))
await using (var sqlTrans = (SqlTransaction)await sqlConn.BeginTransactionAsync().ConfigureAwait(false))
{
//FIRST Copy the Table to ensure we have a Target Table...
var cloneInfo = await sqlTrans.CloneTableAsync(
sourceTableName: TestHelpers.TestTableNameFullyQualified,
targetTableName: targetTableNameTerm,
recreateIfExists: true,
copyDataFromSource: false
).ConfigureAwait(false);

//Second Copy the Data using the New explicit API..
var resultCopyInfo = await sqlTrans.CopyTableDataAsync(TestHelpers.TestTableNameFullyQualified, targetTableNameTerm).ConfigureAwait(false);

await sqlTrans.CommitAsync().ConfigureAwait(false);
//await sqlTrans.RollbackAsync().ConfigureAwait(false);

//Validate that the new table has No Data!
Assert.IsNotNull(cloneInfo);
Assert.IsNotNull(resultCopyInfo);
Assert.AreEqual(resultCopyInfo.SourceTable.FullyQualifiedTableName, TestHelpers.TestTableNameFullyQualified);
Assert.AreEqual(resultCopyInfo.TargetTable.FullyQualifiedTableName, targetTableNameTerm);
var sourceTableCount = await sqlConn.CountAllAsync(tableName: cloneInfo.SourceTable).ConfigureAwait(false);
var targetTableCount = await sqlConn.CountAllAsync(tableName: cloneInfo.TargetTable).ConfigureAwait(false);

//Ensure both Source & Target contain the same number of records!
Assert.AreEqual(sourceTableCount, targetTableCount);

////CLEANUP The Cloned Table so that other Tests Work as expected (e.g. Some tests validate Referencing FKeys, etc.
//// that are now increased with the table clone).
//await using (var sqlTransForCleanup = (SqlTransaction)await sqlConn.BeginTransactionAsync().ConfigureAwait(false))
//{
// await sqlTransForCleanup.DropTableAsync(cloneInfo.TargetTable).ConfigureAwait(false);
// await sqlTransForCleanup.CommitAsync().ConfigureAwait(false);
//}
}
}
}
}

0 comments on commit cac2c35

Please sign in to comment.