Skip to content

Commit 1811625

Browse files
committed
TempTableBuilder
1 parent 36e88a5 commit 1811625

File tree

5 files changed

+71
-100
lines changed

5 files changed

+71
-100
lines changed

src/EntityFrameworkCore.SqlServer.SimpleBulks/TempTable/DbContextAsyncExtensions.cs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,22 @@ public static Task<string> CreateTempTableAsync<T>(this DbContext dbContext, IEn
1515
var connection = dbContext.GetSqlConnection();
1616
var transaction = dbContext.GetCurrentSqlTransaction();
1717

18-
var isEntityType = dbContext.IsEntityType(typeof(T));
19-
20-
IReadOnlyDictionary<string, string> columnNameMappings = null;
21-
IReadOnlyDictionary<string, string> columnTypeMappings = null;
22-
23-
if (isEntityType)
24-
{
25-
columnNameMappings = dbContext.GetColumnNames(typeof(T));
26-
columnTypeMappings = dbContext.GetColumnTypes(typeof(T));
27-
}
28-
2918
return new TempTableBuilder<T>(connection, transaction)
30-
.WithData(data)
3119
.WithColumns(columnNamesSelector)
32-
.WithDbColumnMappings(columnNameMappings)
33-
.WithDbColumnTypeMappings(columnTypeMappings)
34-
.WithValueConverters(isEntityType ? dbContext.GetValueConverters(typeof(T)) : null)
20+
.WithMappingContext(dbContext.GetMappingContext(typeof(T)))
3521
.ConfigureTempTableOptions(configureOptions)
36-
.ExecuteAsync(cancellationToken);
22+
.ExecuteAsync(data, cancellationToken);
3723
}
3824

3925
public static Task<string> CreateTempTableAsync<T>(this DbContext dbContext, IEnumerable<T> data, IEnumerable<string> columnNames, Action<TempTableOptions> configureOptions = null, CancellationToken cancellationToken = default)
4026
{
4127
var connection = dbContext.GetSqlConnection();
4228
var transaction = dbContext.GetCurrentSqlTransaction();
4329

44-
var isEntityType = dbContext.IsEntityType(typeof(T));
45-
46-
IReadOnlyDictionary<string, string> columnNameMappings = null;
47-
IReadOnlyDictionary<string, string> columnTypeMappings = null;
48-
49-
if (isEntityType)
50-
{
51-
columnNameMappings = dbContext.GetColumnNames(typeof(T));
52-
columnTypeMappings = dbContext.GetColumnTypes(typeof(T));
53-
}
54-
5530
return new TempTableBuilder<T>(connection, transaction)
56-
.WithData(data)
5731
.WithColumns(columnNames)
58-
.WithDbColumnMappings(columnNameMappings)
59-
.WithDbColumnTypeMappings(columnTypeMappings)
60-
.WithValueConverters(isEntityType ? dbContext.GetValueConverters(typeof(T)) : null)
32+
.WithMappingContext(dbContext.GetMappingContext(typeof(T)))
6133
.ConfigureTempTableOptions(configureOptions)
62-
.ExecuteAsync(cancellationToken);
34+
.ExecuteAsync(data, cancellationToken);
6335
}
6436
}

src/EntityFrameworkCore.SqlServer.SimpleBulks/TempTable/DbContextExtensions.cs

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,22 @@ public static string CreateTempTable<T>(this DbContext dbContext, IEnumerable<T>
1313
var connection = dbContext.GetSqlConnection();
1414
var transaction = dbContext.GetCurrentSqlTransaction();
1515

16-
var isEntityType = dbContext.IsEntityType(typeof(T));
17-
18-
IReadOnlyDictionary<string, string> columnNameMappings = null;
19-
IReadOnlyDictionary<string, string> columnTypeMappings = null;
20-
21-
if (isEntityType)
22-
{
23-
columnNameMappings = dbContext.GetColumnNames(typeof(T));
24-
columnTypeMappings = dbContext.GetColumnTypes(typeof(T));
25-
}
26-
2716
return new TempTableBuilder<T>(connection, transaction)
28-
.WithData(data)
2917
.WithColumns(columnNamesSelector)
30-
.WithDbColumnMappings(columnNameMappings)
31-
.WithDbColumnTypeMappings(columnTypeMappings)
32-
.WithValueConverters(isEntityType ? dbContext.GetValueConverters(typeof(T)) : null)
18+
.WithMappingContext(dbContext.GetMappingContext(typeof(T)))
3319
.ConfigureTempTableOptions(configureOptions)
34-
.Execute();
20+
.Execute(data);
3521
}
3622

3723
public static string CreateTempTable<T>(this DbContext dbContext, IEnumerable<T> data, IEnumerable<string> columnNames, Action<TempTableOptions> configureOptions = null)
3824
{
3925
var connection = dbContext.GetSqlConnection();
4026
var transaction = dbContext.GetCurrentSqlTransaction();
4127

42-
var isEntityType = dbContext.IsEntityType(typeof(T));
43-
44-
IReadOnlyDictionary<string, string> columnNameMappings = null;
45-
IReadOnlyDictionary<string, string> columnTypeMappings = null;
46-
47-
if (isEntityType)
48-
{
49-
columnNameMappings = dbContext.GetColumnNames(typeof(T));
50-
columnTypeMappings = dbContext.GetColumnTypes(typeof(T));
51-
}
52-
5328
return new TempTableBuilder<T>(connection, transaction)
54-
.WithData(data)
5529
.WithColumns(columnNames)
56-
.WithDbColumnMappings(columnNameMappings)
57-
.WithDbColumnTypeMappings(columnTypeMappings)
58-
.WithValueConverters(isEntityType ? dbContext.GetValueConverters(typeof(T)) : null)
30+
.WithMappingContext(dbContext.GetMappingContext(typeof(T)))
5931
.ConfigureTempTableOptions(configureOptions)
60-
.Execute();
32+
.Execute(data);
6133
}
6234
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
2+
using System.Collections.Generic;
3+
4+
namespace EntityFrameworkCore.SqlServer.SimpleBulks.TempTable;
5+
6+
public class MappingContext
7+
{
8+
public IReadOnlyDictionary<string, string> ColumnNameMappings { get; init; }
9+
10+
public IReadOnlyDictionary<string, string> ColumnTypeMappings { get; init; }
11+
12+
public IReadOnlyDictionary<string, ValueConverter> ValueConverters { get; init; }
13+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using EntityFrameworkCore.SqlServer.SimpleBulks.Extensions;
2+
using Microsoft.EntityFrameworkCore;
3+
using System;
4+
using System.Collections.Concurrent;
5+
6+
namespace EntityFrameworkCore.SqlServer.SimpleBulks.TempTable;
7+
8+
internal static class MappingContextCache
9+
{
10+
private readonly record struct CacheKey(Type DbContextType, Type EntityType);
11+
12+
private static readonly ConcurrentDictionary<CacheKey, MappingContext> _mappingContextCache = [];
13+
14+
public static MappingContext GetMappingContext(this DbContext dbContext, Type type)
15+
{
16+
var cacheKey = new CacheKey(dbContext.GetType(), type);
17+
18+
return _mappingContextCache.GetOrAdd(cacheKey, (key) =>
19+
{
20+
var isEntityType = dbContext.IsEntityType(type);
21+
22+
if (!isEntityType)
23+
{
24+
return new MappingContext();
25+
}
26+
27+
var mappingContext = new MappingContext
28+
{
29+
ColumnNameMappings = dbContext.GetColumnNames(key.EntityType),
30+
ColumnTypeMappings = dbContext.GetColumnTypes(key.EntityType),
31+
ValueConverters = dbContext.GetValueConverters(key.EntityType)
32+
};
33+
return mappingContext;
34+
});
35+
}
36+
}

src/EntityFrameworkCore.SqlServer.SimpleBulks/TempTable/TempTableBuilder.cs

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using EntityFrameworkCore.SqlServer.SimpleBulks.Extensions;
22
using Microsoft.Data.SqlClient;
3-
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
43
using System;
54
using System.Collections.Generic;
65
using System.Linq.Expressions;
@@ -11,11 +10,8 @@ namespace EntityFrameworkCore.SqlServer.SimpleBulks.TempTable;
1110

1211
public class TempTableBuilder<T>
1312
{
14-
private IEnumerable<T> _data;
1513
private IEnumerable<string> _columnNames;
16-
private IReadOnlyDictionary<string, string> _columnNameMappings;
17-
private IReadOnlyDictionary<string, string> _columnTypeMappings;
18-
private IReadOnlyDictionary<string, ValueConverter> _valueConverters;
14+
private MappingContext _mappingContext;
1915
private TempTableOptions _options;
2016
private readonly SqlConnection _connection;
2117
private readonly SqlTransaction _transaction;
@@ -31,12 +27,6 @@ public TempTableBuilder(SqlConnection connection, SqlTransaction transaction)
3127
_transaction = transaction;
3228
}
3329

34-
public TempTableBuilder<T> WithData(IEnumerable<T> data)
35-
{
36-
_data = data;
37-
return this;
38-
}
39-
4030
public TempTableBuilder<T> WithColumns(IEnumerable<string> columnNames)
4131
{
4232
_columnNames = columnNames;
@@ -49,21 +39,9 @@ public TempTableBuilder<T> WithColumns(Expression<Func<T, object>> columnNamesSe
4939
return this;
5040
}
5141

52-
public TempTableBuilder<T> WithDbColumnMappings(IReadOnlyDictionary<string, string> columnNameMappings)
42+
public TempTableBuilder<T> WithMappingContext(MappingContext mappingContext)
5343
{
54-
_columnNameMappings = columnNameMappings;
55-
return this;
56-
}
57-
58-
public TempTableBuilder<T> WithDbColumnTypeMappings(IReadOnlyDictionary<string, string> columnTypeMappings)
59-
{
60-
_columnTypeMappings = columnTypeMappings;
61-
return this;
62-
}
63-
64-
public TempTableBuilder<T> WithValueConverters(IReadOnlyDictionary<string, ValueConverter> valueConverters)
65-
{
66-
_valueConverters = valueConverters;
44+
_mappingContext = mappingContext;
6745
return this;
6846
}
6947

@@ -79,24 +57,24 @@ public TempTableBuilder<T> ConfigureTempTableOptions(Action<TempTableOptions> co
7957

8058
private string GetTableName()
8159
{
82-
if (!string.IsNullOrWhiteSpace(_options.TableName))
60+
if (!string.IsNullOrWhiteSpace(_options?.TableName))
8361
{
8462
return _options.TableName;
8563
}
8664

87-
if (!string.IsNullOrWhiteSpace(_options.PrefixName))
65+
if (!string.IsNullOrWhiteSpace(_options?.PrefixName))
8866
{
8967
return _options.PrefixName + "-" + Guid.NewGuid();
9068
}
9169

9270
return Guid.NewGuid().ToString();
9371
}
9472

95-
public string Execute()
73+
public string Execute(IEnumerable<T> data)
9674
{
9775
var tempTableName = $"[#{GetTableName()}]";
98-
var dataTable = _data.ToDataTable(_columnNames, valueConverters: _valueConverters);
99-
var sqlCreateTempTable = dataTable.GenerateTableDefinition(tempTableName, _columnNameMappings, _columnTypeMappings);
76+
var dataTable = data.ToDataTable(_columnNames, valueConverters: _mappingContext?.ValueConverters);
77+
var sqlCreateTempTable = dataTable.GenerateTableDefinition(tempTableName, _mappingContext?.ColumnNameMappings, _mappingContext?.ColumnTypeMappings);
10078

10179
Log($"Begin creating temp table:{Environment.NewLine}{sqlCreateTempTable}");
10280

@@ -110,7 +88,7 @@ public string Execute()
11088

11189
Log($"Begin executing SqlBulkCopy. TableName: {tempTableName}");
11290

113-
dataTable.SqlBulkCopy(tempTableName, _columnNameMappings, _connection, _transaction);
91+
dataTable.SqlBulkCopy(tempTableName, _mappingContext?.ColumnNameMappings, _connection, _transaction);
11492

11593
Log("End executing SqlBulkCopy.");
11694

@@ -122,15 +100,15 @@ private void Log(string message)
122100
_options?.LogTo?.Invoke($"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [TempTable]: {message}");
123101
}
124102

125-
public async Task<string> ExecuteAsync(CancellationToken cancellationToken = default)
103+
public async Task<string> ExecuteAsync(IEnumerable<T> data, CancellationToken cancellationToken = default)
126104
{
127105
var tempTableName = $"[#{GetTableName()}]";
128-
var dataTable = await _data.ToDataTableAsync(_columnNames, valueConverters: _valueConverters, cancellationToken: cancellationToken);
129-
var sqlCreateTempTable = dataTable.GenerateTableDefinition(tempTableName, _columnNameMappings, _columnTypeMappings);
106+
var dataTable = await data.ToDataTableAsync(_columnNames, valueConverters: _mappingContext?.ValueConverters, cancellationToken: cancellationToken);
107+
var sqlCreateTempTable = dataTable.GenerateTableDefinition(tempTableName, _mappingContext?.ColumnNameMappings, _mappingContext?.ColumnTypeMappings);
130108

131109
Log($"Begin creating temp table:{Environment.NewLine}{sqlCreateTempTable}");
132110

133-
_connection.EnsureOpen();
111+
await _connection.EnsureOpenAsync(cancellationToken);
134112
using (var createTempTableCommand = _connection.CreateTextCommand(_transaction, sqlCreateTempTable))
135113
{
136114
await createTempTableCommand.ExecuteNonQueryAsync(cancellationToken);
@@ -140,7 +118,7 @@ public async Task<string> ExecuteAsync(CancellationToken cancellationToken = def
140118

141119
Log($"Begin executing SqlBulkCopy. TableName: {tempTableName}");
142120

143-
await dataTable.SqlBulkCopyAsync(tempTableName, _columnNameMappings, _connection, _transaction, cancellationToken: cancellationToken);
121+
await dataTable.SqlBulkCopyAsync(tempTableName, _mappingContext?.ColumnNameMappings, _connection, _transaction, cancellationToken: cancellationToken);
144122

145123
Log("End executing SqlBulkCopy.");
146124

0 commit comments

Comments
 (0)