diff --git a/src/Dapper.Bulk/Dapper.Bulk.csproj b/src/Dapper.Bulk/Dapper.Bulk.csproj
index 3cca1b8..a2fab4a 100644
--- a/src/Dapper.Bulk/Dapper.Bulk.csproj
+++ b/src/Dapper.Bulk/Dapper.Bulk.csproj
@@ -36,6 +36,14 @@
bin\Release\net6.0\Dapper.Bulk.xml
+
+
+
+
+
+
+
+
@@ -53,7 +61,7 @@
True
-
+
diff --git a/src/Dapper.Bulk/DapperBulk.cs b/src/Dapper.Bulk/DapperBulk.cs
index 956060c..dc6ba1f 100644
--- a/src/Dapper.Bulk/DapperBulk.cs
+++ b/src/Dapper.Bulk/DapperBulk.cs
@@ -9,339 +9,296 @@
[assembly: InternalsVisibleTo("Dapper.Bulk.Tests")]
-namespace Dapper.Bulk;
-
-///
-/// Bulk inserts for Dapper
-///
-public static class DapperBulk
+namespace Dapper.Bulk
{
///
- /// Inserts entities into table s (by default).
+ /// Bulk inserts for Dapper
///
- /// The type being inserted.
- /// Open SqlConnection
- /// Entities to insert
- /// The transaction to run under, null (the default) if none
- /// Number of bulk items inserted together, 0 (the default) if all
- /// Number of seconds before bulk command execution timeout, 30 (the default)
- /// Usage of db generated ids. By default DB generated IDs are used (identityInsert=false)
- public static void BulkInsert(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
+ public static class DapperBulk
{
- var type = typeof(T);
- BulkInsert(connection,type,data.Cast(),transaction,batchSize,bulkCopyTimeout,identityInsert);
- }
-
- ///
- /// Inserts entities into table.
- /// by default, the table is named after the data type specified.
- ///
- /// Open SqlConnection
- /// The type being inserted.
- /// Entities to insert
- /// The transaction to run under, null (the default) if none
- /// Number of bulk items inserted together, 0 (the default) if all
- /// Number of seconds before bulk command execution timeout, 30 (the default)
- /// Usage of db generated ids. By default DB generated IDs are used (identityInsert=false)
- public static void BulkInsert(this SqlConnection connection, Type type, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
- {
- var tableName = TableMapper.GetTableName(type);
- var allProperties = PropertiesCache.TypePropertiesCache(type);
- var keyProperties = PropertiesCache.KeyPropertiesCache(type);
- var computedProperties = PropertiesCache.ComputedPropertiesCache(type);
- var columns = PropertiesCache.GetColumnNamesCache(type);
-
- var insertProperties = allProperties.Except(computedProperties).ToList();
-
- if (!identityInsert)
- insertProperties = insertProperties.Except(keyProperties).ToList();
-
- var (identityInsertOn, identityInsertOff, sqlBulkCopyOptions) = GetIdentityInsertOptions(identityInsert, tableName);
-
- var insertPropertiesString = GetColumnsStringSqlServer(insertProperties, columns);
- var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty);
-
- connection.Execute($@"SELECT TOP 0 {insertPropertiesString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
-
- using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ private class PropertiesContainer
{
- bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
- bulkCopy.BatchSize = batchSize;
- bulkCopy.DestinationTableName = tempToBeInserted;
- bulkCopy.WriteToServer(ToDataTable(data, insertProperties).CreateDataReader());
+ public List AllProperties;
+ public List KeyProperties;
+ public List ComputedProperties;
+ public IReadOnlyDictionary ColumnNameMap;
}
- connection.Execute($@"
- {identityInsertOn}
- INSERT INTO {FormatTableName(tableName)}({insertPropertiesString})
- SELECT {insertPropertiesString} FROM {tempToBeInserted}
- {identityInsertOff}
- DROP TABLE {tempToBeInserted};", null, transaction);
- }
+ ///
+ /// Inserts entities into table s (by default).
+ ///
+ public static void BulkInsert(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
+ {
+ BulkInsert(connection, typeof(T), data.Cast(), transaction, batchSize, bulkCopyTimeout, identityInsert);
+ }
- ///
- /// Inserts entities into table s (by default) returns inserted entities.
- ///
- /// The element type of the array
- /// Open SqlConnection
- /// Entities to insert
- /// The transaction to run under, null (the default) if none
- /// Number of bulk items inserted together, 0 (the default) if all
- /// Number of seconds before bulk command execution timeout, 30 (the default)
- /// Usage of db generated ids. By default DB generated IDs are used (identityInsert=false)
- /// Inserted entities
- public static IEnumerable BulkInsertAndSelect(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
- {
- var type = typeof(T);
- var tableName = TableMapper.GetTableName(type);
- var allProperties = PropertiesCache.TypePropertiesCache(type);
- var keyProperties = PropertiesCache.KeyPropertiesCache(type);
- var computedProperties = PropertiesCache.ComputedPropertiesCache(type);
- var columns = PropertiesCache.GetColumnNamesCache(type);
-
- if (keyProperties.Count == 0)
+ ///
+ /// Inserts entities into table.
+ ///
+ public static void BulkInsert(this SqlConnection connection, Type type, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
{
- var dataList = data.ToList();
- connection.BulkInsert(dataList, transaction, batchSize, bulkCopyTimeout);
- return dataList;
+ var properties = GetProperties(type);
+ var (tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr, insertProperties, sqlBulkCopyOptions) = PrepareInfo(type, identityInsert, properties);
+
+ connection.Execute($@"SELECT TOP 0 {insertPropertiesStr} INTO {tempTableName} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
+
+ using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ {
+ bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
+ bulkCopy.BatchSize = batchSize;
+ bulkCopy.DestinationTableName = tempTableName;
+ bulkCopy.WriteToServer(ToDataTable(data, insertProperties).CreateDataReader());
+ }
+
+ var sqlString = GetInsertSql(tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr);
+ connection.Execute(sqlString, null, transaction);
}
- var insertProperties = allProperties.Except(computedProperties).ToList();
- if (!identityInsert)
- insertProperties = insertProperties.Except(keyProperties).ToList();
+ ///
+ /// Inserts entities into table s (by default) returns inserted entities.
+ ///
+ public static IEnumerable BulkInsertAndSelect(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
+ {
+ var type = typeof(T);
- var (identityInsertOn, identityInsertOff, sqlBulkCopyOptions) = GetIdentityInsertOptions(identityInsert, tableName);
-
- var keyPropertiesString = GetColumnsStringSqlServer(keyProperties,columns);
- var keyPropertiesInsertedString = GetColumnsStringSqlServer(keyProperties, columns, "inserted.");
- var insertPropertiesString = GetColumnsStringSqlServer(insertProperties, columns);
- var allPropertiesString = GetColumnsStringSqlServer(allProperties, columns, "target.");
+ var properties = GetProperties(type);
+ var (tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr, insertProperties, sqlBulkCopyOptions) = PrepareInfo(type, identityInsert, properties);
- var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty);
- var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty);
+ if (!properties.KeyProperties.Any())
+ {
+ var dataList = data.ToList();
+ connection.BulkInsert(dataList, transaction, batchSize, bulkCopyTimeout);
+ return dataList;
+ }
- connection.Execute($"SELECT TOP 0 {insertPropertiesString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
+ var keyPropertiesStr = GetColumnsStringSqlServer(properties.KeyProperties, properties.ColumnNameMap);
+ var keyPropertiesInsertedStr = GetColumnsStringSqlServer(properties.KeyProperties, properties.ColumnNameMap, "inserted.");
+ var allPropertiesStr = GetColumnsStringSqlServer(PropertiesCache.TypePropertiesCache(type), properties.ColumnNameMap, "target.");
- using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
- {
- bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
- bulkCopy.BatchSize = batchSize;
- bulkCopy.DestinationTableName = tempToBeInserted;
- bulkCopy.WriteToServer(ToDataTable(data, insertProperties).CreateDataReader());
- }
+ var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty);
- var table = string.Join(", ", keyProperties.Select(k => $"[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}] bigint"));
- var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}] = ins.[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}]"));
-
- return connection.Query($@"
- {identityInsertOn}
- DECLARE {tempInsertedWithIdentity} TABLE ({table})
- INSERT INTO {FormatTableName(tableName)}({insertPropertiesString})
- OUTPUT {keyPropertiesInsertedString} INTO {tempInsertedWithIdentity} ({keyPropertiesString})
- SELECT {insertPropertiesString} FROM {tempToBeInserted}
- {identityInsertOff}
-
- SELECT {allPropertiesString}
- FROM {FormatTableName(tableName)} target INNER JOIN {tempInsertedWithIdentity} ins ON {joinOn}
-
- DROP TABLE {tempToBeInserted};", null, transaction);
- }
+ connection.Execute($@"SELECT TOP 0 {insertPropertiesStr} INTO {tempTableName} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
- ///
- /// Inserts entities into table s (by default) asynchronously.
- ///
- /// The type being inserted.
- /// Open SqlConnection
- /// Entities to insert
- /// The transaction to run under, null (the default) if none
- /// Number of bulk items inserted together, 0 (the default) if all
- /// Number of seconds before bulk command execution timeout, 30 (the default)
- /// Usage of db generated ids. By default DB generated IDs are used (identityInsert=false)
- public static async Task BulkInsertAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
- {
- var type = typeof(T);
- var tableName = TableMapper.GetTableName(type);
- var allProperties = PropertiesCache.TypePropertiesCache(type);
- var keyProperties = PropertiesCache.KeyPropertiesCache(type);
- var computedProperties = PropertiesCache.ComputedPropertiesCache(type);
- var columns = PropertiesCache.GetColumnNamesCache(type);
-
- var insertProperties = allProperties.Except(computedProperties).ToList();
-
- if (!identityInsert)
- insertProperties = insertProperties.Except(keyProperties).ToList();
+ using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ {
+ bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
+ bulkCopy.BatchSize = batchSize;
+ bulkCopy.DestinationTableName = tempTableName;
+ bulkCopy.WriteToServer(ToDataTable(data, insertProperties).CreateDataReader());
+ }
- var (identityInsertOn, identityInsertOff, sqlBulkCopyOptions) = GetIdentityInsertOptions(identityInsert, tableName);
-
- var insertPropertiesString = GetColumnsStringSqlServer(insertProperties,columns);
- var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty);
+ var tableStr = GetKeysString(properties.KeyProperties, properties.ColumnNameMap);
+ var joinOnStr = GetJoinString(properties);
- await connection.ExecuteAsync($@"SELECT TOP 0 {insertPropertiesString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
+ var sqlStr = GetInsertAndSelectSql(tableName, identityInsertOnStr, identityInsertOffStr, tempTableName, insertPropertiesStr, keyPropertiesStr, keyPropertiesInsertedStr, allPropertiesStr, tempInsertedWithIdentity, tableStr, joinOnStr);
+ return connection.Query(sqlStr, null, transaction);
+ }
- using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ ///
+ /// Inserts entities into table s (by default) asynchronously.
+ ///
+ public static async Task BulkInsertAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
{
- bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
- bulkCopy.BatchSize = batchSize;
- bulkCopy.DestinationTableName = tempToBeInserted;
- await bulkCopy.WriteToServerAsync(ToDataTable(data, insertProperties).CreateDataReader());
- }
+ var type = typeof(T);
+ var properties = GetProperties(type);
+ var (tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr, insertProperties, sqlBulkCopyOptions) = PrepareInfo(type, identityInsert, properties);
- await connection.ExecuteAsync($@"
- {identityInsertOn}
- INSERT INTO {FormatTableName(tableName)}({insertPropertiesString})
- SELECT {insertPropertiesString} FROM {tempToBeInserted}
- {identityInsertOff}
+ await connection.ExecuteAsync($@"SELECT TOP 0 {insertPropertiesStr} INTO {tempTableName} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
- DROP TABLE {tempToBeInserted};", null, transaction);
- }
+ using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ {
+ bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
+ bulkCopy.BatchSize = batchSize;
+ bulkCopy.DestinationTableName = tempTableName;
+ await bulkCopy.WriteToServerAsync(ToDataTable(data, insertProperties).CreateDataReader());
+ }
- ///
- /// Inserts entities into table s (by default) asynchronously and returns inserted entities.
- ///
- /// The type being inserted.
- /// Open SqlConnection
- /// Entities to insert
- /// The transaction to run under, null (the default) if none
- /// Number of bulk items inserted together, 0 (the default) if all
- /// Number of seconds before bulk command execution timeout, 30 (the default)
- /// Usage of db generated ids. By default DB generated IDs are used (identityInsert=false)
- /// Inserted entities
- public static async Task> BulkInsertAndSelectAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
- {
- var type = typeof(T);
- var tableName = TableMapper.GetTableName(type);
- var allProperties = PropertiesCache.TypePropertiesCache(type);
- var keyProperties = PropertiesCache.KeyPropertiesCache(type);
- var computedProperties = PropertiesCache.ComputedPropertiesCache(type);
- var columns = PropertiesCache.GetColumnNamesCache(type);
-
- if (keyProperties.Count == 0)
+ var sqlString = GetInsertSql(tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr);
+ await connection.ExecuteAsync(sqlString, null, transaction);
+ }
+
+ ///
+ /// Inserts entities into table s (by default) asynchronously and returns inserted entities.
+ ///
+ public static async Task> BulkInsertAndSelectAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30, bool identityInsert = false)
{
- var dataList = data.ToList();
- await connection.BulkInsertAsync(dataList, transaction, batchSize, bulkCopyTimeout);
- return dataList;
+ var type = typeof(T);
+ var properties = GetProperties(type);
+ var (tableName, tempTableName, identityInsertOnStr, identityInsertOffStr, insertPropertiesStr, insertProperties, sqlBulkCopyOptions) = PrepareInfo(type, identityInsert, properties);
+
+ if (!properties.KeyProperties.Any())
+ {
+ var dataList = data.ToList();
+ await connection.BulkInsertAsync(dataList, transaction, batchSize, bulkCopyTimeout);
+ return dataList;
+ }
+
+ var keyPropertiesStr = GetColumnsStringSqlServer(properties.KeyProperties, properties.ColumnNameMap);
+ var keyPropertiesInsertedStr = GetColumnsStringSqlServer(properties.KeyProperties, properties.ColumnNameMap, "inserted.");
+ var allPropertiesStr = GetColumnsStringSqlServer(PropertiesCache.TypePropertiesCache(type), properties.ColumnNameMap, "target.");
+
+ var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty);
+
+ await connection.ExecuteAsync($@"SELECT TOP 0 {insertPropertiesStr} INTO {tempTableName} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
+
+ using (var bulkCopy = new SqlBulkCopy(connection, sqlBulkCopyOptions, transaction))
+ {
+ bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
+ bulkCopy.BatchSize = batchSize;
+ bulkCopy.DestinationTableName = tempTableName;
+ await bulkCopy.WriteToServerAsync(ToDataTable(data, insertProperties).CreateDataReader());
+ }
+
+ var tableStr = GetKeysString(properties.KeyProperties, properties.ColumnNameMap);
+ string joinOnStr = GetJoinString(properties);
+
+ var sqlStr = GetInsertAndSelectSql(tableName, identityInsertOnStr, identityInsertOffStr, tempTableName, insertPropertiesStr, keyPropertiesStr, keyPropertiesInsertedStr, allPropertiesStr, tempInsertedWithIdentity, tableStr, joinOnStr);
+ return await connection.QueryAsync(sqlStr, null, transaction);
}
- var insertProperties = allProperties.Except(computedProperties).ToList();
+ private static string GetJoinString(PropertiesContainer properties)
+ {
+ var str = properties.KeyProperties.Select(k =>
+ $"target.[{(properties.ColumnNameMap.ContainsKey(k.Name) ? properties.ColumnNameMap[k.Name] : k.Name)}] = ins.[{(properties.ColumnNameMap.ContainsKey(k.Name) ? properties.ColumnNameMap[k.Name] : k.Name)}]");
+ return string.Join(" AND ", str);
+ }
- if (!identityInsert)
- insertProperties = insertProperties.Except(keyProperties).ToList();
+ private static PropertiesContainer GetProperties(Type type)
+ {
+ var properties = new PropertiesContainer();
+ properties.AllProperties = PropertiesCache.TypePropertiesCache(type);
+ properties.KeyProperties = PropertiesCache.KeyPropertiesCache(type);
+ properties.ComputedProperties = PropertiesCache.ComputedPropertiesCache(type);
+ properties.ColumnNameMap = PropertiesCache.GetColumnNamesCache(type);
- var (identityInsertOn, identityInsertOff, sqlBulkCopyOptions) = GetIdentityInsertOptions(identityInsert, tableName);
-
- var keyPropertiesString = GetColumnsStringSqlServer(keyProperties,columns);
- var keyPropertiesInsertedString = GetColumnsStringSqlServer(keyProperties,columns, "inserted.");
- var insertPropertiesString = GetColumnsStringSqlServer(insertProperties,columns);
- var allPropertiesString = GetColumnsStringSqlServer(allProperties, columns, "target.");
+ return properties;
+ }
- var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty);
- var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty);
+ private static (string tableName, string tempTableName, string identityInsertOn, string identityInsertOff, string insertPropertiesStr, List insertProperties, SqlBulkCopyOptions sqlBulkCopyOptions) PrepareInfo(Type type, bool identityInsert, PropertiesContainer properties)
+ {
+ var insertProperties = properties.AllProperties.Except(properties.ComputedProperties).ToList();
+ if (!identityInsert)
+ insertProperties = insertProperties.Except(properties.KeyProperties).ToList();
+ var insertPropertiesStr = GetColumnsStringSqlServer(insertProperties, properties.ColumnNameMap);
- await connection.ExecuteAsync($@"SELECT TOP 0 {insertPropertiesString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction);
+ var tableName = TableMapper.GetTableName(type);
+ var tempTableName = GetTempTableName(tableName);
- using (var bulkCopy = new SqlBulkCopy(connection,sqlBulkCopyOptions, transaction))
- {
- bulkCopy.BulkCopyTimeout = bulkCopyTimeout;
- bulkCopy.BatchSize = batchSize;
- bulkCopy.DestinationTableName = tempToBeInserted;
- await bulkCopy.WriteToServerAsync(ToDataTable(data, insertProperties).CreateDataReader());
+ var keyIsGuid = AnyKeyIsGuid(properties.KeyProperties);
+ var (identityInsertOn, identityInsertOff, sqlBulkCopyOptions) = GetIdentityInsertOptions(identityInsert, keyIsGuid, tableName);
+
+ return (tableName, tempTableName, identityInsertOn, identityInsertOff, insertPropertiesStr, insertProperties, sqlBulkCopyOptions);
}
- var table = string.Join(", ", keyProperties.Select(k => $"[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}] bigint"));
- var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}] = ins.[{(columns.ContainsKey(k.Name) ? columns[k.Name] : k.Name)}]"));
- return await connection.QueryAsync($@"
- {identityInsertOn}
- DECLARE {tempInsertedWithIdentity} TABLE ({table})
- INSERT INTO {FormatTableName(tableName)}({insertPropertiesString})
- OUTPUT {keyPropertiesInsertedString} INTO {tempInsertedWithIdentity} ({keyPropertiesString})
- SELECT {insertPropertiesString} FROM {tempToBeInserted}
- {identityInsertOff}
- SELECT {allPropertiesString}
- FROM {FormatTableName(tableName)} target INNER JOIN {tempInsertedWithIdentity} ins ON {joinOn}
-
- DROP TABLE {tempToBeInserted};", null, transaction);
- }
+ private static string GetTempTableName(string tableName) => $"#TempInsert_{tableName}".Replace(".", string.Empty);
- private static string GetColumnsStringSqlServer(IEnumerable properties, IReadOnlyDictionary columnNames, string tablePrefix = null)
- {
- if (tablePrefix == "target.")
+ ///
+ /// this provides support for the feature allowing object property names to be different than table columns names.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static string GetColumnsStringSqlServer(IEnumerable properties, IReadOnlyDictionary columnNames, string tablePrefix = null)
{
- return string.Join(", ", properties.Select(property => $"{tablePrefix}[{columnNames[property.Name]}] as [{property.Name}] "));
+ if (tablePrefix == "target.")
+ {
+ return string.Join(", ", properties.Select(property => $"{tablePrefix}[{columnNames[property.Name]}] as [{property.Name}] "));
+ }
+
+ return string.Join(", ", properties.Select(property => $"{tablePrefix}[{columnNames[property.Name]}] "));
}
- return string.Join(", ", properties.Select(property => $"{tablePrefix}[{columnNames[property.Name]}] "));
- }
-
- private static DataTable ToDataTable(IEnumerable data, IList properties)
- {
- var typeCasts = new Type[properties.Count];
- for (var i = 0; i < properties.Count; i++)
+ private static DataTable ToDataTable(IEnumerable data, IList properties)
{
- if (properties[i].PropertyType.IsEnum)
+ var dataTable = new DataTable();
+ var typeCasts = properties.Select(p => p.PropertyType.IsEnum ? Enum.GetUnderlyingType(p.PropertyType) : null).ToArray();
+
+ foreach (var property in properties)
{
- typeCasts[i] = Enum.GetUnderlyingType(properties[i].PropertyType);
+ var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
+ dataTable.Columns.Add(property.Name, typeCasts[properties.IndexOf(property)] ?? propertyType);
}
- else
+
+ foreach (var item in data)
{
- typeCasts[i] = null;
+ var values = properties.Select((p, i) => typeCasts[i] == null ? p.GetValue(item) : Convert.ChangeType(p.GetValue(item), typeCasts[i])).ToArray();
+ dataTable.Rows.Add(values);
}
+
+ return dataTable;
}
- var dataTable = new DataTable();
- for (var i = 0; i < properties.Count; i++)
+ internal static string FormatTableName(string table)
{
- // Nullable types are not supported.
- var propertyNonNullType = Nullable.GetUnderlyingType(properties[i].PropertyType) ?? properties[i].PropertyType;
- dataTable.Columns.Add(properties[i].Name, typeCasts[i] ?? propertyNonNullType);
+ if (string.IsNullOrEmpty(table)) return table;
+ return string.Join(".", table.Split('.').Select(part => $"[{part}]"));
}
- foreach (var item in data)
+ private static (string identityInsertOn, string identityInsertOff, SqlBulkCopyOptions bulkCopyOptions) GetIdentityInsertOptions(bool identityInsert, bool keyIsGuid, string tableName)
{
- var values = new object[properties.Count];
- for (var i = 0; i < properties.Count; i++)
+ var identityInsertOn = string.Empty;
+ var identityInsertOff = string.Empty;
+ var bulkCopyOptions = identityInsert ? SqlBulkCopyOptions.KeepIdentity : SqlBulkCopyOptions.Default;
+
+ if (identityInsert && !keyIsGuid)
{
- var value = properties[i].GetValue(item, null);
- values[i] = typeCasts[i] == null ? value : Convert.ChangeType(value, typeCasts[i]);
+ identityInsertOn = $"SET IDENTITY_INSERT {FormatTableName(tableName)} ON";
+ identityInsertOff = $"SET IDENTITY_INSERT {FormatTableName(tableName)} OFF";
}
- dataTable.Rows.Add(values);
+ return (identityInsertOn, identityInsertOff, bulkCopyOptions);
}
- return dataTable;
- }
+ private static bool AnyKeyIsGuid(IEnumerable propertyInfos) => propertyInfos.Any(p => p.PropertyType == typeof(Guid));
- internal static string FormatTableName(string table)
- {
- if (string.IsNullOrEmpty(table))
+ private static string GetKeysString(IEnumerable keyProperties, IReadOnlyDictionary columns)
{
- return table;
+ var keys = keyProperties.Select(k =>
+ {
+ if (columns.ContainsKey(k.Name))
+ {
+ var typeString = k.PropertyType.Name switch
+ {
+ "Guid" => "uniqueidentifier",
+ "Int32" or "UInt32" => "int",
+ "Int64" or "UInt64" => "bigint",
+ _ => throw new ArgumentException($"Invalid data type used in primary key. Type='{k.PropertyType.Name}'.")
+ };
+ return $"[{columns[k.Name]}] {typeString}";
+ }
+ else
+ {
+ return $"[{k.Name}]" + " bigint";
+ }
+ });
+
+ return String.Join(",", keys);
}
- var parts = table.Split('.');
-
- if (parts.Length == 1)
+ private static string GetInsertSql(string tableName, string tempTableNameStr, string identityInsertOnStr, string identityInsertOffStr, string insertPropertiesStr)
{
- return $"[{table}]";
+ return
+ $@"{identityInsertOnStr}
+ INSERT INTO {FormatTableName(tableName)} ({insertPropertiesStr})
+ SELECT {insertPropertiesStr} FROM {tempTableNameStr}
+ {identityInsertOffStr}
+ DROP TABLE {tempTableNameStr};";
}
- var tableName = "";
- for (int i = 0; i < parts.Length; i++)
+ private static string GetInsertAndSelectSql(string tableName, string identityInsertOnStr, string identityInsertOffStr, string tempTableName, string insertPropertiesStr, string keyPropertiesStr, string keyPropertiesInsertedStr, string allPropertiesStr, string tempInsertedWithIdentity, string tableStr, string joinOnStr)
{
- tableName += $"[{parts[i]}]";
- if (i + 1 < parts.Length)
- {
- tableName += ".";
- }
+ return
+ $@"{identityInsertOnStr}
+ DECLARE {tempInsertedWithIdentity} TABLE ({tableStr})
+ INSERT INTO {FormatTableName(tableName)} ({insertPropertiesStr})
+ OUTPUT {keyPropertiesInsertedStr} INTO {tempInsertedWithIdentity} ({keyPropertiesStr})
+ SELECT {insertPropertiesStr} FROM {tempTableName}
+ {identityInsertOffStr}
+ SELECT {allPropertiesStr}
+ FROM {FormatTableName(tableName)} target INNER JOIN {tempInsertedWithIdentity} ins ON {joinOnStr}
+ DROP TABLE {tempTableName};";
}
-
- return tableName;
}
-
- private static (string identityInsertOn, string identityInsertOff, SqlBulkCopyOptions bulkCopyOptions)
- GetIdentityInsertOptions(bool identityInsert, string tableName)
- => identityInsert
- ? ($"SET IDENTITY_INSERT {FormatTableName(tableName)} ON",
- $"SET IDENTITY_INSERT {FormatTableName(tableName)} OFF", SqlBulkCopyOptions.KeepIdentity)
- : (string.Empty, string.Empty, SqlBulkCopyOptions.Default);
-}
+}
\ No newline at end of file
diff --git a/tests/Dapper.Bulk.Tests/CustomColumnNameTests.cs b/tests/Dapper.Bulk.Tests/CustomColumnNameTests.cs
index 0c9c44e..1c57cf6 100644
--- a/tests/Dapper.Bulk.Tests/CustomColumnNameTests.cs
+++ b/tests/Dapper.Bulk.Tests/CustomColumnNameTests.cs
@@ -29,24 +29,6 @@ private class CustomColumnName
public int Ignored { get; set; }
}
- [Fact]
- public void InsertBulk()
- {
- var data = new List();
- for (var i = 0; i < 10; i++)
- {
- data.Add(new CustomColumnName { Name = Guid.NewGuid().ToString() , LongCol = i * 1000, IntCol = i});
- }
-
- using var connection = GetConnection();
- connection.Open();
- var inserted = connection.BulkInsertAndSelect(data).ToList();
- for (var i = 0; i < data.Count; i++)
- {
- IsValidInsert(inserted[i], data[i]);
- }
- }
-
[Fact]
public void InsertSingle()
{
@@ -78,6 +60,24 @@ public void InsertSingleTransaction()
IsValidInsert(inserted, item);
}
+ [Fact]
+ public void InsertBulk()
+ {
+ var data = new List();
+ for (var i = 0; i < 10; i++)
+ {
+ data.Add(new CustomColumnName { Name = Guid.NewGuid().ToString(), LongCol = i * 1000, IntCol = i });
+ }
+
+ using var connection = GetConnection();
+ connection.Open();
+ var inserted = connection.BulkInsertAndSelect(data).ToList();
+ for (var i = 0; i < data.Count; i++)
+ {
+ IsValidInsert(inserted[i], data[i]);
+ }
+ }
+
private static void IsValidInsert(CustomColumnName inserted, CustomColumnName toBeInserted)
{
inserted.IdKey.Should().BePositive();
diff --git a/tests/Dapper.Bulk.Tests/GuidIdTests.cs b/tests/Dapper.Bulk.Tests/GuidIdTests.cs
new file mode 100644
index 0000000..95b1b1d
--- /dev/null
+++ b/tests/Dapper.Bulk.Tests/GuidIdTests.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using FluentAssertions;
+using Xunit;
+
+namespace Dapper.Bulk.Tests;
+
+public class GuidIdTests : SqlServerTestSuite
+{
+ private List data;
+ private List ids = new();
+
+ public GuidIdTests()
+ {
+ data = new List();
+ for (var i = 0; i < 10; i++)
+ {
+ var obj = new GuidIdentity
+ {
+ Id = Guid.NewGuid(),
+ Int_Col = i,
+ CreateDate = DateTime.UtcNow,
+ Name = i.ToString() + " user field",
+ };
+
+ data.Add(obj);
+
+ ids.Add(obj.Id);
+ }
+ }
+
+ [Fact]
+ public void InsertBulk()
+ {
+ using var connection = GetConnection();
+ connection.Open();
+
+ connection.BulkInsert(data, null, 0, 30, true);
+
+ var query = "SELECT * FROM GuidIdentity WHERE Id IN @Ids";
+ var inserted = connection.Query(query, new { Ids = ids });
+
+ foreach (var item in inserted)
+ {
+ var refItem = data.FirstOrDefault(o => o.Id == item.Id);
+ IsValidInsert(item, refItem, true);
+ }
+ }
+
+ [Fact]
+ public void InsertBulkAndSelect()
+ {
+ using var connection = GetConnection();
+ connection.Open();
+
+ var inserted = connection.BulkInsertAndSelect(data, null, 0, 30, false).ToList();
+ for (var i = 0; i < data.Count; i++)
+ {
+ IsValidInsert(inserted[i], data[i], false);
+ }
+ }
+
+ private static void IsValidInsert(GuidIdentity inserted, GuidIdentity toBeInserted, bool checkIdentity = true)
+ {
+ if (checkIdentity)
+ inserted.Id.Should().Be(toBeInserted.Id);
+ else
+ inserted.Id.Should().NotBe(toBeInserted.Id);
+
+ inserted.Int_Col.Should().Be(toBeInserted.Int_Col);
+ inserted.CreateDate.Should().Be(toBeInserted.CreateDate);
+ inserted.Name.Should().Be(toBeInserted.Name);
+ }
+
+ [Table("GuidIdentity")]
+ public class GuidIdentity
+ {
+ [Key]
+ public Guid Id { get; set; }
+
+ public int Int_Col { get; set; }
+
+ public DateTime CreateDate { get; set; }
+
+ public string Name { get; set; }
+ }
+}
diff --git a/tests/Dapper.Bulk.Tests/README.md b/tests/Dapper.Bulk.Tests/README.md
index 61f4ac5..a5e0485 100644
--- a/tests/Dapper.Bulk.Tests/README.md
+++ b/tests/Dapper.Bulk.Tests/README.md
@@ -1,2 +1,2 @@
Before Running test, please create localDb DapperBulkTest
-the connection string as : Server=(localdb)\\mssqllocaldb;Database=DapperBulkTest;Trusted_Connection=True;MultipleActiveResultSets=true;
\ No newline at end of file
+the connection string as : Server=localhost;Database=DapperBulkTest;Trusted_Connection=True;MultipleActiveResultSets=true;
\ No newline at end of file
diff --git a/tests/Dapper.Bulk.Tests/SqlServerTestSuite.cs b/tests/Dapper.Bulk.Tests/SqlServerTestSuite.cs
index b673c5b..efa58f3 100644
--- a/tests/Dapper.Bulk.Tests/SqlServerTestSuite.cs
+++ b/tests/Dapper.Bulk.Tests/SqlServerTestSuite.cs
@@ -1,10 +1,10 @@
-using Microsoft.Data.SqlClient;
+using Microsoft.Data.SqlClient;
namespace Dapper.Bulk.Tests;
public class SqlServerTestSuite
{
- private static readonly string ConnectionString = "Server=(localdb)\\mssqllocaldb;Database=DapperBulkTest;Trusted_Connection=True;MultipleActiveResultSets=true;";
+ private static readonly string ConnectionString = "Server=(localdb)\\mssqllocaldb;Database=DapperBulkTest;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true;";
public static SqlConnection GetConnection() => new(ConnectionString);
@@ -16,72 +16,80 @@ static SqlServerTestSuite()
connection.Open();
connection.Execute(
$@"{DropTable("IdentityAndComputedTests")}
- CREATE TABLE IdentityAndComputedTests
- (
- [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [Name] NVARCHAR(100) NULL,
- [CreateDate] DATETIME2 NOT NULL DEFAULT(GETDATE())
- );");
+ CREATE TABLE IdentityAndComputedTests
+ (
+ [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [Name] NVARCHAR(100) NULL,
+ [CreateDate] DATETIME2 NOT NULL DEFAULT(GETDATE())
+ );");
connection.Execute(
$@"{DropTable("NoIdentityTests")}
- CREATE TABLE NoIdentityTests(
- [ItemId] BIGINT NULL,
- [Name] NVARCHAR(100) NULL
- );");
+ CREATE TABLE NoIdentityTests(
+ [ItemId] BIGINT NULL,
+ [Name] NVARCHAR(100) NULL
+ );");
connection.Execute(
$@"{DropTable("EnumTests")}
- CREATE TABLE EnumTests(
- [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [IntEnum] INT NOT NULL,
- [LongEnum] BIGINT NOT NULL
- );");
+ CREATE TABLE EnumTests(
+ [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [IntEnum] INT NOT NULL,
+ [LongEnum] BIGINT NOT NULL
+ );");
connection.Execute(
$@"{DropTable("ByteArrays")}
- CREATE TABLE ByteArrays(
- [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [TestArray] varbinary(100) NOT NULL
- );");
+ CREATE TABLE ByteArrays(
+ [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [TestArray] varbinary(100) NOT NULL
+ );");
connection.Execute(
$@"{DropTable("IdentityInsertEnabledTests")}
- CREATE TABLE IdentityInsertEnabledTests
- (
- [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [Name] NVARCHAR(100) NULL
- );");
+ CREATE TABLE IdentityInsertEnabledTests
+ (
+ [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [Name] NVARCHAR(100) NULL
+ );");
connection.Execute(
- $@"{DropTable("IdentityAndNotMappedTests")}
- CREATE TABLE IdentityAndNotMappedTests
- (
- [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [Name] NVARCHAR(100) NULL
- );");
+ $@"{DropTable("IdentityAndNotMappedTests")}
+ CREATE TABLE IdentityAndNotMappedTests
+ (
+ [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [Name] NVARCHAR(100) NULL
+ );");
connection.Execute(
- $@"{DropTable("CustomColumnNames")}
- CREATE TABLE CustomColumnNames
- (
- [Id_Key] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [Name_1] NVARCHAR(100) NULL,
- [Int_Col] INT NOT NULL,
- [Long_Col] BIGINT NOT NULL
- );");
+ $@"{DropTable("CustomColumnNames")}
+ CREATE TABLE CustomColumnNames
+ (
+ [Id_Key] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [Name_1] NVARCHAR(100) NULL,
+ [Int_Col] INT NOT NULL,
+ [Long_Col] BIGINT NOT NULL
+ );");
connection.Execute(
$@"{DropTable("10_Escapes")}
- CREATE TABLE [10_Escapes](
- [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [10_Name] NVARCHAR(100) NULL
- );");
+ CREATE TABLE [10_Escapes](
+ [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [10_Name] NVARCHAR(100) NULL
+ );");
+ connection.Execute(
+ $@"{DropTable("GuidIdentity")}
+ CREATE TABLE [dbo].[GuidIdentity](
+ [Id] [uniqueidentifier] ROWGUIDCOL DEFAULT NEWID() NOT NULL PRIMARY KEY,
+ [Int_Col] [int] NOT NULL,
+ [CreateDate] [datetime2](7) NULL,
+ [Name] [nvarchar](50) NULL
+ );");
var schemaName = "test";
connection.Execute(
- $@"{DropTable($@"{schemaName}.10_Escapes")}");
+ $@"{DropTable($@"{schemaName}.10_Escapes")}");
connection.Execute(
$@"{DropSchema(schemaName)}");
@@ -91,27 +99,27 @@ [10_Name] NVARCHAR(100) NULL
connection.Execute(
$@"CREATE TABLE [{schemaName}].[10_Escapes](
- [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [10_Name] NVARCHAR(100) NULL
- );");
+ [Id] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [10_Name] NVARCHAR(100) NULL
+ );");
connection.Execute(
$@"{DropTable("PE_TranslationPhrase")}
- CREATE TABLE [PE_TranslationPhrase](
- [TranslationId] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [CultureName] NVARCHAR(100) NOT NULL,
- [Phrase] NVARCHAR(100) NOT NULL,
- [PhraseHash] uniqueidentifier NULL,
- [RowAddedDateTime] DATETIME2 NOT NULL DEFAULT(GETDATE())
- );");
+ CREATE TABLE [PE_TranslationPhrase](
+ [TranslationId] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [CultureName] NVARCHAR(100) NOT NULL,
+ [Phrase] NVARCHAR(100) NOT NULL,
+ [PhraseHash] uniqueidentifier NULL,
+ [RowAddedDateTime] DATETIME2 NOT NULL DEFAULT(GETDATE())
+ );");
connection.Execute(
$@"{DropTable("IdentityAndWriteInsertTests")}
- CREATE TABLE IdentityAndWriteInsertTests
- (
- [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
- [Name] NVARCHAR(100) NULL,
- [NotIgnored] INT NULL
- );");
+ CREATE TABLE IdentityAndWriteInsertTests
+ (
+ [IdKey] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
+ [Name] NVARCHAR(100) NULL,
+ [NotIgnored] INT NULL
+ );");
}
}