public void TestColumnMappings()
        {
            var reader = new BulkCopyDataReader <Student>(new List <Student>());

            Assert.Equal(2, reader.ColumnMappings.Count);
            var ps = TypeExtensions.GetProperties(typeof(Student),
                                                  BindingFlags.Instance |
                                                  BindingFlags.Public).ToList();

            for (int i = 0; i < ps.Count; i++)
            {
                Assert.Equal(ps[i].Name, reader.ColumnMappings[i].SourceColumn);
                Assert.Equal(ps[i].Name, reader.ColumnMappings[i].DestinationColumn);
            }
        }
            /// <summary>Gets the <see cref="SqlBulkCopyInvalidConversionException"/>
            /// that is thrown when an invalid conversion occurs.</summary>
            /// <param name="e">The <see cref="Exception"/>
            /// that was thrown.</param>
            /// <param name="reader">The <see cref="BulkCopyDataReader{T}"/>
            /// instance that contains extra information about the
            /// items being yielded for bulk copy.</param>
            /// <returns>The <see cref="SqlBulkCopyInvalidConversionException"/>
            /// that should be thrown, or null if not applicable.</returns>
            internal static SqlBulkCopyInvalidConversionException GetBulkCopyInvalidConversionException(Exception e,
                                                                                                        BulkCopyDataReader <T> reader)
            {
                // Validate parameters.
                Debug.Assert(e != null);
                Debug.Assert(reader != null);

                // Get the invalid operation exception.
                var ioe = e as InvalidOperationException;

                // If null, get out.
                if (ioe == null)
                {
                    return(null);
                }

                // The regular expression match.
                Match match = InvalidConversionRegex.Match(ioe.Message);

                // Match exception message.
                if (!match.Success)
                {
                    return(null);
                }

                // Get the row and the item from the reader, and feed into the exception.
                return(new SqlBulkCopyInvalidConversionException(ioe.Message, ioe, reader._enumerator.Current,
                                                                 reader._itemsRead, match.Groups["sourceType"].Value, match.Groups["destinationType"].Value));
            }
        /// <summary>Bulk copies a sequence of instances of <typeparamref name="T"/> to the database.</summary>
        /// <typeparam name="T">The type that is bulk copied to the database.</typeparam>
        /// <remarks>
        /// <para>Reflection is not used for getting the values from the <paramref name="items"/>
        /// sequence; reflection is used to perform the mapping, but the mapping itself is compiled
        /// code generated on-the-fly for performance.</para>
        /// <para>The name of the type is used as the table name (with "dbo" as the default schema), or
        /// if the <see cref="TableAttribute"/> is applied, then the <see cref="TableAttribute.Name"/>
        /// property is used.</para>
        /// <para>The names of the public properties are mapped to the names of the columns, or, if the
        /// property has the <see cref="ColumnAttribute"/> applied to it, then
        /// <see cref="ColumnAttribute.Name"/> is used.</para>
        /// </remarks>
        /// <param name="connection">The <see cref="SqlConnection"/> that is used to perform the bulk copy.</param>
        /// <param name="items">The sequence of instances of <typeparamref name="T"/>
        /// to bulk copy to the database.</param>
        /// <param name="commandTimeout">The timeout to assign to the operation.</param>
        /// <param name="sqlBulkCopyOptions">Values from the <see cref="SqlBulkCopyOptions"/>
        /// that set the options when bulk copying to the database.</param>
        /// <param name="table">The name of the table that the <paramref name="items"/>
        /// should be copied to.</param>
        /// <param name="batchSize">The size of the batch to send to SQL server.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> used to
        /// cancel the operation, if necessary.</param>
        public static async Task SqlBulkCopyAsync <T>(this SqlConnection connection, IEnumerable <T> items, string table, TimeSpan commandTimeout, int batchSize,
                                                      SqlBulkCopyOptions sqlBulkCopyOptions, CancellationToken cancellationToken)
        {
            // Validate the parameters.
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }
            if (items == null)
            {
                throw new ArgumentNullException(nameof(items));
            }
            if (string.IsNullOrWhiteSpace(table))
            {
                throw new ArgumentNullException(nameof(table));
            }

            // Validate the batch size.
            if (batchSize < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(batchSize), batchSize, "The batchSize parameter must be a non-negative value.");
            }

            // Get the command timeout in seconds.
            var commandTimeoutSeconds = (int)commandTimeout.TotalSeconds;

            // If 0 or less, throw an exception.
            if (commandTimeoutSeconds <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(commandTimeout), commandTimeout,
                                                      "The commandTimeout parameter must be a positive value.");
            }

            // Create the sql bulk copy.
            using (var bc = new SqlBulkCopy(connection, sqlBulkCopyOptions, null))
            {
                // Create the reader.
                var reader = new BulkCopyDataReader <T>(items);

                // Initialize bulk copier.
                bc.BatchSize       = batchSize;
                bc.BulkCopyTimeout = commandTimeoutSeconds;

                // Set properties from the reader.  Destination table name.
                bc.DestinationTableName = table;

                // Configure mappings, this is from the field name in the
                // destination to an index-based lookup in the source (the
                // enumerables).
                bc.ColumnMappings.Clear();
                foreach (KeyValuePair <int, BulkCopyDataReader <T> .Mapping> mapping in
                         BulkCopyDataReader <T> .OrdinalToMappingMap)
                {
                    bc.ColumnMappings.Add(mapping.Key, mapping.Value.Column);
                }

                // Wrap in a try/catch.
                try
                {
                    // Write the values.
                    await bc.WriteToServerAsync(reader, cancellationToken).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    // The exception to throw.
                    Exception exceptionToThrow = null;

                    // Try to parse, if there's an exception, throw the original.
                    try
                    {
                        // Try and get the BulkException.
                        exceptionToThrow = BulkCopyDataReader <T> .GetBulkCopyColumnIdException(e, connection, table);

                        // Try and get the column mappings exception.
                        exceptionToThrow = exceptionToThrow ?? BulkCopyDataReader <T> .GetBulkCopyInvalidColumnMappingsException(e, bc);

                        // Try and get the invalid conversion exception.
                        exceptionToThrow = exceptionToThrow ?? BulkCopyDataReader <T> .GetBulkCopyInvalidConversionException(e, reader);
                    }
                    catch
                    {
                        // Intentionally do nothing.
                    }

                    // Throw the exception if not null.
                    if (exceptionToThrow != null)
                    {
                        throw exceptionToThrow;
                    }

                    // Throw the original exception.
                    throw;
                }
            }
        }