33using System . Buffers . Text ;
44using System . Collections . Generic ;
55using System . Data ;
6+ using System . Linq ;
67using System . Text ;
78using System . Threading ;
89using System . Threading . Tasks ;
@@ -20,6 +21,7 @@ public MySqlBulkCopy(MySqlConnection connection, MySqlTransaction? transaction =
2021 {
2122 m_connection = connection ?? throw new ArgumentNullException ( nameof ( connection ) ) ;
2223 m_transaction = transaction ;
24+ ColumnMappings = new List < MySqlBulkCopyColumnMapping > ( ) ;
2325 }
2426
2527 public int BulkCopyTimeout { get ; set ; }
@@ -44,6 +46,15 @@ public MySqlBulkCopy(MySqlConnection connection, MySqlTransaction? transaction =
4446 /// </remarks>
4547 public event MySqlRowsCopiedEventHandler ? RowsCopied ;
4648
49+ /// <summary>
50+ /// A collection of <see cref="MySqlBulkCopyColumnMapping"/> objects. If the columns being copied from the
51+ /// data source line up one-to-one with the columns in the destination table then populating this collection is
52+ /// unnecessary. Otherwise, this should be filled with a collection of <see cref="MySqlBulkCopyColumnMapping"/> objects
53+ /// specifying how source columns are to be mapped onto destination columns. If one column mapping is specified,
54+ /// then all must be specified.
55+ /// </summary>
56+ public List < MySqlBulkCopyColumnMapping > ColumnMappings { get ; }
57+
4758#if ! NETSTANDARD1_3
4859 public void WriteToServer ( DataTable dataTable )
4960 {
@@ -134,16 +145,17 @@ private async ValueTask WriteToServerAsync(IOBehavior ioBehavior, CancellationTo
134145 closeConnection = true ;
135146 }
136147
137- using ( var cmd = new MySqlCommand ( "select * from " + QuoteIdentifier ( tableName ) + ";" , m_connection , m_transaction ) )
138- using ( var reader = ( MySqlDataReader ) await cmd . ExecuteReaderAsync ( CommandBehavior . SchemaOnly , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) )
148+ // if no user-supplied column mappings, compute them from the destination schema
149+ if ( ColumnMappings . Count == 0 )
139150 {
151+ using var cmd = new MySqlCommand ( "select * from " + QuoteIdentifier ( tableName ) + ";" , m_connection , m_transaction ) ;
152+ using var reader = ( MySqlDataReader ) await cmd . ExecuteReaderAsync ( CommandBehavior . SchemaOnly , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
140153 var schema = reader . GetColumnSchema ( ) ;
141154 for ( var i = 0 ; i < schema . Count ; i ++ )
142155 {
143156 if ( schema [ i ] . DataTypeName == "BIT" )
144157 {
145- bulkLoader . Columns . Add ( $ "@col{ i } ") ;
146- bulkLoader . Expressions . Add ( $ "`{ reader . GetName ( i ) } ` = CAST(@col{ i } AS UNSIGNED)") ;
158+ ColumnMappings . Add ( new MySqlBulkCopyColumnMapping ( i , $ "@col{ i } ", $ "`{ reader . GetName ( i ) } ` = CAST(@col{ i } AS UNSIGNED)") ) ;
147159 }
148160 else if ( schema [ i ] . DataTypeName == "YEAR" )
149161 {
@@ -155,17 +167,37 @@ private async ValueTask WriteToServerAsync(IOBehavior ioBehavior, CancellationTo
155167 var type = schema [ i ] . DataType ;
156168 if ( type == typeof ( byte [ ] ) || ( type == typeof ( Guid ) && ( m_connection . GuidFormat == MySqlGuidFormat . Binary16 || m_connection . GuidFormat == MySqlGuidFormat . LittleEndianBinary16 || m_connection . GuidFormat == MySqlGuidFormat . TimeSwapBinary16 ) ) )
157169 {
158- bulkLoader . Columns . Add ( $ "@col{ i } ") ;
159- bulkLoader . Expressions . Add ( $ "`{ reader . GetName ( i ) } ` = UNHEX(@col{ i } )") ;
170+ ColumnMappings . Add ( new MySqlBulkCopyColumnMapping ( i , $ "@col{ i } ", $ "`{ reader . GetName ( i ) } ` = UNHEX(@col{ i } )") ) ;
160171 }
161172 else
162173 {
163- bulkLoader . Columns . Add ( QuoteIdentifier ( reader . GetName ( i ) ) ) ;
174+ ColumnMappings . Add ( new MySqlBulkCopyColumnMapping ( i , reader . GetName ( i ) ) ) ;
164175 }
165176 }
166177 }
167178 }
168179
180+ // set columns and expressions from the column mappings
181+ for ( var i = 0 ; i < m_valuesEnumerator ! . FieldCount ; i ++ )
182+ {
183+ var columnMapping = ColumnMappings . FirstOrDefault ( x => x . SourceOrdinal == i ) ;
184+ if ( columnMapping is null )
185+ {
186+ bulkLoader . Columns . Add ( "@`\uE002 \b ignore`" ) ;
187+ }
188+ else
189+ {
190+ if ( columnMapping . DestinationColumn . Length == 0 )
191+ throw new InvalidOperationException ( "MySqlBulkCopyColumnMapping.DestinationName is not set." ) ;
192+ if ( columnMapping . DestinationColumn [ 0 ] == '@' )
193+ bulkLoader . Columns . Add ( columnMapping . DestinationColumn ) ;
194+ else
195+ bulkLoader . Columns . Add ( QuoteIdentifier ( columnMapping . DestinationColumn ) ) ;
196+ if ( columnMapping . Expression is object )
197+ bulkLoader . Expressions . Add ( columnMapping . Expression ) ;
198+ }
199+ }
200+
169201 await bulkLoader . LoadAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
170202
171203 if ( closeConnection )
0 commit comments