-
Notifications
You must be signed in to change notification settings - Fork 321
Feature | Add SqlBulkCopyOptions.CacheMetadata flag #3939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7110977
a3075fb
638a2ab
855e63c
343ecd0
b7feea4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -235,6 +235,19 @@ This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. | |||||
| ]]></format> | ||||||
| </example> | ||||||
| </Close> | ||||||
| <InvalidateMetadataCache> | ||||||
| <summary> | ||||||
| Clears the cached destination table metadata when using the <see cref="F:Microsoft.Data.SqlClient.SqlBulkCopyOptions.CacheMetadata" /> option. | ||||||
| </summary> | ||||||
| <remarks> | ||||||
| <para> | ||||||
| Call this method when you know the destination table schema has changed and you want to force the next <see cref="M:Microsoft.Data.SqlClient.SqlBulkCopy.WriteToServer(System.Data.Common.DbDataReader)" /> operation to refresh the metadata from the server. | ||||||
|
||||||
| Call this method when you know the destination table schema has changed and you want to force the next <see cref="M:Microsoft.Data.SqlClient.SqlBulkCopy.WriteToServer(System.Data.Common.DbDataReader)" /> operation to refresh the metadata from the server. | |
| Call this method when you know the destination table schema has changed and you want to force the next bulk copy operation (for example, a subsequent call to one of the <see cref="M:Microsoft.Data.SqlClient.SqlBulkCopy.WriteToServer" /> overloads) to refresh the metadata from the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This belongs on the DestinationTableName docs, not here.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,5 +68,19 @@ To see how the option changes the way the bulk load works, run the sample with t | |
| When specified, each batch of the bulk-copy operation will occur within a transaction. If you indicate this option and also provide a <see cref="T:Microsoft.Data.SqlClient.SqlTransaction" /> object to the constructor, an <see cref="T:System.ArgumentException" /> occurs. | ||
| </summary> | ||
| </UseInternalTransaction> | ||
| <CacheMetadata> | ||
| <summary> | ||
| <para> | ||
| When specified, <b>CacheMetadata</b> caches destination table metadata after the first bulk copy operation, allowing subsequent operations to the same table to skip the metadata discovery query. This can improve performance when performing multiple bulk copy operations to the same destination table. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try to stick to our new 100 char max per line. |
||
| </para> | ||
| <para> | ||
| <b>Warning:</b> Use this option only when you are certain the destination table schema will not change between bulk copy operations. If the table schema changes (columns added, removed, or modified), using cached metadata may result in data corruption, failed operations, or unexpected behavior. Call <see cref="M:Microsoft.Data.SqlClient.SqlBulkCopy.InvalidateMetadataCache" /> to clear the cache if the schema changes. | ||
| </para> | ||
| <para> | ||
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| The cache is automatically invalidated when <see cref="P:Microsoft.Data.SqlClient.SqlBulkCopy.DestinationTableName" /> is changed to a different table. | ||
| Changing <see cref="P:Microsoft.Data.SqlClient.SqlBulkCopy.ColumnMappings" /> between operations does not require cache invalidation because the cached metadata describes only the destination table schema, not the source-to-destination column mapping. | ||
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </para> | ||
| </summary> | ||
| </CacheMetadata> | ||
| </members> | ||
| </docs> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -235,6 +235,14 @@ private int RowNumber | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| private SourceColumnMetadata[] _currentRowMetadata; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Metadata caching fields for CacheMetadata option | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private BulkCopySimpleResultSet _cachedMetadata; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private string _cachedDestinationTableName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Per-operation clone of the destination table metadata, used when CacheMetadata is | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // enabled so that column-pruning in AnalyzeTargetAndCreateUpdateBulkCommand does not | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // mutate the cached BulkCopySimpleResultSet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private _SqlMetaDataSet _operationMetaData; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| #if DEBUG | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static bool s_setAlwaysTaskOnWrite; //when set and in DEBUG mode, TdsParser::WriteBulkCopyValue will always return a task | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static bool SetAlwaysTaskOnWrite | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -353,6 +361,14 @@ public string DestinationTableName | |||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw ADP.ArgumentOutOfRange(nameof(DestinationTableName)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Invalidate cached metadata if the destination table name changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!string.Equals(_destinationTableName, value, StringComparison.Ordinal)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can replace this statement with an Then the remaining code is unconditional: This makes it obvious that the cache is always cleared when the name changes. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cachedMetadata = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to allow the destination table name to change when caching is enabled? Would it simplify things to throw in this case, and then you don't need to ever clear/invalidate? It's trivial to discard a SqlBulkCopy instance and make a new one in this case. See my comments on |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cachedDestinationTableName = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| _destinationTableName = value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -497,6 +513,16 @@ IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sy | |||||||||||||||||||||||||||||||||||||||||||||||||||
| // We need to have a _parser.RunAsync to make it real async. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Task<BulkCopySimpleResultSet> CreateAndExecuteInitialQueryAsync(out BulkCopySimpleResultSet result) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if we have valid cached metadata for the current destination table | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (IsCopyOption(SqlBulkCopyOptions.CacheMetadata) && | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cachedMetadata != null && | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| string.Equals(_cachedDestinationTableName, _destinationTableName, StringComparison.Ordinal)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't possible for the destination table name to differ from the cached name. In fact, you don't need the cached name at all. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqlClientEventSource.Log.TryTraceEvent("SqlBulkCopy.CreateAndExecuteInitialQueryAsync | Info | Using cached metadata for table '{0}'", _destinationTableName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = _cachedMetadata; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| string TDSCommand = CreateInitialQuery(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqlClientEventSource.Log.TryTraceEvent("SqlBulkCopy.CreateAndExecuteInitialQueryAsync | Info | Initial Query: '{0}'", TDSCommand); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlBulkCopy.CreateAndExecuteInitialQueryAsync | Info | Correlation | Object Id {0}, Activity Id {1}", ObjectID, ActivityCorrelator.Current); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -506,6 +532,7 @@ private Task<BulkCopySimpleResultSet> CreateAndExecuteInitialQueryAsync(out Bulk | |||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = new BulkCopySimpleResultSet(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| RunParser(result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| CacheMetadataIfEnabled(result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -523,12 +550,23 @@ private Task<BulkCopySimpleResultSet> CreateAndExecuteInitialQueryAsync(out Bulk | |||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| var internalResult = new BulkCopySimpleResultSet(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| RunParserReliably(internalResult); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| CacheMetadataIfEnabled(internalResult); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return internalResult; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, TaskScheduler.Default); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void CacheMetadataIfEnabled(BulkCopySimpleResultSet result) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (IsCopyOption(SqlBulkCopyOptions.CacheMetadata)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cachedMetadata = result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cachedDestinationTableName = _destinationTableName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a race condition here where the DestinationName setter has just cleared
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should definitely avoid race conditions. But also, SqlBulkCopy (like SqlConnection) isn't thread safe and should be documented as such. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqlClientEventSource.Log.TryTraceEvent("SqlBulkCopy.CacheMetadataIfEnabled | Info | Cached metadata for table '{0}'", _destinationTableName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Matches associated columns with metadata from initial query. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Builds and executes the update bulk command. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet internalResults) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -578,7 +616,17 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i | |||||||||||||||||||||||||||||||||||||||||||||||||||
| bool appendComma = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Loop over the metadata for each result column. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // When using cached metadata, clone the metadata set so that null-pruning of | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // unmatched/rejected columns does not mutate the shared cache. Without this, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // changing ColumnMappings between WriteToServer calls (e.g. mapping fewer columns | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // on the first call, then more on the second) would permanently lose metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // entries from the cache. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (IsCopyOption(SqlBulkCopyOptions.CacheMetadata) && _cachedMetadata != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| metaDataSet = metaDataSet.Clone(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+625
to
+628
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (IsCopyOption(SqlBulkCopyOptions.CacheMetadata) && _cachedMetadata != null) | |
| { | |
| metaDataSet = metaDataSet.Clone(); | |
| } | |
| // Cloning _SqlMetaDataSet for cached metadata is safe only when there are | |
| // no encrypted columns. The clone/copy routines do not currently copy the | |
| // Always Encrypted metadata (e.g. cekTable on the set and isEncrypted/baseTI/cipherMD | |
| // on individual columns). Using a cloned set in that case would cause encrypted | |
| // columns to be treated as unencrypted. | |
| bool hasEncryptedColumns = false; | |
| for (int encryptedCheckIndex = 0; encryptedCheckIndex < metaDataSet.Length; encryptedCheckIndex++) | |
| { | |
| _SqlMetaData encryptedCheckMetadata = metaDataSet[encryptedCheckIndex]; | |
| if (encryptedCheckMetadata != null && encryptedCheckMetadata.IsEncrypted) | |
| { | |
| hasEncryptedColumns = true; | |
| break; | |
| } | |
| } | |
| if (IsCopyOption(SqlBulkCopyOptions.CacheMetadata) && _cachedMetadata != null && !hasEncryptedColumns) | |
| { | |
| metaDataSet = metaDataSet.Clone(); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if someone changed the destination name or invalidated the cache between here and line 629? Shouldn't _operationMetaData be nulled too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced the public API needs this. If the caller knows the schema has changed, they can create a new SqlBulkCopy instance. Allowing this mutation creates race conditions and maintenance burden. Thoughts?
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,9 @@ public enum SqlBulkCopyOptions | |
|
|
||
| /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml' path='docs/members[@name="SqlBulkCopyOptions"]/AllowEncryptedValueModifications/*'/> | ||
| AllowEncryptedValueModifications = 1 << 6, | ||
|
|
||
| /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml' path='docs/members[@name="SqlBulkCopyOptions"]/CacheMetadata/*'/> | ||
| CacheMetadata = 1 << 7, | ||
samsharma2700 marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I don't understand why the codebase uses this This change is consistent with the existing values, so I'm not asking for a change. Just ranting. |
||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.Tests | ||
| { | ||
| public class SqlBulkCopyCacheMetadataTest | ||
| { | ||
| [Fact] | ||
| public void CacheMetadata_FlagValue_IsCorrect() | ||
| { | ||
| Assert.Equal(1 << 7, (int)SqlBulkCopyOptions.CacheMetadata); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CacheMetadata_CanBeCombinedWithOtherOptions() | ||
| { | ||
| SqlBulkCopyOptions combined = | ||
| SqlBulkCopyOptions.CacheMetadata | | ||
| SqlBulkCopyOptions.KeepIdentity | | ||
| SqlBulkCopyOptions.TableLock; | ||
|
|
||
| Assert.True((combined & SqlBulkCopyOptions.CacheMetadata) == SqlBulkCopyOptions.CacheMetadata); | ||
| Assert.True((combined & SqlBulkCopyOptions.KeepIdentity) == SqlBulkCopyOptions.KeepIdentity); | ||
| Assert.True((combined & SqlBulkCopyOptions.TableLock) == SqlBulkCopyOptions.TableLock); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CacheMetadata_DoesNotOverlapExistingFlags() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't find a new enum member that we add next week that has the same underlying value. You can get all of the underlying values and look for duplicates in that list. |
||
| { | ||
| int cacheMetadataValue = (int)SqlBulkCopyOptions.CacheMetadata; | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.Default, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.KeepIdentity, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.CheckConstraints, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.TableLock, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.KeepNulls, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.FireTriggers, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.UseInternalTransaction, cacheMetadataValue); | ||
| Assert.NotEqual((int)SqlBulkCopyOptions.AllowEncryptedValueModifications, cacheMetadataValue); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidateMetadataCache_CanBeCalledWithoutError() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection()); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these tests be in the UnitTests project, and check the private members for side effects? The members would need to become internal in that case. |
||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidateMetadataCache_CanBeCalledMultipleTimes() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection()); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidateMetadataCache_WithCacheMetadataOption() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection(), SqlBulkCopyOptions.CacheMetadata, null); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void InvalidateMetadataCache_WithoutCacheMetadataOption() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection(), SqlBulkCopyOptions.Default, null); | ||
| bulkCopy.InvalidateMetadataCache(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void DestinationTableName_Change_DoesNotThrowWithCacheMetadata() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection(), SqlBulkCopyOptions.CacheMetadata, null); | ||
| bulkCopy.DestinationTableName = "Table1"; | ||
| bulkCopy.DestinationTableName = "Table2"; | ||
| bulkCopy.DestinationTableName = "Table1"; | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Constructor_WithCacheMetadataOption_Succeeds() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new(new SqlConnection(), SqlBulkCopyOptions.CacheMetadata, null); | ||
| Assert.NotNull(bulkCopy); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Constructor_WithCacheMetadataAndConnectionString_Succeeds() | ||
| { | ||
| using SqlBulkCopy bulkCopy = new("Server=localhost", SqlBulkCopyOptions.CacheMetadata); | ||
| Assert.NotNull(bulkCopy); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
ClearCachedMetadatais a better choice. You even use the termclearsin the docs below.But I'm not convinced we need this method, so stay tuned...