Skip to content

Commit 4ce4dfa

Browse files
author
fabien.menager
committed
Add basic support for Oracle
1 parent 8606f69 commit 4ce4dfa

16 files changed

Lines changed: 74 additions & 78 deletions

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PhenX.EntityFrameworkCore.BulkInsert
22

3-
A high-performance, provider-agnostic bulk insert extension for Entity Framework Core 8+. Supports SQL Server, PostgreSQL, SQLite and MySQL.
3+
A high-performance, provider-agnostic bulk insert extension for Entity Framework Core 8+. Supports SQL Server, PostgreSQL, SQLite, MySQL and Oracle.
44

55
Its main purpose is to provide a fast way to perform simple bulk inserts in Entity Framework Core applications.
66

@@ -21,6 +21,7 @@ but they are in [the roadmap](#roadmap).
2121
| `PhenX.EntityFrameworkCore.BulkInsert.PostgreSql` | For PostgreSQL | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql) |
2222
| `PhenX.EntityFrameworkCore.BulkInsert.Sqlite` | For SQLite | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.Sqlite.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.Sqlite) |
2323
| `PhenX.EntityFrameworkCore.BulkInsert.MySql` | For MySql | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.Sqlite.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.MySql) |
24+
| `PhenX.EntityFrameworkCore.BulkInsert.Oracle` | For Oracle | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.Oracle.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.Oracle) |
2425
| `PhenX.EntityFrameworkCore.BulkInsert` | Common library | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert) |
2526

2627
## Installation
@@ -39,6 +40,9 @@ Install-Package PhenX.EntityFrameworkCore.BulkInsert.Sqlite
3940

4041
# For MySql
4142
Install-Package PhenX.EntityFrameworkCore.BulkInsert.MySql
43+
44+
# For Oracle
45+
Install-Package PhenX.EntityFrameworkCore.BulkInsert.Oracle
4246
```
4347

4448
## Usage
@@ -58,6 +62,8 @@ services.AddDbContext<MyDbContext>(options =>
5862
.UseBulkInsertSqlite()
5963
// OR
6064
.UseBulkInsertMySql()
65+
// OR
66+
.UseBulkInsertOracle()
6167
;
6268
});
6369
```
@@ -148,6 +154,10 @@ MySQL results with 500 000 rows :
148154

149155
![bench-mysql.png](https://raw.githubusercontent.com/PhenX/PhenX.EntityFrameworkCore.BulkInsert/refs/heads/main/images/bench-mysql.png)
150156

157+
Oracle results with 500 000 rows :
158+
159+
![bench-oracle.png](https://raw.githubusercontent.com/PhenX/PhenX.EntityFrameworkCore.BulkInsert/refs/heads/main/images/bench-oracle.png)
160+
151161
## Contributing
152162

153163
Contributions are welcome! Please open issues or submit pull requests for bug fixes, features, or documentation improvements.

images/bench-oracle.png

23.8 KB
Loading

src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,40 @@
66
using Oracle.ManagedDataAccess.Client;
77

88
using PhenX.EntityFrameworkCore.BulkInsert.Metadata;
9+
using PhenX.EntityFrameworkCore.BulkInsert.Options;
910

1011
namespace PhenX.EntityFrameworkCore.BulkInsert.Oracle;
1112

1213
[UsedImplicitly]
1314
internal class OracleBulkInsertProvider(ILogger<OracleBulkInsertProvider>? logger) : BulkInsertProviderBase<OracleDialectBuilder, OracleBulkInsertOptions>(logger)
1415
{
15-
//language=sql
1616
/// <inheritdoc />
17-
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT IDENTITY PRIMARY KEY;";
17+
protected override string BulkInsertId => "ROWID";
1818

1919
/// <inheritdoc />
20-
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
20+
protected override string AddTableCopyBulkInsertId => ""; // No need to add an ID column in Oracle
21+
22+
/// <inheritdoc />
23+
protected override string GetTempTableName(string tableName) => $"#temp_bulk_insert_{Guid.NewGuid().ToString("N")[..8]}";
2124

2225
protected override OracleBulkInsertOptions CreateDefaultOptions() => new()
2326
{
2427
BatchSize = 50_000,
25-
Converters = [OracleGeometryConverter.Instance]
2628
};
2729

30+
/// <inheritdoc />
31+
protected override IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
32+
bool sync,
33+
DbContext context,
34+
TableMetadata tableInfo,
35+
IEnumerable<T> entities,
36+
OracleBulkInsertOptions options,
37+
OnConflictOptions<T>? onConflict,
38+
CancellationToken ctk)
39+
{
40+
throw new NotSupportedException("Provider does not support returning entities.");
41+
}
42+
2843
/// <inheritdoc />
2944
protected override Task BulkInsert<T>(
3045
bool sync,
@@ -40,7 +55,7 @@ protected override Task BulkInsert<T>(
4055

4156
using var bulkCopy = new OracleBulkCopy(connection, options.CopyOptions);
4257

43-
bulkCopy.DestinationTableName = tableName;
58+
bulkCopy.DestinationTableName = tableInfo.QuotedTableName;
4459
bulkCopy.BatchSize = options.BatchSize;
4560
bulkCopy.BulkCopyTimeout = options.GetCopyTimeoutInSeconds();
4661

@@ -55,4 +70,10 @@ protected override Task BulkInsert<T>(
5570

5671
return Task.CompletedTask;
5772
}
73+
74+
/// <inheritdoc />
75+
protected override Task DropTempTableAsync(bool sync, DbContext dbContext, string tableName)
76+
{
77+
return ExecuteAsync(sync, dbContext, $"DROP TABLE IF EXISTS {tableName}", default);
78+
}
5879
}

src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleDialectBuilder.cs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ public override string BuildMoveDataSql<T>(
4444
{
4545
var q = new StringBuilder();
4646

47-
if (options.CopyGeneratedColumns)
48-
{
49-
q.AppendLine($"SET IDENTITY_INSERT {target.QuotedTableName} ON;");
50-
}
51-
5247
// Merge handling
5348
if (onConflict is OnConflictOptions<T> onConflictTyped)
5449
{
@@ -109,28 +104,24 @@ public override string BuildMoveDataSql<T>(
109104
q.Append($"INSERT INTO {target.QuotedTableName} (");
110105
q.AppendColumns(insertedColumns);
111106
q.AppendLine(")");
112-
113-
if (returnedColumns.Count != 0)
114-
{
115-
q.Append("OUTPUT ");
116-
q.AppendJoin(", ", returnedColumns, (b, col) => b.Append($"INSERTED.{col.QuotedColumName} AS {col.QuotedColumName}"));
117-
q.AppendLine();
118-
}
119-
120107
q.Append("SELECT ");
121108
q.AppendColumns(insertedColumns);
122109
q.AppendLine();
123110
q.Append($"FROM {source}");
124111
q.AppendLine();
112+
113+
if (returnedColumns.Count != 0)
114+
{
115+
q.Append("RETURNING ");
116+
q.AppendJoin(", ", returnedColumns, (b, col) => b.Append(col.QuotedColumName));
117+
q.Append(" INTO ");
118+
q.AppendJoin(", ", returnedColumns, (b, col) => b.Append($":{col.ColumnName}"));
119+
q.AppendLine();
120+
}
125121
}
126122

127123
q.AppendLine(";");
128124

129-
if (options.CopyGeneratedColumns)
130-
{
131-
q.AppendLine($"SET IDENTITY_INSERT {target.QuotedTableName} OFF;");
132-
}
133-
134125
var result = q.ToString();
135126
return result;
136127
}

src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleGeometryConverter.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/PhenX.EntityFrameworkCore.BulkInsert.Oracle.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
</ItemGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
98
<PackageReference Include="Oracle.EntityFrameworkCore" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.23.80" />
109
<PackageReference Include="Oracle.EntityFrameworkCore" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.23.80" />
1110
</ItemGroup>

src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal class SqliteBulkInsertProvider(ILogger<SqliteBulkInsertProvider>? logge
2424
protected override string AddTableCopyBulkInsertId => "--"; // No need to add an ID column in SQLite
2525

2626
/// <inheritdoc />
27-
protected override string GetTempTableName(string tableName) => $"_temp_bulk_insert_test_entity_{Guid.NewGuid():N}";
27+
protected override string GetTempTableName(string tableName) => $"_temp_bulk_insert_{Guid.NewGuid():N}";
2828

2929
/// <inheritdoc />
3030
protected override BulkInsertOptions CreateDefaultOptions() => new()

src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ protected virtual async Task AddBulkInsertIdColumn<T>(
185185
string tempTableName,
186186
CancellationToken ctk) where T : class
187187
{
188+
if (string.IsNullOrEmpty(AddTableCopyBulkInsertId))
189+
{
190+
// No need to add an ID column in this provider
191+
return;
192+
}
193+
188194
var alterQuery = string.Format(AddTableCopyBulkInsertId, tempTableName);
189195

190196
await ExecuteAsync(sync, context, alterQuery, ctk);

src/PhenX.EntityFrameworkCore.BulkInsert/Enums/ProviderType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public enum ProviderType
2424
/// MySQL provider.
2525
/// </summary>
2626
MySql,
27+
28+
/// <summary>
29+
/// Oracle provider.
30+
/// </summary>
31+
Oracle,
2732
}

src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/InternalExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ internal static async Task<ConnectionInfo> GetConnection(
7474
/// <summary>
7575
/// Tells if the current provider is the specified provider type.
7676
/// </summary>
77-
internal static bool IsProvider(this DbContext context, ProviderType providerType)
77+
internal static bool IsProvider(this DbContext context, params ProviderType[] providerType)
7878
{
7979
if (context.Database.ProviderName == null)
8080
{
8181
throw new InvalidOperationException("Database provider name is null.");
8282
}
8383

84-
return context.Database.ProviderName.Contains(providerType.ToString(), StringComparison.OrdinalIgnoreCase);
84+
return providerType.Any(p => context.Database.ProviderName.Contains(p.ToString(), StringComparison.OrdinalIgnoreCase));
8585
}
8686
}

0 commit comments

Comments
 (0)