diff --git a/PhenX.EntityFrameworkCore.BulkInsert.slnx b/PhenX.EntityFrameworkCore.BulkInsert.slnx
index 4697239..7555761 100644
--- a/PhenX.EntityFrameworkCore.BulkInsert.slnx
+++ b/PhenX.EntityFrameworkCore.BulkInsert.slnx
@@ -14,6 +14,7 @@
+
diff --git a/README.md b/README.md
index 7df4e90..f5aa86c 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ but they are in [the roadmap](#roadmap).
| `PhenX.EntityFrameworkCore.BulkInsert.MySql` | For MySql | [](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.MySql) |
| `PhenX.EntityFrameworkCore.BulkInsert.Oracle` | For Oracle | [](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.Oracle) |
| `PhenX.EntityFrameworkCore.BulkInsert` | Common library | [](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert) |
+| `OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert` | OpenTelemetry instrumentation | [](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert) |
### Dependencies
@@ -51,6 +52,9 @@ Install-Package PhenX.EntityFrameworkCore.BulkInsert.MySql
# For Oracle
Install-Package PhenX.EntityFrameworkCore.BulkInsert.Oracle
+
+# For OpenTelemetry instrumentation
+Install-Package OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert
```
## Usage
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 66a5c4f..65777c3 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -48,6 +48,12 @@ export default defineConfig({
{
text: 'Documentation',
link: '/documentation',
+ items: [
+ {
+ text: 'OpenTelemetry',
+ link: '/opentelemetry',
+ },
+ ],
},
{
text: 'Limitations',
diff --git a/docs/opentelemetry.md b/docs/opentelemetry.md
new file mode 100644
index 0000000..e9e1d7b
--- /dev/null
+++ b/docs/opentelemetry.md
@@ -0,0 +1,87 @@
+---
+title: OpenTelemetry Instrumentation
+lang: en-US
+---
+
+# OpenTelemetry Instrumentation
+
+PhenX.EntityFrameworkCore.BulkInsert has built-in support for [OpenTelemetry](https://opentelemetry.io/) tracing via the `OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert` package.
+
+Each bulk insert operation is automatically tracked as an [Activity](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activity) using the `PhenX.EntityFrameworkCore.BulkInsert` activity source, enabling distributed tracing and performance monitoring in your application.
+
+## Installation
+
+Install the instrumentation NuGet package:
+
+```shell
+Install-Package OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert
+```
+
+## Configuration
+
+Register the instrumentation when configuring your OpenTelemetry `TracerProvider` by calling `AddPhenXEntityFrameworkCoreBulkInsertInstrumentation()`:
+
+```csharp
+using OpenTelemetry.Trace;
+
+builder.Services.AddOpenTelemetry()
+ .WithTracing(tracing => tracing
+ .AddAspNetCoreInstrumentation()
+ .AddPhenXEntityFrameworkCoreBulkInsertInstrumentation() // <-- add this
+ .AddOtlpExporter()
+ );
+```
+
+## `TracerProviderBuilderExtensions`
+
+### `AddPhenXEntityFrameworkCoreBulkInsertInstrumentation`
+
+```csharp
+public static TracerProviderBuilder AddPhenXEntityFrameworkCoreBulkInsertInstrumentation(
+ this TracerProviderBuilder builder)
+```
+
+Adds the `PhenX.EntityFrameworkCore.BulkInsert` activity source to the given `TracerProviderBuilder`.
+
+**Parameters**
+
+| Parameter | Type | Description |
+|-----------|------------------------|-----------------------------------------------------------|
+| `builder` | `TracerProviderBuilder` | The `TracerProviderBuilder` to add the instrumentation to. |
+
+**Returns**
+
+The `TracerProviderBuilder` with the `PhenX.EntityFrameworkCore.BulkInsert` instrumentation registered.
+
+## Traced Operations
+
+The following bulk insert operations produce OpenTelemetry traces:
+
+| Operation | Description |
+|--------------------------------------|----------------------------------------------------------|
+| `ExecuteBulkInsert` | Synchronous bulk insert without entity return |
+| `ExecuteBulkInsertAsync` | Asynchronous bulk insert without entity return |
+| `ExecuteBulkInsertReturnEntities` | Synchronous bulk insert with entity return |
+| `ExecuteBulkInsertReturnEntitiesAsync` | Asynchronous bulk insert with entity return |
+
+## Activity Source
+
+The activity source name used by this library is:
+
+```
+PhenX.EntityFrameworkCore.BulkInsert
+```
+
+You can use this name to subscribe directly to the activity source if you need lower-level access:
+
+```csharp
+using System.Diagnostics;
+
+ActivitySource.AddActivityListener(new ActivityListener
+{
+ ShouldListenTo = source => source.Name == "PhenX.EntityFrameworkCore.BulkInsert",
+ Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded,
+ ActivityStarted = activity => Console.WriteLine($"Started: {activity.DisplayName}"),
+ ActivityStopped = activity => Console.WriteLine($"Stopped: {activity.DisplayName} ({activity.Duration})"),
+});
+```
diff --git a/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert.csproj b/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert.csproj
new file mode 100644
index 0000000..09a57f5
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert.csproj
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/TracerProviderBuilderExtensions.cs
new file mode 100644
index 0000000..7fd1f48
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert/TracerProviderBuilderExtensions.cs
@@ -0,0 +1,18 @@
+using OpenTelemetry.Trace;
+namespace OpenTelemetry.Instrumentation.PhenX.EntityFrameworkCore.BulkInsert;
+
+///
+/// Extension methods for to add instrumentation for PhenX.EntityFrameworkCore.BulkInsert.
+///
+public static class TracerProviderBuilderExtensions
+{
+ ///
+ /// Adds instrumentation for PhenX.EntityFrameworkCore.BulkInsert to the OpenTelemetry TracerProviderBuilder.
+ ///
+ /// The TracerProviderBuilder to add the instrumentation to.
+ /// The TracerProviderBuilder with the PhenX.EntityFrameworkCore.BulkInsert instrumentation added.
+ public static TracerProviderBuilder AddPhenXEntityFrameworkCoreBulkInsertInstrumentation(this TracerProviderBuilder builder)
+ {
+ return builder.AddSource("PhenX.EntityFrameworkCore.BulkInsert");
+ }
+}
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();