Skip to content

Commit c16dee5

Browse files
author
fabien.menager
committed
Refactor bulk insert provider classes to use generic SQL dialect builders and streamline table name escaping
1 parent c52932b commit c16dee5

10 files changed

Lines changed: 410 additions & 456 deletions

File tree

src/EntityFrameworkCore.ExecuteInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
using System.Data.Common;
2-
using System.Linq.Expressions;
3-
using System.Text;
4-
5-
using EntityFrameworkCore.ExecuteInsert.OnConflict;
62

73
using Microsoft.EntityFrameworkCore;
8-
using Microsoft.EntityFrameworkCore.Metadata;
94

105
using Npgsql;
116

127
namespace EntityFrameworkCore.ExecuteInsert.PostgreSql;
138

14-
public class PostgreSqlBulkInsertProvider : BulkInsertProviderBase
9+
public class PostgreSqlBulkInsertProvider : BulkInsertProviderBase<PostgreSqlDialectBuilder>
1510
{
16-
public override string OpenDelimiter => "\"";
17-
public override string CloseDelimiter => "\"";
18-
1911
//language=sql
2012
protected override string CreateTableCopySql => "CREATE TEMPORARY TABLE {0} AS TABLE {1} WITH NO DATA;";
2113

@@ -29,75 +21,6 @@ private string GetBinaryImportCommand(DbContext context, Type entityType, string
2921
return $"COPY {tableName} ({string.Join(", ", columns)}) FROM STDIN (FORMAT BINARY)";
3022
}
3123

32-
protected override string BuildInsertSelectQuery<T>(string tableName,
33-
string targetTableName,
34-
IProperty[] insertedProperties,
35-
IProperty[] properties,
36-
BulkInsertOptions options, OnConflictOptions? onConflict = null)
37-
{
38-
var insertedColumns = insertedProperties.Select(p => Escape(p.GetColumnName()));
39-
var insertedColumnList = string.Join(", ", insertedColumns);
40-
41-
var returnedColumns = properties.Select(p => $"{Escape(p.GetColumnName())} AS {Escape(p.Name)}");
42-
var columnList = string.Join(", ", returnedColumns);
43-
44-
var q = new StringBuilder();
45-
46-
if (options.MoveRows)
47-
{
48-
q.AppendLine($"""
49-
WITH moved_rows AS (
50-
DELETE FROM {tableName}
51-
RETURNING {insertedColumnList}
52-
)
53-
""");
54-
tableName = "moved_rows";
55-
}
56-
57-
q.AppendLine($"""
58-
INSERT INTO {targetTableName} ({insertedColumnList})
59-
SELECT {insertedColumnList}
60-
FROM {tableName}
61-
WHERE TRUE
62-
""");
63-
64-
if (onConflict is OnConflictOptions<T> onConflictTyped)
65-
{
66-
q.AppendLine("ON CONFLICT");
67-
68-
if (onConflictTyped.Update != null)
69-
{
70-
if (onConflictTyped.Match != null)
71-
{
72-
q.AppendLine($"({string.Join(", ", GetColumns(onConflictTyped.Match).Select(Escape))})");
73-
}
74-
75-
if (onConflictTyped.Update != null)
76-
{
77-
q.AppendLine($"DO UPDATE SET {string.Join(", ", GetUpdates(onConflictTyped.Update))}");
78-
}
79-
80-
if (onConflictTyped.Condition != null)
81-
{
82-
q.AppendLine($"WHERE {onConflictTyped.Condition}");
83-
}
84-
}
85-
else
86-
{
87-
q.AppendLine("DO NOTHING");
88-
}
89-
}
90-
91-
if (columnList.Length != 0)
92-
{
93-
q.AppendLine($"RETURNING {columnList}");
94-
}
95-
96-
q.AppendLine(";");
97-
98-
return q.ToString();
99-
}
100-
10124
protected override async Task BulkImport<T>(DbContext context, DbConnection connection, IEnumerable<T> entities,
10225
string tableName, PropertyAccessor[] properties, BulkInsertOptions options, CancellationToken ctk) where T : class
10326
{
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace EntityFrameworkCore.ExecuteInsert.PostgreSql;
2+
3+
public class PostgreSqlDialectBuilder : SqlDialectBuilder
4+
{
5+
protected override string OpenDelimiter => "\"";
6+
protected override string CloseDelimiter => "\"";
7+
}
Lines changed: 2 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
using System.Data.Common;
2-
using System.Linq.Expressions;
3-
using System.Text;
4-
5-
using EntityFrameworkCore.ExecuteInsert.Helpers;
6-
using EntityFrameworkCore.ExecuteInsert.OnConflict;
72

83
using Microsoft.Data.SqlClient;
94
using Microsoft.EntityFrameworkCore;
10-
using Microsoft.EntityFrameworkCore.Metadata;
115

126
namespace EntityFrameworkCore.ExecuteInsert.SqlServer;
137

14-
public class SqlServerBulkInsertProvider : BulkInsertProviderBase
8+
public class SqlServerBulkInsertProvider : BulkInsertProviderBase<SqlServerDialectBuilder>
159
{
16-
public override string OpenDelimiter => "[";
17-
public override string CloseDelimiter => "]";
18-
1910
//language=sql
2011
protected override string CreateTableCopySql => "SELECT {2} INTO {0} FROM {1} WHERE 1 = 0;";
2112

@@ -37,94 +28,11 @@ protected override async Task BulkImport<T>(DbContext context, DbConnection conn
3728

3829
foreach (var prop in properties)
3930
{
40-
bulkCopy.ColumnMappings.Add(prop.Name, DatabaseHelper.GetEscapedColumnName(prop.Name, OpenDelimiter, CloseDelimiter));
31+
bulkCopy.ColumnMappings.Add(prop.Name, SqlDialect.Escape(prop.Name));
4132
}
4233

4334
await bulkCopy.WriteToServerAsync(new EnumerableDataReader<T>(entities, properties), ctk);
4435

4536
await t.CommitAsync(ctk);
4637
}
47-
48-
protected override string BuildInsertSelectQuery<T>(string tableName,
49-
string targetTableName,
50-
IProperty[] insertedProperties,
51-
IProperty[] properties,
52-
BulkInsertOptions options, OnConflictOptions? onConflict = null)
53-
{
54-
var insertedColumns = insertedProperties.Select(p => Escape(p.GetColumnName())).ToArray();
55-
var insertedColumnList = string.Join(", ", insertedColumns);
56-
57-
var returnedColumns = properties.Select(p => $"INSERTED.{p.GetColumnName()} AS [{p.Name}]");
58-
var columnList = string.Join(", ", returnedColumns);
59-
60-
var q = new StringBuilder();
61-
62-
// if (options.MoveRows)
63-
// {
64-
// var deletedColumnList = string.Join(", ", insertedColumns.Select(c => $"DELETED.{c}"));
65-
//
66-
// q.AppendLine($"""
67-
// DELETE FROM {tableName}
68-
// OUTPUT {deletedColumnList}
69-
// """);
70-
// }
71-
72-
// Merge handling
73-
if (onConflict is OnConflictOptions<T> onConflictTyped && onConflictTyped.Match != null)
74-
{
75-
var matchColumns = GetColumns(onConflictTyped.Match);
76-
var matchOn = string.Join(" AND ",
77-
matchColumns.Select(col => $"TARGET.{Escape(col)} = SOURCE.{Escape(col)}"));
78-
79-
var updateSet = onConflictTyped.Update != null
80-
? string.Join(", ", GetUpdates(onConflictTyped.Update))
81-
: null;
82-
83-
q.AppendLine($"MERGE INTO {targetTableName} AS TARGET");
84-
q.AppendLine(
85-
$"USING (SELECT {string.Join(", ", insertedColumns)} FROM {tableName}) AS SOURCE ({insertedColumnList})");
86-
q.AppendLine($"ON {matchOn}");
87-
88-
if (updateSet != null)
89-
{
90-
q.AppendLine($"WHEN MATCHED THEN UPDATE SET {updateSet}");
91-
}
92-
93-
q.AppendLine(
94-
$"WHEN NOT MATCHED THEN INSERT ({insertedColumnList}) VALUES ({string.Join(", ", insertedColumns.Select(c => $"SOURCE.{c}"))})");
95-
96-
if (columnList.Length != 0)
97-
{
98-
q.AppendLine($"OUTPUT {columnList}");
99-
}
100-
}
101-
102-
// No conflict handling
103-
else
104-
{
105-
q.AppendLine($"INSERT INTO {targetTableName} ({insertedColumnList})");
106-
107-
if (columnList.Length != 0)
108-
{
109-
q.AppendLine($"OUTPUT {columnList}");
110-
}
111-
112-
q.AppendLine($"""
113-
SELECT {insertedColumnList}
114-
FROM {tableName}
115-
""");
116-
}
117-
118-
q.AppendLine(";");
119-
120-
return q.ToString();
121-
}
122-
123-
protected override string GetExcludedColumnName(MemberExpression member)
124-
{
125-
var prefix = "SOURCE";
126-
return $"{prefix}.{Escape(member.Member.Name)}";
127-
}
128-
129-
protected override string ConcatOperator => "+";
13038
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System.Linq.Expressions;
2+
using System.Text;
3+
4+
using EntityFrameworkCore.ExecuteInsert.OnConflict;
5+
6+
using Microsoft.EntityFrameworkCore;
7+
using Microsoft.EntityFrameworkCore.Metadata;
8+
9+
namespace EntityFrameworkCore.ExecuteInsert.SqlServer;
10+
11+
public class SqlServerDialectBuilder : SqlDialectBuilder
12+
{
13+
protected override string OpenDelimiter => "[";
14+
protected override string CloseDelimiter => "]";
15+
16+
public override string BuildMoveDataSql<T>(string source,
17+
string target,
18+
IProperty[] insertedProperties,
19+
IProperty[] properties,
20+
BulkInsertOptions options, OnConflictOptions? onConflict = null)
21+
{
22+
var insertedColumns = insertedProperties.Select(p => Escape(p.GetColumnName())).ToArray();
23+
var insertedColumnList = string.Join(", ", insertedColumns);
24+
25+
var returnedColumns = properties.Select(p => $"INSERTED.{p.GetColumnName()} AS [{p.Name}]");
26+
var columnList = string.Join(", ", returnedColumns);
27+
28+
var q = new StringBuilder();
29+
30+
// if (options.MoveRows)
31+
// {
32+
// var deletedColumnList = string.Join(", ", insertedColumns.Select(c => $"DELETED.{c}"));
33+
//
34+
// q.AppendLine($"""
35+
// DELETE FROM {tableName}
36+
// OUTPUT {deletedColumnList}
37+
// """);
38+
// }
39+
40+
// Merge handling
41+
if (onConflict is OnConflictOptions<T> onConflictTyped && onConflictTyped.Match != null)
42+
{
43+
var matchColumns = GetColumns(onConflictTyped.Match);
44+
var matchOn = string.Join(" AND ",
45+
matchColumns.Select(col => $"TARGET.{Escape(col)} = SOURCE.{Escape(col)}"));
46+
47+
var updateSet = onConflictTyped.Update != null
48+
? string.Join(", ", GetUpdates(onConflictTyped.Update))
49+
: null;
50+
51+
q.AppendLine($"MERGE INTO {target} AS TARGET");
52+
q.AppendLine(
53+
$"USING (SELECT {string.Join(", ", insertedColumns)} FROM {source}) AS SOURCE ({insertedColumnList})");
54+
q.AppendLine($"ON {matchOn}");
55+
56+
if (updateSet != null)
57+
{
58+
q.AppendLine($"WHEN MATCHED THEN UPDATE SET {updateSet}");
59+
}
60+
61+
q.AppendLine(
62+
$"WHEN NOT MATCHED THEN INSERT ({insertedColumnList}) VALUES ({string.Join(", ", insertedColumns.Select(c => $"SOURCE.{c}"))})");
63+
64+
if (columnList.Length != 0)
65+
{
66+
q.AppendLine($"OUTPUT {columnList}");
67+
}
68+
}
69+
70+
// No conflict handling
71+
else
72+
{
73+
q.AppendLine($"INSERT INTO {target} ({insertedColumnList})");
74+
75+
if (columnList.Length != 0)
76+
{
77+
q.AppendLine($"OUTPUT {columnList}");
78+
}
79+
80+
q.AppendLine($"""
81+
SELECT {insertedColumnList}
82+
FROM {source}
83+
""");
84+
}
85+
86+
q.AppendLine(";");
87+
88+
return q.ToString();
89+
}
90+
91+
protected override string GetExcludedColumnName(MemberExpression member)
92+
{
93+
var prefix = "SOURCE";
94+
return $"{prefix}.{Escape(member.Member.Name)}";
95+
}
96+
97+
protected override string ConcatOperator => "+";
98+
}

0 commit comments

Comments
 (0)