From ba1b5b4b58ec41b1deecbf0d05bbbe55743d1d82 Mon Sep 17 00:00:00 2001 From: Pschichholz Date: Wed, 6 May 2026 15:43:51 +0200 Subject: [PATCH 1/2] Fix Oracle bulk insert schema qualification issues (#1) - Fix double-schema qualification when EF Core default schema is configured - Clarify table name handling comment --- .../OracleBulkInsertProvider.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs index b97feab..d58c915 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs @@ -59,7 +59,14 @@ protected override Task BulkInsert( using var bulkCopy = new OracleBulkCopy(connection, options.CopyOptions); - bulkCopy.DestinationTableName = tableName; + // When tableName is the SQL-quoted fully qualified name (direct insert path), use the + // unquoted plain table name so ODP.NET does not apply double schema qualification + // (e.g. SchemaX.SchemaX.TABLE_NAME) when a default schema is configured via HasDefaultSchema. + var destinationTableName = tableName == tableInfo.QuotedTableName + ? tableInfo.TableName + : tableName; + + bulkCopy.DestinationTableName = destinationTableName; bulkCopy.BatchSize = options.BatchSize; bulkCopy.BulkCopyTimeout = options.GetCopyTimeoutInSeconds(); From 4096009a474d1b76306845071d3da3a94e5683ad Mon Sep 17 00:00:00 2001 From: PhenX Date: Tue, 9 Jun 2026 22:41:12 +0200 Subject: [PATCH 2/2] Fix Oracle destination schema --- .../OracleBulkInsertProvider.cs | 22 ++++++++++++------- .../Metadata/TableMetadata.cs | 5 ++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs index d58c915..36d22ff 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Oracle/OracleBulkInsertProvider.cs @@ -59,14 +59,20 @@ protected override Task BulkInsert( using var bulkCopy = new OracleBulkCopy(connection, options.CopyOptions); - // When tableName is the SQL-quoted fully qualified name (direct insert path), use the - // unquoted plain table name so ODP.NET does not apply double schema qualification - // (e.g. SchemaX.SchemaX.TABLE_NAME) when a default schema is configured via HasDefaultSchema. - var destinationTableName = tableName == tableInfo.QuotedTableName - ? tableInfo.TableName - : tableName; - - bulkCopy.DestinationTableName = destinationTableName; + // OracleBulkCopy's direct-path load cannot handle schema-qualified table names. + // When a schema is configured (e.g. via HasDefaultSchema()), the QuotedTableName + // includes a schema prefix (e.g. "SchemaX"."TableA"), which ODP.NET double-applies, + // producing SchemaX.SchemaX.TableA (ORA-39831). Use DestinationSchemaName explicitly. + if (tableName == tableInfo.QuotedTableName && tableInfo.Schema != null) + { + bulkCopy.DestinationTableName = SqlDialect.Quote(tableInfo.TableName); + bulkCopy.DestinationSchemaName = tableInfo.Schema; + } + else + { + bulkCopy.DestinationTableName = tableName; + } + bulkCopy.BatchSize = options.BatchSize; bulkCopy.BulkCopyTimeout = options.GetCopyTimeoutInSeconds(); diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/TableMetadata.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/TableMetadata.cs index 17028b7..28f6f56 100644 --- a/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/TableMetadata.cs +++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/TableMetadata.cs @@ -12,6 +12,8 @@ internal sealed class TableMetadata private readonly IEntityType _entityType; + public string? Schema { get; } + public string QuotedTableName { get; } public string TableName { get; } @@ -21,8 +23,9 @@ internal sealed class TableMetadata public TableMetadata(IEntityType entityType, SqlDialectBuilder dialect) { _entityType = entityType; + Schema = entityType.GetSchema(); TableName = entityType.GetTableName() ?? throw new InvalidOperationException("Cannot determine table name."); - QuotedTableName = dialect.QuoteTableName(entityType.GetSchema(), TableName); + QuotedTableName = dialect.QuoteTableName(Schema, TableName); Columns = GetColumns(entityType, dialect); }