diff --git a/src/Dapper.Bulk.Shared/PropertiesCache.cs b/src/Dapper.Bulk.Shared/PropertiesCache.cs index e21210b..1c0c214 100644 --- a/src/Dapper.Bulk.Shared/PropertiesCache.cs +++ b/src/Dapper.Bulk.Shared/PropertiesCache.cs @@ -84,6 +84,17 @@ public static List KeyPropertiesCache(Type type) return keyProperties; } + public static List ExplicitKeyPropertiesCache(Type type) + { + var keyProperties = KeyPropertiesCache(type); + var explicitKeys = keyProperties + .Where(p => ((IEnumerable)p.GetCustomAttributes(true)) + .Any((Func)(a => a.GetType().Name == "ExplicitKeyAttribute"))) + .ToList(); + + return explicitKeys; + } + public static List ComputedPropertiesCache(Type type) { if (ComputedProperties.TryGetValue(type.TypeHandle, out var cachedProps)) diff --git a/src/Dapper.Bulk/DapperBulk.cs b/src/Dapper.Bulk/DapperBulk.cs index a64c958..4e3fbb8 100644 --- a/src/Dapper.Bulk/DapperBulk.cs +++ b/src/Dapper.Bulk/DapperBulk.cs @@ -25,10 +25,11 @@ public static class DapperBulk /// 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) - public static void BulkInsert(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) + public static void BulkInsert(this SqlConnection connection, IEnumerable data, + SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) { var type = typeof(T); - BulkInsert(connection,type,data.Cast(),transaction,batchSize,bulkCopyTimeout); + BulkInsert(connection, type, data.Cast(), transaction, batchSize, bulkCopyTimeout); } /// @@ -41,19 +42,25 @@ public static void BulkInsert(this SqlConnection connection, IEnumerable d /// 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) - public static void BulkInsert(this SqlConnection connection, Type type, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) - { + public static void BulkInsert(this SqlConnection connection, Type type, IEnumerable data, + SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) + { var tableName = TableMapper.GetTableName(type); var allProperties = PropertiesCache.TypePropertiesCache(type); var keyProperties = PropertiesCache.KeyPropertiesCache(type); + var explicitKeyProperties = PropertiesCache.ExplicitKeyPropertiesCache(type); var computedProperties = PropertiesCache.ComputedPropertiesCache(type); var columns = PropertiesCache.GetColumnNamesCache(type); - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - var allPropertiesExceptKeyAndComputedString = GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); + var allPropertiesExceptKeyAndComputed = + GetAllPropertiesExceptKeyAndComputed(allProperties, keyProperties, explicitKeyProperties, computedProperties); + var allPropertiesExceptKeyAndComputedString = + GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty); - connection.Execute($@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction); + connection.Execute( + $@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", + null, transaction); using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction)) { @@ -70,6 +77,13 @@ public static void BulkInsert(this SqlConnection connection, Type type, IEnumera DROP TABLE {tempToBeInserted};", null, transaction); } + private static List GetAllPropertiesExceptKeyAndComputed(List allProperties, List keyProperties, List explicitKeyProperties, List computedProperties) + { + return explicitKeyProperties.Count > 0 ? + allProperties : + allProperties.Except(keyProperties.Union(computedProperties)).ToList(); + } + /// /// Inserts entities into table s (by default) returns inserted entities. /// @@ -80,12 +94,14 @@ public static void BulkInsert(this SqlConnection connection, Type type, IEnumera /// Number of bulk items inserted together, 0 (the default) if all /// Number of seconds before bulk command execution timeout, 30 (the default) /// Inserted entities - public static IEnumerable BulkInsertAndSelect(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) + public static IEnumerable BulkInsertAndSelect(this SqlConnection connection, IEnumerable data, + SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) { var type = typeof(T); var tableName = TableMapper.GetTableName(type); var allProperties = PropertiesCache.TypePropertiesCache(type); var keyProperties = PropertiesCache.KeyPropertiesCache(type); + var explicitKeyProperties = PropertiesCache.ExplicitKeyPropertiesCache(type); var computedProperties = PropertiesCache.ComputedPropertiesCache(type); var columns = PropertiesCache.GetColumnNamesCache(type); @@ -96,17 +112,21 @@ public static IEnumerable BulkInsertAndSelect(this SqlConnection connectio return dataList; } - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); + var allPropertiesExceptKeyAndComputed = + GetAllPropertiesExceptKeyAndComputed(allProperties, keyProperties, explicitKeyProperties, computedProperties); - var keyPropertiesString = GetColumnsStringSqlServer(keyProperties,columns); + var keyPropertiesString = GetColumnsStringSqlServer(keyProperties, columns); var keyPropertiesInsertedString = GetColumnsStringSqlServer(keyProperties, columns, "inserted."); - var allPropertiesExceptKeyAndComputedString = GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); + var allPropertiesExceptKeyAndComputedString = + GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); var allPropertiesString = GetColumnsStringSqlServer(allProperties, columns, "target."); var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty); var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty); - connection.Execute($"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction); + connection.Execute( + $"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", + null, transaction); using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction)) { @@ -116,8 +136,8 @@ public static IEnumerable BulkInsertAndSelect(this SqlConnection connectio bulkCopy.WriteToServer(ToDataTable(data, allPropertiesExceptKeyAndComputed).CreateDataReader()); } - var table = string.Join(", ", keyProperties.Select(k => $"[{k.Name }] bigint")); - var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{k.Name }] = ins.[{k.Name }]")); + var table = string.Join(", ", keyProperties.Select(k => $"[{k.Name}] bigint")); + var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{k.Name}] = ins.[{k.Name}]")); return connection.Query($@" DECLARE {tempInsertedWithIdentity} TABLE ({table}) INSERT INTO {FormatTableName(tableName)}({allPropertiesExceptKeyAndComputedString}) @@ -139,27 +159,35 @@ public static IEnumerable BulkInsertAndSelect(this SqlConnection connectio /// 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) - public static async Task BulkInsertAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) + public static async Task BulkInsertAsync(this SqlConnection connection, IEnumerable data, + SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) { var type = typeof(T); var tableName = TableMapper.GetTableName(type); var allProperties = PropertiesCache.TypePropertiesCache(type); var keyProperties = PropertiesCache.KeyPropertiesCache(type); + var explicitKeyProperties = PropertiesCache.ExplicitKeyPropertiesCache(type); var computedProperties = PropertiesCache.ComputedPropertiesCache(type); var columns = PropertiesCache.GetColumnNamesCache(type); - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - var allPropertiesExceptKeyAndComputedString = GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed,columns); + var allPropertiesExceptKeyAndComputed = + GetAllPropertiesExceptKeyAndComputed(allProperties, keyProperties, explicitKeyProperties, computedProperties); + + var allPropertiesExceptKeyAndComputedString = + GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty); - await connection.ExecuteAsync($@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction); + await connection.ExecuteAsync( + $@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", + null, transaction); using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction)) { bulkCopy.BulkCopyTimeout = bulkCopyTimeout; bulkCopy.BatchSize = batchSize; bulkCopy.DestinationTableName = tempToBeInserted; - await bulkCopy.WriteToServerAsync(ToDataTable(data, allPropertiesExceptKeyAndComputed).CreateDataReader()); + await bulkCopy.WriteToServerAsync(ToDataTable(data, allPropertiesExceptKeyAndComputed) + .CreateDataReader()); } await connection.ExecuteAsync($@" @@ -179,12 +207,14 @@ await connection.ExecuteAsync($@" /// Number of bulk items inserted together, 0 (the default) if all /// Number of seconds before bulk command execution timeout, 30 (the default) /// Inserted entities - public static async Task> BulkInsertAndSelectAsync(this SqlConnection connection, IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) + public static async Task> BulkInsertAndSelectAsync(this SqlConnection connection, + IEnumerable data, SqlTransaction transaction = null, int batchSize = 0, int bulkCopyTimeout = 30) { var type = typeof(T); var tableName = TableMapper.GetTableName(type); var allProperties = PropertiesCache.TypePropertiesCache(type); var keyProperties = PropertiesCache.KeyPropertiesCache(type); + var explicitKeyProperties = PropertiesCache.ExplicitKeyPropertiesCache(type); var computedProperties = PropertiesCache.ComputedPropertiesCache(type); var columns = PropertiesCache.GetColumnNamesCache(type); @@ -195,28 +225,33 @@ public static async Task> BulkInsertAndSelectAsync(this SqlCon return dataList; } - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); + var allPropertiesExceptKeyAndComputed = + GetAllPropertiesExceptKeyAndComputed(allProperties, keyProperties, explicitKeyProperties, computedProperties); - var keyPropertiesString = GetColumnsStringSqlServer(keyProperties,columns); - var keyPropertiesInsertedString = GetColumnsStringSqlServer(keyProperties,columns, "inserted."); - var allPropertiesExceptKeyAndComputedString = GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed,columns); + var keyPropertiesString = GetColumnsStringSqlServer(keyProperties, columns); + var keyPropertiesInsertedString = GetColumnsStringSqlServer(keyProperties, columns, "inserted."); + var allPropertiesExceptKeyAndComputedString = + GetColumnsStringSqlServer(allPropertiesExceptKeyAndComputed, columns); var allPropertiesString = GetColumnsStringSqlServer(allProperties, columns, "target."); var tempToBeInserted = $"#TempInsert_{tableName}".Replace(".", string.Empty); var tempInsertedWithIdentity = $"@TempInserted_{tableName}".Replace(".", string.Empty); - await connection.ExecuteAsync($@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", null, transaction); + await connection.ExecuteAsync( + $@"SELECT TOP 0 {allPropertiesExceptKeyAndComputedString} INTO {tempToBeInserted} FROM {FormatTableName(tableName)} target WITH(NOLOCK);", + null, transaction); using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction)) { bulkCopy.BulkCopyTimeout = bulkCopyTimeout; bulkCopy.BatchSize = batchSize; bulkCopy.DestinationTableName = tempToBeInserted; - await bulkCopy.WriteToServerAsync(ToDataTable(data, allPropertiesExceptKeyAndComputed).CreateDataReader()); + await bulkCopy.WriteToServerAsync(ToDataTable(data, allPropertiesExceptKeyAndComputed) + .CreateDataReader()); } - var table = string.Join(", ", keyProperties.Select(k => $"[{k.Name }] bigint")); - var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{k.Name }] = ins.[{k.Name }]")); + var table = string.Join(", ", keyProperties.Select(k => $"[{k.Name}] bigint")); + var joinOn = string.Join(" AND ", keyProperties.Select(k => $"target.[{k.Name}] = ins.[{k.Name}]")); return await connection.QueryAsync($@" DECLARE {tempInsertedWithIdentity} TABLE ({table}) INSERT INTO {FormatTableName(tableName)}({allPropertiesExceptKeyAndComputedString}) @@ -229,16 +264,19 @@ public static async Task> BulkInsertAndSelectAsync(this SqlCon DROP TABLE {tempToBeInserted};", null, transaction); } - private static string GetColumnsStringSqlServer(IEnumerable properties, IReadOnlyDictionary columnNames, string tablePrefix = null) + private static string GetColumnsStringSqlServer(IEnumerable properties, + IReadOnlyDictionary columnNames, string tablePrefix = null) { 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]}] as [{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]; @@ -258,8 +296,9 @@ private static DataTable ToDataTable(IEnumerable data, IList for (var i = 0; i < properties.Count; i++) { // Nullable types are not supported. - var propertyNonNullType = Nullable.GetUnderlyingType(properties[i].PropertyType) ?? properties[i].PropertyType; - dataTable.Columns.Add(properties[i].Name, typeCasts[i] == null ? propertyNonNullType : typeCasts[i]); + var propertyNonNullType = + Nullable.GetUnderlyingType(properties[i].PropertyType) ?? properties[i].PropertyType; + dataTable.Columns.Add(properties[i].Name, typeCasts[i] == null ? propertyNonNullType : typeCasts[i]); } foreach (var item in data) @@ -294,4 +333,4 @@ internal static string FormatTableName(string table) return $"[{parts[0]}].[{parts[1]}]"; } } -} +} \ No newline at end of file