Skip to content

Commit aacc5cd

Browse files
authored
Enhance temporary table name generation with random suffix for uniqueness (#80)
* Enhance temporary table name generation with random suffix for uniqueness * Skip tests for Oracle provider due to direct path operation restriction * Refactor temporary table name generation for Oracle to use an 8-character random suffix for uniqueness and use Shared random generator
1 parent df214d8 commit aacc5cd

7 files changed

Lines changed: 53 additions & 7 deletions

File tree

src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider> logger)
1919
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT AUTO_INCREMENT PRIMARY KEY;";
2020

2121
/// <inheritdoc />
22-
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
22+
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}_{Helpers.RandomString(6)}";
2323

2424
/// <inheritdoc />
2525
protected override MySqlBulkInsertOptions CreateDefaultOptions() => new()

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ internal class OracleBulkInsertProvider(ILogger<OracleBulkInsertProvider>? logge
2121

2222
/// <inheritdoc />
2323
/// <summary>
24-
/// The temporary table name is generated with a GUID to ensure uniqueness, but limited to less than 30 characters,
25-
/// because Oracle prior 12.2 has a limit of 30 characters for identifiers.
24+
/// The temporary table name is generated with a random 8-character suffix to ensure uniqueness, and is limited to less than 30 characters,
25+
/// because Oracle prior to 12.2 has a limit of 30 characters for identifiers.
2626
/// </summary>
27-
protected override string GetTempTableName(string tableName) => $"#temp_bulk_insert_{Guid.NewGuid().ToString("N")[..8]}";
27+
protected override string GetTempTableName(string tableName) => $"#temp_bulk_insert_{Helpers.RandomString(8)}";
2828

2929
protected override OracleBulkInsertOptions CreateDefaultOptions() => new()
3030
{

src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal class SqlServerBulkInsertProvider(ILogger<SqlServerBulkInsertProvider>?
1717
protected override string AddTableCopyBulkInsertId => $"ALTER TABLE {{0}} ADD {BulkInsertId} INT IDENTITY PRIMARY KEY;";
1818

1919
/// <inheritdoc />
20-
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
20+
protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}_{Helpers.RandomString(6)}";
2121

2222
protected override SqlServerBulkInsertOptions CreateDefaultOptions() => new()
2323
{

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_{Guid.NewGuid():N}";
27+
protected override string GetTempTableName(string tableName) => $"_temp_bulk_insert_{Helpers.RandomString(6)}";
2828

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

src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal abstract class BulkInsertProviderBase<TDialect, TOptions>(ILogger? logg
1919

2020
protected abstract string AddTableCopyBulkInsertId { get; }
2121

22-
protected virtual string GetTempTableName(string tableName) => $"_temp_bulk_insert_{tableName}";
22+
protected virtual string GetTempTableName(string tableName) => $"_temp_bulk_insert_{tableName}_{Helpers.RandomString(6)}";
2323

2424
protected override async IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
2525
bool sync,
@@ -124,6 +124,7 @@ protected override async Task BulkInsert<T>(
124124
await connection.Close(sync, ctk);
125125
}
126126
}
127+
127128
private async Task<string> PerformBulkInsertAsync<T>(
128129
bool sync,
129130
DbContext context,

src/PhenX.EntityFrameworkCore.BulkInsert/Helpers.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,21 @@ public static StringBuilder AppendColumns(this StringBuilder sb, IReadOnlyList<C
2727
{
2828
return sb.AppendJoin(", ", columns.Select(c => c.QuotedColumName));
2929
}
30+
31+
/// <summary>
32+
/// Generates a random alphanumeric string of the specified length.
33+
/// </summary>
34+
public static string RandomString(int length)
35+
{
36+
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
37+
38+
var sb = new StringBuilder(length);
39+
40+
for (var i = 0; i < length; i++)
41+
{
42+
sb.Append(chars[Random.Shared.Next(chars.Length)]);
43+
}
44+
45+
return sb.ToString();
46+
}
3047
}

tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,34 @@ public async Task InsertEntities_WithOpenTransaction_RollsBackOnFailure(InsertSt
235235
Assert.Empty(insertedEntities);
236236
}
237237

238+
[SkippableTheory]
239+
[CombinatorialData]
240+
public async Task InsertEntities_WithOpenTransaction_MultipleInserts(InsertStrategy strategy)
241+
{
242+
// Oracle: ORA-39822: A new direct path operation is not allowed in the current transaction.
243+
Skip.If(_context.IsProvider(ProviderType.Oracle));
244+
245+
// Arrange
246+
var entities = new List<TestEntity>
247+
{
248+
new TestEntity { TestRun = _run, Name = $"{_run}_EntityWithTx1" },
249+
new TestEntity { TestRun = _run, Name = $"{_run}_EntityWithTx2" },
250+
new TestEntity { TestRun = _run, Name = $"{_run}_EntityWithTx3" },
251+
new TestEntity { TestRun = _run, Name = $"{_run}_EntityWithTx4" },
252+
};
253+
254+
// Act
255+
await using var transaction = await _context.Database.BeginTransactionAsync();
256+
257+
var batches = entities.Chunk(2);
258+
foreach (var batch in batches)
259+
{
260+
await _context.InsertWithStrategyAsync(strategy, batch.ToList());
261+
}
262+
263+
await transaction.CommitAsync();
264+
}
265+
238266
[SkippableFact]
239267
public async Task ThrowsWhenUsingWrongConfigurationType()
240268
{

0 commit comments

Comments
 (0)