public static Task BulkInsertAsync <T>(this SqlConnection connection, IEnumerable <T> entitiesToInsert, SqlTransaction transaction = null, int?commandTimeout = null, CancellationToken cancellationToken = default) where T : class
        {
            var engine  = new BulkCRUDCommandEngine();
            var command = new BulkCommandDefinition <T>(
                connection,
                entitiesToInsert,
                transaction,
                commandTimeout,
                cancellationToken
                );

            return(engine.BulkInsertAsync(command));
        }
        public async Task BulkInsertAsync <T>(BulkCommandDefinition <T> commandDefinition) where T : class
        {
            var type             = CheckCommandConditions(commandDefinition.Entities, "insert");
            var entityTypeResult = MetaData.Models.EntityCache.GetEntity(type) ?? MetaData.TypeAnalyzer.AnalyzeType(type);

            if (!entityTypeResult.HasPrimaryKey())
            {
                throw new ArgumentException("At least one key column property must be defined.");
            }

            if (commandDefinition.Connection.State == ConnectionState.Closed)
            {
                await commandDefinition.Connection.OpenAsync(commandDefinition.CancellationToken).ConfigureAwait(false);
            }

            var tableName = entityTypeResult.TableName;

            if (!commandDefinition.TableName.IsNullOrEmpty())
            {
                tableName = commandDefinition.TableName;
            }

            var sqlBulkCopy = commandDefinition.Transaction == default
                                ? new SqlBulkCopy(commandDefinition.Connection)
            {
                DestinationTableName = tableName
            }
                                : new SqlBulkCopy(commandDefinition.Connection, SqlBulkCopyOptions.Default, commandDefinition.Transaction)
            {
                DestinationTableName = tableName
            };

            if (commandDefinition.CommandTimeout.HasValue)
            {
                sqlBulkCopy.BulkCopyTimeout = commandDefinition.CommandTimeout.Value;
            }

            var properties = GetProperties(new PropertyRequest
            {
                Type   = type,
                Entity = commandDefinition.Entities.First()
            });

            var dataTable = CreateDataTable(properties, sqlBulkCopy);

            foreach (var entity in commandDefinition.Entities)
            {
                // Must be executed for all entities, because OwnsOne Properties can be filled or not filled
                properties = GetProperties(new PropertyRequest
                {
                    Type   = type,
                    Entity = entity
                });

                var row = dataTable.NewRow();

                foreach (var property in properties)
                {
                    var columnName = property.Prefix.IsNullOrEmpty()
                                                ? property.ColumnName
                                                : property.Prefix + property.ColumnName
                    ;

                    if (property.TypeHandler == default)
                    {
                        row[columnName] = property.PropertyInfo.GetValue(property.Entity) ?? DBNull.Value;

                        continue;
                    }

                    var cellValue = property.PropertyInfo.GetValue(property.Entity);
                    if (cellValue == null)
                    {
                        row[columnName] = DBNull.Value;
                    }
                    else
                    {
                        var sqlParameter = new SqlParameter();
                        property.TypeHandler.SetValue(sqlParameter, cellValue);
                        row[columnName] = sqlParameter.Value;
                    }
                }

                dataTable.Rows.Add(row);
            }

            await sqlBulkCopy.WriteToServerAsync(dataTable, commandDefinition.CancellationToken);
        }