Repository: PhenX/PhenX.EntityFrameworkCore.BulkInsert
Pull request #23: Feature/provider options
Author: login: PhenX
Created: 2025-05-22T14:39:01Z | Merged: 2025-05-24T08:46:52Z

Fixes #8 

Linked issue #8: Expose more SqlBulkCopy/BinaryImport options
Linked issue comments: user: login: rgroenewoudt
created_at: 2025-05-21T07:45:33Z
body: We would need to able to modify SqlBulkCopyOptions to use the library in our application.
author_association: NONE
user: login: PhenX
created_at: 2025-05-21T16:35:47Z
body: @rgroenewoudt which options do you need to customize ?
author_association: OWNER
user: login: rgroenewoudt
created_at: 2025-05-21T18:07:06Z
body: - CheckConstraints. For local development we often disable it due performance (testing large imports locally) but for production we need it enabled as SqlBulkCopy will otherwise disable foreign keys for the entire table.
- We don't like to use TableLock and prefer row or partition locking to allow other transactions to continue.

It might be easier to just offer SqlBulkCopyOptions as a property.
author_association: NONE
user: login: SebastianStehle
created_at: 2025-05-21T20:58:38Z
body: If it is okay to have a one time config, I would actually move this part to the provider itself.

e,g. something like 

```
protected override void Configure(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer()
        .UseBulkInsertSqlServer(options => {
            options.OnBulkCopy = bulkCopy => bulkCopy.FooBar = 12
        });
}
```

Then the bulk options does not become too large and you have a lot of freedom to do what you want (at your own risk).
author_association: COLLABORATOR
user: login: PhenX
created_at: 2025-05-21T21:21:35Z
body: I have a working prototype where you can pass provider specific options in the insert methods.
I'll push it tomorrow.
But it may be also a good idea to be able to specify defaults during configuration too. 
author_association: OWNER
user: login: SebastianStehle
created_at: 2025-05-21T21:56:44Z
body: my concern was more the open-closed-principle.
author_association: COLLABORATOR
user: login: PhenX
created_at: 2025-05-22T14:51:31Z
body: @SebastianStehle what do you think about this ? https://github.com/PhenX/PhenX.EntityFrameworkCore.BulkInsert/pull/23/files#diff-a41deb14e1df51f89a6a8637487da81ec4aa77e25ccee066ce44481ce160cdebR516
author_association: OWNER
user: login: PhenX
created_at: 2025-05-26T10:53:16Z
body: @rgroenewoudt I forgot to tell you, but you can now specify provider specific options : 

```csharp
await _context.ExecuteBulkInsertAsync(entities, (SqlServerBulkInsertOptions o) =>
{
    o.CopyOptions = SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.CheckConstraints;
});
```

or
            
```csharp
await _context.ExecuteBulkInsertAsync(entities, o =>
{
    if (o is SqlServerBulkInsertOptions sqlServerOptions)
    {
        sqlServerOptions.CopyOptions = SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.CheckConstraints;
    }
});
```
author_association: OWNER

Diff:
diff --git a/README.md b/README.md
index 227a927..62902e2 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,28 @@
 # PhenX.EntityFrameworkCore.BulkInsert
 
-A high-performance, provider-agnostic bulk insert extension for Entity Framework Core 8+. Supports SQL Server, PostgreSQL, SQLite.
+A high-performance, provider-agnostic bulk insert extension for Entity Framework Core 8+. Supports SQL Server, PostgreSQL, SQLite and MySQL.
 
 Its main purpose is to provide a fast way to perform simple bulk inserts in Entity Framework Core applications.
 
 ## Why this library?
 
 - **Performance**: It is designed to be fast and memory efficient, making it suitable for high-performance applications.
-- **Provider-agnostic**: It works with multiple database providers (SQL Server, PostgreSQL, and SQLite), allowing you to use it in different environments without changing your code.
+- **Provider-agnostic**: It works with multiple database providers (SQL Server, PostgreSQL, SQLite and MySQL), allowing you to use it in different environments without changing your code.
 - **Simplicity**: The API is simple and easy to use, making it accessible for developers of all skill levels.
 
 For now, it does not support navigation properties, complex types, owned types, shadow properties, or inheritance,
 but they are in [the roadmap](#roadmap).
 
+## Packages
+
+| Package Name                                      | Description    | NuGet Link                                                                                                                                                                     |
+|---------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `PhenX.EntityFrameworkCore.BulkInsert.SqlServer`  | For SQL Server | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.SqlServer.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert.SqlServer)   |
+| `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) |
+| `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)         |
+| `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)          |
+| `PhenX.EntityFrameworkCore.BulkInsert`            | Common library | [![NuGet](https://img.shields.io/nuget/v/PhenX.EntityFrameworkCore.BulkInsert.svg)](https://www.nuget.org/packages/PhenX.EntityFrameworkCore.BulkInsert)                       |
+
 ## Installation
 
 Install the NuGet package for your database provider:
@@ -26,6 +36,9 @@ Install-Package PhenX.EntityFrameworkCore.BulkInsert.PostgreSql
 
 # For SQLite
 Install-Package PhenX.EntityFrameworkCore.BulkInsert.Sqlite
+
+# For MySql
+Install-Package PhenX.EntityFrameworkCore.BulkInsert.MySql
 ```
 
 ## Usage
@@ -43,6 +56,8 @@ services.AddDbContext<MyDbContext>(options =>
         .UseBulkInsertSqlServer()
         // OR
         .UseBulkInsertSqlite()
+        // OR
+        .UseBulkInsertMySql()
         ;
 });
 ```
@@ -57,13 +72,35 @@ await dbContext.ExecuteBulkInsertAsync(entities);
 dbContext.ExecuteBulkInsert(entities);
 ```
 
-3. Optionally, you can configure the bulk insert options:
+3. You can also configure the bulk insert options:
 
 ```csharp
+// Common options
 await dbContext.ExecuteBulkInsertAsync(entities, options =>
 {
     options.BatchSize = 1000; // Set the batch size for the insert operation, the default value is different for each provider
 });
+
+// Provider specific options, when available, example for SQL Server
+await dbContext.ExecuteBulkInsertAsync(entities, (SqlServerBulkInsertOptions o) => // <<< here specify the SQL Server options class
+{
+    options.EnableStreaming = true; // Enable streaming for SQL Server
+});
+
+// Provider specific options, supporting multiple providers
+await dbContext.ExecuteBulkInsertAsync(entities, o =>
+{
+    o.MoveRows = true;
+
+    if (o is SqlServerBulkInsertOptions sqlServerOptions)
+    {
+        sqlServerOptions.EnableStreaming = true;
+    }
+    else if (o is MySqlBulkInsertOptions mysqlOptions)
+    {
+        mysqlOptions.BatchSize = 1000;
+    }
+});
 ```
 
 4. You can also return the inserted entities (slower):
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertOptions.cs
new file mode 100644
index 0000000..d1e8d60
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertOptions.cs
@@ -0,0 +1,11 @@
+﻿using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.MySql;
+
+/// <summary>
+/// Options specific to MySQL bulk insert.
+/// </summary>
+public class MySqlBulkInsertOptions : BulkInsertOptions
+{
+
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs
index ab08f92..ff32163 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs
@@ -9,7 +9,7 @@
 
 namespace PhenX.EntityFrameworkCore.BulkInsert.MySql;
 
-internal class MySqlBulkInsertProvider : BulkInsertProviderBase<MySqlServerDialectBuilder>
+internal class MySqlBulkInsertProvider : BulkInsertProviderBase<MySqlServerDialectBuilder, MySqlBulkInsertOptions>
 {
     public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null) : base(logger)
     {
@@ -22,6 +22,9 @@ public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null)
     /// <inheritdoc />
     protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
 
+    /// <inheritdoc />
+    protected override MySqlBulkInsertOptions CreateDefaultOptions() => new();
+
     /// <inheritdoc />
     public override IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
         bool sync,
@@ -43,21 +46,25 @@ protected override async Task BulkInsert<T>(
         IEnumerable<T> entities,
         string tableName,
         IReadOnlyList<ColumnMetadata> properties,
-        BulkInsertOptions options,
+        MySqlBulkInsertOptions options,
         CancellationToken ctk
     )
     {
         var connection = (MySqlConnection)context.Database.GetDbConnection();
-        var sqlTransaction = context.Database.CurrentTransaction!.GetDbTransaction()
+
+        var sqlTransaction = context.Database.CurrentTransaction?.GetDbTransaction()
             ?? throw new InvalidOperationException("No open transaction found.");
+
         if (sqlTransaction is not MySqlTransaction mySqlTransaction)
         {
             throw new InvalidOperationException($"Invalid transaction foud, got {sqlTransaction.GetType()}.");
         }
 
-        var bulkCopy = new MySqlBulkCopy(connection, mySqlTransaction);
-        bulkCopy.DestinationTableName = tableName;
-        bulkCopy.BulkCopyTimeout = options.GetCopyTimeoutInSeconds();
+        var bulkCopy = new MySqlBulkCopy(connection, mySqlTransaction)
+        {
+            DestinationTableName = tableName,
+            BulkCopyTimeout = options.GetCopyTimeoutInSeconds(),
+        };
 
         var sourceOrdinal = 0;
         foreach (var prop in properties)
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs
index 1258845..4dd86a2 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.PostgreSql/PostgreSqlBulkInsertProvider.cs
@@ -13,7 +13,7 @@
 namespace PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
 
 [UsedImplicitly]
-internal class PostgreSqlBulkInsertProvider : BulkInsertProviderBase<PostgreSqlDialectBuilder>
+internal class PostgreSqlBulkInsertProvider : BulkInsertProviderBase<PostgreSqlDialectBuilder, BulkInsertOptions>
 {
     public PostgreSqlBulkInsertProvider(ILogger<PostgreSqlBulkInsertProvider>? logger = null) : base(logger)
     {
@@ -32,6 +32,12 @@ private static string GetBinaryImportCommand(IReadOnlyList<ColumnMetadata> prope
         return sql.ToString();
     }
 
+    /// <inheritdoc />
+    protected override BulkInsertOptions CreateDefaultOptions() => new()
+    {
+        BatchSize = 50_000,
+    };
+
     /// <inheritdoc />
     protected override async Task BulkInsert<T>(
         bool sync,
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertOptions.cs
new file mode 100644
index 0000000..b31b6cc
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertOptions.cs
@@ -0,0 +1,18 @@
+﻿using Microsoft.Data.SqlClient;
+
+using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer;
+
+/// <summary>
+/// Options specific to SQL Server bulk insert.
+/// </summary>
+public class SqlServerBulkInsertOptions : BulkInsertOptions
+{
+    /// <inheritdoc cref="SqlBulkCopyOptions"/>
+    public SqlBulkCopyOptions CopyOptions { get; set; } = SqlBulkCopyOptions.Default;
+
+    /// <inheritdoc cref="SqlBulkCopy.EnableStreaming"/>
+    public bool EnableStreaming { get; set; } = false;
+
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs
index 79fc58f..1efbbb4 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.SqlServer/SqlServerBulkInsertProvider.cs
@@ -11,7 +11,7 @@
 namespace PhenX.EntityFrameworkCore.BulkInsert.SqlServer;
 
 [UsedImplicitly]
-internal class SqlServerBulkInsertProvider : BulkInsertProviderBase<SqlServerDialectBuilder>
+internal class SqlServerBulkInsertProvider : BulkInsertProviderBase<SqlServerDialectBuilder, SqlServerBulkInsertOptions>
 {
     public SqlServerBulkInsertProvider(ILogger<SqlServerBulkInsertProvider>? logger = null) : base(logger)
     {
@@ -24,6 +24,11 @@ public SqlServerBulkInsertProvider(ILogger<SqlServerBulkInsertProvider>? logger
     /// <inheritdoc />
     protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
 
+    protected override SqlServerBulkInsertOptions CreateDefaultOptions() => new()
+    {
+        BatchSize = 50_000,
+    };
+
     /// <inheritdoc />
     protected override async Task BulkInsert<T>(
         bool sync,
@@ -32,16 +37,18 @@ protected override async Task BulkInsert<T>(
         IEnumerable<T> entities,
         string tableName,
         IReadOnlyList<ColumnMetadata> columns,
-        BulkInsertOptions options,
+        SqlServerBulkInsertOptions options,
         CancellationToken ctk)
     {
         var connection = (SqlConnection) context.Database.GetDbConnection();
         var sqlTransaction = context.Database.CurrentTransaction!.GetDbTransaction() as SqlTransaction;
 
-        using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, sqlTransaction);
+        using var bulkCopy = new SqlBulkCopy(connection, options.CopyOptions, sqlTransaction);
+
         bulkCopy.DestinationTableName = tableName;
-        bulkCopy.BatchSize = options.BatchSize ?? 50_000;
+        bulkCopy.BatchSize = options.BatchSize;
         bulkCopy.BulkCopyTimeout = options.GetCopyTimeoutInSeconds();
+        bulkCopy.EnableStreaming = options.EnableStreaming;
 
         foreach (var column in columns)
         {
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs
index bd82de2..439d0f2 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert.Sqlite/SqliteBulkInsertProvider.cs
@@ -13,7 +13,7 @@
 namespace PhenX.EntityFrameworkCore.BulkInsert.Sqlite;
 
 [UsedImplicitly]
-internal class SqliteBulkInsertProvider : BulkInsertProviderBase<SqliteDialectBuilder>
+internal class SqliteBulkInsertProvider : BulkInsertProviderBase<SqliteDialectBuilder, BulkInsertOptions>
 {
     public SqliteBulkInsertProvider(ILogger<SqliteBulkInsertProvider>? logger = null) : base(logger)
     {
@@ -26,6 +26,12 @@ public SqliteBulkInsertProvider(ILogger<SqliteBulkInsertProvider>? logger = null
     /// <inheritdoc />
     protected override string AddTableCopyBulkInsertId => "--"; // No need to add an ID column in SQLite
 
+    /// <inheritdoc />
+    protected override BulkInsertOptions CreateDefaultOptions() => new()
+    {
+        BatchSize = 5,
+    };
+
     /// <inheritdoc />
     protected override Task AddBulkInsertIdColumn<T>(
         bool sync,
@@ -142,10 +148,10 @@ CancellationToken ctk
     ) where T : class
     {
         const int maxParams = 1000;
-        var batchSize = options.BatchSize ?? 5;
+        var batchSize = options.BatchSize;
         batchSize = Math.Min(batchSize, maxParams / columns.Count);
 
-        // The StringBuilder can be resuse between the batches. 
+        // The StringBuilder can be resuse between the batches.
         var sb = new StringBuilder();
 
         var columnList = tableInfo.GetColumns(options.CopyGeneratedColumns);
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs
index 226b52a..a8fa8e6 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Abstractions/IBulkInsertProvider.cs
@@ -38,4 +38,9 @@ internal Task BulkInsert<T>(
     ) where T : class;
 
     SqlDialectBuilder SqlDialect { get; }
+
+    /// <summary>
+    /// Make the default options for the provider, can be a subclass of <see cref="BulkInsertOptions"/>.
+    /// </summary>
+    internal BulkInsertOptions InternalCreateDefaultOptions();
 }
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
index ace778a..48fa384 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
@@ -12,7 +12,9 @@
 
 namespace PhenX.EntityFrameworkCore.BulkInsert;
 
-internal abstract class BulkInsertProviderBase<TDialect>(ILogger<BulkInsertProviderBase<TDialect>>? logger = null) : IBulkInsertProvider where TDialect : SqlDialectBuilder, new()
+internal abstract class BulkInsertProviderBase<TDialect, TOptions>(ILogger<BulkInsertProviderBase<TDialect, TOptions>>? logger = null) : IBulkInsertProvider
+    where TDialect : SqlDialectBuilder, new()
+    where TOptions : BulkInsertOptions, new()
 {
     protected readonly TDialect SqlDialect = new();
 
@@ -24,6 +26,13 @@ namespace PhenX.EntityFrameworkCore.BulkInsert;
 
     SqlDialectBuilder IBulkInsertProvider.SqlDialect => SqlDialect;
 
+    public BulkInsertOptions InternalCreateDefaultOptions() => CreateDefaultOptions();
+
+    /// <summary>
+    /// Create the default options for the provider, can be a subclass of <see cref="BulkInsertOptions"/>.
+    /// </summary>
+    protected abstract TOptions CreateDefaultOptions();
+
     public virtual async IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
         bool sync,
         DbContext context,
@@ -33,6 +42,11 @@ public virtual async IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
         OnConflictOptions? onConflict,
         [EnumeratorCancellation] CancellationToken ctk) where T : class
     {
+        if (options is not TOptions providerOptions)
+        {
+            throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}");
+        }
+
         using var activity = Telemetry.ActivitySource.StartActivity("BulkInsertReturnEntities");
         activity?.AddTag("tableName", tableInfo.TableName);
         activity?.AddTag("synchronous", sync);
@@ -45,10 +59,10 @@ public virtual async IAsyncEnumerable<T> BulkInsertReturnEntities<T>(
                 Log.UsingTempTablToReturnData(logger);
             }
 
-            var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: true, ctk: ctk);
+            var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: true, ctk: ctk);
 
             var result =
-                await CopyFromTempTableAsync<T, T>(sync, context, tableInfo, tableName, true, options, onConflict, ctk: ctk)
+                await CopyFromTempTableAsync<T, T>(sync, context, tableInfo, tableName, true, providerOptions, onConflict, ctk: ctk)
                     ?? throw new InvalidOperationException("Copy returns null enumerable.");
 
             await foreach (var item in result.WithCancellation(ctk))
@@ -74,6 +88,11 @@ public virtual async Task BulkInsert<T>(
         OnConflictOptions? onConflict,
         CancellationToken ctk) where T : class
     {
+        if (options is not TOptions providerOptions)
+        {
+            throw new InvalidOperationException($"Invalid options type: {options.GetType().Name}. Expected: {typeof(TOptions).Name}");
+        }
+
         using var activity = Telemetry.ActivitySource.StartActivity("BulkInsert");
         activity?.AddTag("tableName", tableInfo.TableName);
         activity?.AddTag("synchronous", sync);
@@ -88,9 +107,9 @@ public virtual async Task BulkInsert<T>(
                     Log.UsingTempTableToResolveConflicts(logger);
                 }
 
-                var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: true, ctk: ctk);
+                var tableName = await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: true, ctk: ctk);
 
-                await CopyFromTempTableAsync<T, T>(sync, context, tableInfo, tableName, false, options, onConflict, ctk);
+                await CopyFromTempTableAsync<T, T>(sync, context, tableInfo, tableName, false, providerOptions, onConflict, ctk);
             }
             else
             {
@@ -99,7 +118,7 @@ public virtual async Task BulkInsert<T>(
                     Log.UsingDirectInsert(logger);
                 }
 
-                await PerformBulkInsertAsync(sync, context, tableInfo, entities, options, tempTableRequired: false, ctk: ctk);
+                await PerformBulkInsertAsync(sync, context, tableInfo, entities, providerOptions, tempTableRequired: false, ctk: ctk);
             }
 
             // Commit the transaction if we own them.
@@ -116,7 +135,7 @@ private async Task<string> PerformBulkInsertAsync<T>(
         DbContext context,
         TableMetadata tableInfo,
         IEnumerable<T> entities,
-        BulkInsertOptions options,
+        TOptions options,
         bool tempTableRequired,
         CancellationToken ctk) where T : class
     {
@@ -149,7 +168,7 @@ protected abstract Task BulkInsert<T>(
         IEnumerable<T> entities,
         string tableName,
         IReadOnlyList<ColumnMetadata> columns,
-        BulkInsertOptions options,
+        TOptions options,
         CancellationToken ctk) where T : class;
 
     protected async Task<string> CreateTableCopyAsync<T>(
@@ -187,7 +206,7 @@ protected virtual async Task AddBulkInsertIdColumn<T>(
         TableMetadata tableInfo,
         string tempTableName,
         bool returnData,
-        BulkInsertOptions options,
+        TOptions options,
         OnConflictOptions? onConflict,
         CancellationToken ctk) where T : class where TResult : class
     {
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Enums/ProviderType.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Enums/ProviderType.cs
new file mode 100644
index 0000000..1010c28
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Enums/ProviderType.cs
@@ -0,0 +1,27 @@
+﻿namespace PhenX.EntityFrameworkCore.BulkInsert.Enums;
+
+/// <summary>
+/// Enumeration of supported database providers.
+/// </summary>
+public enum ProviderType
+{
+    /// <summary>
+    /// SQL Server provider.
+    /// </summary>
+    SqlServer,
+
+    /// <summary>
+    /// PostgreSQL provider.
+    /// </summary>
+    PostgreSql,
+
+    /// <summary>
+    /// SQLite provider.
+    /// </summary>
+    Sqlite,
+
+    /// <summary>
+    /// MySQL provider.
+    /// </summary>
+    MySql,
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbSetExtensions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbSetExtensions.cs
deleted file mode 100644
index 7e773a9..0000000
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbSetExtensions.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-
-using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
-using PhenX.EntityFrameworkCore.BulkInsert.Options;
-
-namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
-
-/// <summary>
-/// DbSet extensions for bulk insert operations.
-/// </summary>
-public static class DbSetExtensions
-{
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet (synchronous variant).
-    /// </summary>
-    public static List<T> ExecuteBulkInsertReturnEntities<T>(
-        this DbSet<T> dbSet,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null
-    ) where T : class
-    {
-        return dbSet.ExecuteBulkInsertReturnEntitiesCoreAsync(true, entities, configure, onConflict, default).GetAwaiter().GetResult();
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext (synchronous variant).
-    /// </summary>
-    public static List<T> ExecuteBulkInsertReturnEntities<T>(
-        this DbContext dbContext,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null
-    ) where T : class
-    {
-        var dbSet = dbContext.Set<T>() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
-
-        return dbSet.ExecuteBulkInsertReturnEntitiesCoreAsync(true, entities, configure, onConflict, default).GetAwaiter().GetResult();
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext.
-    /// </summary>
-    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
-        this DbContext dbContext,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        var dbSet = dbContext.Set<T>() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
-
-        return dbSet.ExecuteBulkInsertReturnEntitiesCoreAsync(false, entities, configure, onConflict, ctk);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet.
-    /// </summary>
-    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
-        this DbSet<T> dbSet,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        return dbSet.ExecuteBulkInsertReturnEntitiesCoreAsync(false, entities, configure, onConflict, ctk);
-    }
-
-    private static async Task<List<T>> ExecuteBulkInsertReturnEntitiesCoreAsync<T>(
-        this DbSet<T> dbSet,
-        bool sync,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure,
-        OnConflictOptions<T>? onConflict,
-        CancellationToken ctk
-    ) where T : class
-    {
-        var provider = InitProvider(dbSet, configure, out var context, out var options);
-
-        var enumerable = provider.BulkInsertReturnEntities(sync, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict, ctk);
-
-        var result = new List<T>();
-        await foreach (var item in enumerable.WithCancellation(ctk))
-        {
-            result.Add(item);
-        }
-
-        return result;
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext.
-    /// </summary>
-    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
-        this DbContext dbContext,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        var dbSet = dbContext.Set<T>() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
-
-        return dbSet.ExecuteBulkInsertReturnEnumerableAsync(entities, configure, onConflict, ctk);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet.
-    /// </summary>
-    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
-        this DbSet<T> dbSet,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        var provider = InitProvider(dbSet, configure, out var context, out var options);
-
-        return provider.BulkInsertReturnEntities(false, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict, ctk);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext.
-    /// </summary>
-    public static async Task ExecuteBulkInsertAsync<T>(
-        this DbContext dbContext,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        var dbSet = dbContext.Set<T>() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
-
-        await dbSet.ExecuteBulkInsertAsync(entities, configure, onConflict, ctk);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet.
-    /// </summary>
-    public static async Task ExecuteBulkInsertAsync<T>(
-        this DbSet<T> dbSet,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null,
-        CancellationToken ctk = default
-    ) where T : class
-    {
-        var provider = InitProvider(dbSet, configure, out var context, out var options);
-
-        await provider.BulkInsert(false, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict, ctk);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext (synchronous variant).
-    /// </summary>
-    public static void ExecuteBulkInsert<T>(
-        this DbContext dbContext,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null
-    ) where T : class
-    {
-        var dbSet = dbContext.Set<T>() ?? throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
-
-        dbSet.ExecuteBulkInsert(entities, configure, onConflict);
-    }
-
-    /// <summary>
-    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet (synchronous variant).
-    /// </summary>
-    public static void ExecuteBulkInsert<T>(
-        this DbSet<T> dbSet,
-        IEnumerable<T> entities,
-        Action<BulkInsertOptions>? configure = null,
-        OnConflictOptions<T>? onConflict = null
-    ) where T : class
-    {
-        var provider = InitProvider(dbSet, configure, out var context, out var options);
-
-        provider.BulkInsert(true, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict).GetAwaiter().GetResult();
-    }
-
-    private static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class
-    {
-        IInfrastructure<IServiceProvider> infrastructure = dbSet;
-        return (infrastructure.Instance.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext)!.Context;
-    }
-
-    private static IBulkInsertProvider InitProvider<T>(DbSet<T> dbSet, Action<BulkInsertOptions>? configure, out DbContext context,
-        out BulkInsertOptions options) where T : class
-    {
-        context = dbSet.GetDbContext();
-        var provider = context.GetService<IBulkInsertProvider>();
-
-        options = new BulkInsertOptions();
-        configure?.Invoke(options);
-        return provider;
-    }
-}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbContextExtensions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/InternalExtensions.cs
similarity index 73%
rename from src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbContextExtensions.cs
rename to src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/InternalExtensions.cs
index 2b4bd16..16443b7 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbContextExtensions.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/InternalExtensions.cs
@@ -6,18 +6,20 @@
 using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
 using PhenX.EntityFrameworkCore.BulkInsert.Metadata;
 
+using PhenX.EntityFrameworkCore.BulkInsert.Enums;
+
 namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
 
-internal static class DbContextExtensions
+internal static class InternalExtensions
 {
-    public static TableMetadata GetTableInfo<T>(this DbContext context)
+    internal static TableMetadata GetTableInfo<T>(this DbContext context)
     {
         var provider = context.GetService<MetadataProvider>();
 
         return provider.GetTableInfo<T>(context);
     }
 
-    public static DbContextOptionsBuilder UseProvider<TProvider>(this DbContextOptionsBuilder optionsBuilder)
+    internal static DbContextOptionsBuilder UseProvider<TProvider>(this DbContextOptionsBuilder optionsBuilder)
         where TProvider : class, IBulkInsertProvider
     {
         var extension = optionsBuilder.Options.FindExtension<BulkInsertOptionsExtension<TProvider>>() ?? new BulkInsertOptionsExtension<TProvider>();
@@ -69,4 +71,17 @@ internal static async Task<ConnectionInfo> GetConnection(
 
         return new ConnectionInfo(connection, wasClosed, transaction, wasBegan);
     }
+
+    /// <summary>
+    /// Tells if the current provider is the specified provider type.
+    /// </summary>
+    internal static bool IsProvider(this DbContext context, ProviderType providerType)
+    {
+        if (context.Database.ProviderName == null)
+        {
+            throw new InvalidOperationException("Database provider name is null.");
+        }
+
+        return context.Database.ProviderName.Contains(providerType.ToString(), StringComparison.OrdinalIgnoreCase);
+    }
 }
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs
new file mode 100644
index 0000000..9bc410b
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbContext.cs
@@ -0,0 +1,239 @@
+﻿using Microsoft.EntityFrameworkCore;
+
+using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
+
+public static partial class PublicExtensions
+{
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext (synchronous variant), with provider specific options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T, TConfig>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<TConfig> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+        where TConfig : BulkInsertOptions
+    {
+        var dbSet = dbContext.Set<T>() ??
+                    throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
+
+        return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet, true, entities, configure, onConflict, CancellationToken.None)
+            .GetAwaiter().GetResult();
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext (synchronous variant), with common options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntities<T, BulkInsertOptions>(dbContext, entities, configure, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext (synchronous variant), without options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntities<T, BulkInsertOptions>(dbContext, entities, _ => { }, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, with provider specific options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T, TConfig>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<TConfig> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TConfig : BulkInsertOptions
+    {
+        var dbSet = dbContext.Set<T>() ??
+                    throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
+
+        return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet,false, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, with common options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntitiesAsync<T, BulkInsertOptions>(dbContext, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, without options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntitiesAsync<T, BulkInsertOptions>(dbContext, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, with provider specific options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T, TConfig>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<TConfig> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TConfig : BulkInsertOptions
+    {
+        var dbSet = dbContext.Set<T>() ??
+                    throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
+
+        return dbSet.ExecuteBulkInsertReturnEnumerableAsync(entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, with common options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    ) where T : class
+    {
+        return ExecuteBulkInsertReturnEnumerableAsync<T, BulkInsertOptions>(dbContext, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbContext, without options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    ) where T : class
+    {
+        return ExecuteBulkInsertReturnEnumerableAsync<T, BulkInsertOptions>(dbContext, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext, with provider specific options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T, TConfig>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<TConfig> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TConfig : BulkInsertOptions
+    {
+        var dbSet = dbContext.Set<T>() ??
+                    throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
+
+        await dbSet.ExecuteBulkInsertAsync(entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext, with common options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        await ExecuteBulkInsertAsync<T, BulkInsertOptions>(dbContext, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext, without options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        await ExecuteBulkInsertAsync<T, BulkInsertOptions>(dbContext, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext (synchronous variant), with provider specific options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T, TConfig>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<TConfig> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+        where TConfig : BulkInsertOptions
+    {
+        var dbSet = dbContext.Set<T>() ??
+                    throw new InvalidOperationException($"DbSet of type {typeof(T).Name} not found in DbContext.");
+
+        dbSet.ExecuteBulkInsert(entities, configure, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext (synchronous variant), with common options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    ) where T : class
+    {
+        ExecuteBulkInsert<T, BulkInsertOptions>(dbContext, entities, configure, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbContext (synchronous variant), without options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T>(
+        this DbContext dbContext,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null
+    ) where T : class
+    {
+        ExecuteBulkInsert<T, BulkInsertOptions>(dbContext, entities, _ => { }, onConflict);
+    }
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs
new file mode 100644
index 0000000..c7cd183
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs
@@ -0,0 +1,237 @@
+﻿using Microsoft.EntityFrameworkCore;
+
+using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
+
+public static partial class PublicExtensions
+{
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet (synchronous variant), with provider specific options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T, TOptions>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet, true, entities, configure, onConflict, CancellationToken.None)
+            .GetAwaiter().GetResult();
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet (synchronous variant), with common options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntities<T, BulkInsertOptions>(dbSet, entities, configure, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet (synchronous variant), without options.
+    /// </summary>
+    public static List<T> ExecuteBulkInsertReturnEntities<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntities<T, BulkInsertOptions>(dbSet, entities, _ => { }, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, with provider specific options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T, TOptions>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        return ExecuteBulkInsertReturnEntitiesCoreAsync(dbSet, false, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, with common options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntitiesAsync<T, BulkInsertOptions>(dbSet, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, without options.
+    /// </summary>
+    public static Task<List<T>> ExecuteBulkInsertReturnEntitiesAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEntitiesAsync<T, BulkInsertOptions>(dbSet, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, with provider specific options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T, TOptions>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        var provider = InitProvider(dbSet, configure, out var context, out var options);
+
+        return provider.BulkInsertReturnEntities(false, context, dbSet.GetDbContext().GetTableInfo<T>(), entities,
+            options, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, with common options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEnumerableAsync<T, BulkInsertOptions>(dbSet, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation returning the inserted/updated entities, from the DbSet, without options.
+    /// </summary>
+    public static IAsyncEnumerable<T> ExecuteBulkInsertReturnEnumerableAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        return ExecuteBulkInsertReturnEnumerableAsync<T, BulkInsertOptions>(dbSet, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet, with provider specific options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T, TOptions>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        var provider = InitProvider(dbSet, configure, out var context, out var options);
+
+        await provider.BulkInsert(false, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict,
+            ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet, with common options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        await ExecuteBulkInsertAsync<T, BulkInsertOptions>(dbSet, entities, configure, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet, without options.
+    /// </summary>
+    public static async Task ExecuteBulkInsertAsync<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null,
+        CancellationToken ctk = default
+    )
+        where T : class
+    {
+        await ExecuteBulkInsertAsync<T, BulkInsertOptions>(dbSet, entities, _ => { }, onConflict, ctk);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet (synchronous variant), with provider specific options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T, TOptions>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        var provider = InitProvider(dbSet, configure, out var context, out var options);
+
+        provider.BulkInsert(true, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict)
+            .GetAwaiter().GetResult();
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet (synchronous variant), with common options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        Action<BulkInsertOptions> configure,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        ExecuteBulkInsert<T, BulkInsertOptions>(dbSet, entities, configure, onConflict);
+    }
+
+    /// <summary>
+    /// Executes a bulk insert operation without returning the inserted/updated entities, from the DbSet (synchronous variant), without options.
+    /// </summary>
+    public static void ExecuteBulkInsert<T>(
+        this DbSet<T> dbSet,
+        IEnumerable<T> entities,
+        OnConflictOptions<T>? onConflict = null
+    )
+        where T : class
+    {
+        ExecuteBulkInsert<T, BulkInsertOptions>(dbSet, entities, _ => { }, onConflict);
+    }
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs
new file mode 100644
index 0000000..4dcc850
--- /dev/null
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.cs
@@ -0,0 +1,67 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+
+using PhenX.EntityFrameworkCore.BulkInsert.Abstractions;
+using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
+
+/// <summary>
+/// DbSet extensions for bulk insert operations.
+/// </summary>
+public static partial class PublicExtensions
+{
+    private static async Task<List<T>> ExecuteBulkInsertReturnEntitiesCoreAsync<T, TOptions>(
+        this DbSet<T> dbSet,
+        bool sync,
+        IEnumerable<T> entities,
+        Action<TOptions> configure,
+        OnConflictOptions<T>? onConflict,
+        CancellationToken ctk
+    )
+        where T : class
+        where TOptions : BulkInsertOptions
+    {
+        var provider = InitProvider(dbSet, configure, out var context, out var options);
+
+        var enumerable = provider.BulkInsertReturnEntities(sync, context, dbSet.GetDbContext().GetTableInfo<T>(), entities, options, onConflict, ctk);
+
+        var result = new List<T>();
+        await foreach (var item in enumerable.WithCancellation(ctk))
+        {
+            result.Add(item);
+        }
+
+        return result;
+    }
+
+    private static DbContext GetDbContext<T>(this DbSet<T> dbSet) where T : class
+    {
+        IInfrastructure<IServiceProvider> infrastructure = dbSet;
+        return (infrastructure.Instance.GetService(typeof(ICurrentDbContext)) as ICurrentDbContext)!.Context;
+    }
+
+    private static IBulkInsertProvider InitProvider<T, TOptions>(
+        DbSet<T> dbSet,
+        Action<TOptions>? configure,
+        out DbContext context,
+        out TOptions options
+    )
+        where T : class where TOptions : BulkInsertOptions
+    {
+        context = dbSet.GetDbContext();
+        var provider = context.GetService<IBulkInsertProvider>();
+
+        var defaultOptions = provider.InternalCreateDefaultOptions();
+
+        if (defaultOptions is not TOptions castedOptions)
+        {
+            throw new InvalidOperationException($"Options type mismatch. Expected {defaultOptions.GetType().Name}, but got {typeof(TOptions).Name}.");
+        }
+
+        options = castedOptions;
+        configure?.Invoke(options);
+
+        return provider;
+    }
+}
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/ConnectionInfo.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ConnectionInfo.cs
similarity index 95%
rename from src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/ConnectionInfo.cs
rename to src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ConnectionInfo.cs
index 33ecc9b..cbb52e1 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/ConnectionInfo.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Metadata/ConnectionInfo.cs
@@ -2,7 +2,7 @@
 
 using Microsoft.EntityFrameworkCore.Storage;
 
-namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
+namespace PhenX.EntityFrameworkCore.BulkInsert.Metadata;
 
 internal readonly record struct ConnectionInfo(DbConnection Connection, bool WasClosed, IDbContextTransaction Transaction, bool WasBegan)
 {
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs
index c72f919..19f4835 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/Options/BulkInsertOptions.cs
@@ -31,17 +31,17 @@ public class BulkInsertOptions
     /// </item>
     /// </list>
     /// </summary>
-    public int? BatchSize { get; set; }
+    public int BatchSize { get; set; }
 
     /// <summary>
     /// Indicates if also generated columns should be copied. This is useful for upsert operations.
     /// </summary>
     public bool CopyGeneratedColumns { get; set; }
-    
+
     /// <summary>
     /// The timeout to copy records.
     /// </summary>
-    public TimeSpan CopyTimeout = TimeSpan.FromMinutes(10);
+    public TimeSpan CopyTimeout { get; set; } = TimeSpan.FromMinutes(10);
 
     internal int GetCopyTimeoutInSeconds()
     {
diff --git a/src/PhenX.EntityFrameworkCore.BulkInsert/PhenX.EntityFrameworkCore.BulkInsert.csproj b/src/PhenX.EntityFrameworkCore.BulkInsert/PhenX.EntityFrameworkCore.BulkInsert.csproj
index 17da02a..1f957fb 100644
--- a/src/PhenX.EntityFrameworkCore.BulkInsert/PhenX.EntityFrameworkCore.BulkInsert.csproj
+++ b/src/PhenX.EntityFrameworkCore.BulkInsert/PhenX.EntityFrameworkCore.BulkInsert.csproj
@@ -6,6 +6,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <InternalsVisibleTo Include="PhenX.EntityFrameworkCore.BulkInsert.Tests" />
     <InternalsVisibleTo Include="PhenX.EntityFrameworkCore.BulkInsert.MySql" />
     <InternalsVisibleTo Include="PhenX.EntityFrameworkCore.BulkInsert.PostgreSql" />
     <InternalsVisibleTo Include="PhenX.EntityFrameworkCore.BulkInsert.SqlServer" />
diff --git a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs
index eaa1976..d344a1c 100644
--- a/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs
+++ b/tests/PhenX.EntityFrameworkCore.BulkInsert.Tests/Tests/Basic/BasicTestsBase.cs
@@ -1,5 +1,8 @@
+using PhenX.EntityFrameworkCore.BulkInsert.Enums;
 using PhenX.EntityFrameworkCore.BulkInsert.Extensions;
+using PhenX.EntityFrameworkCore.BulkInsert.MySql;
 using PhenX.EntityFrameworkCore.BulkInsert.Options;
+using PhenX.EntityFrameworkCore.BulkInsert.SqlServer;
 using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContainer;
 using PhenX.EntityFrameworkCore.BulkInsert.Tests.DbContext;
 
@@ -74,7 +77,7 @@ public void InsertsEntitiesSuccessfully_Sync()
     [SkippableFact]
     public async Task InsertsEntitiesAndReturn()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         // Arrange
         var entities = new List<TestEntity>
@@ -121,7 +124,7 @@ public async Task InsertsEntitiesAndReturnAsyncEnumerable()
     [SkippableFact]
     public void InsertsEntitiesAndReturn_Sync()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         // Arrange
         var entities = new List<TestEntity>
@@ -142,8 +145,8 @@ public void InsertsEntitiesAndReturn_Sync()
     [SkippableFact]
     public async Task InsertsEntities_MultipleTimes()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Postgres", StringComparison.InvariantCultureIgnoreCase));
-        Skip.If(_context.Database.ProviderName!.Contains("SqlServer", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.PostgreSql));
+        Skip.If(_context.IsProvider(ProviderType.SqlServer));
 
         // Arrange
         var entities = new List<TestEntity>
@@ -263,7 +266,7 @@ await _context.ExecuteBulkInsertAsync(entities, o =>
     [SkippableFact]
     public async Task InsertsEntitiesWithConflict_SingleColumn()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         _context.TestEntities.Add(new TestEntity { TestRun = _run, Name = $"{_run}_Entity1" });
         await _context.SaveChangesAsync();
@@ -302,7 +305,7 @@ await _context.ExecuteBulkInsertAsync(entities, o =>
     [SkippableFact]
     public async Task InsertsEntitiesWithConflict_DoNothing()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         _context.TestEntities.Add(new TestEntity { TestRun = _run, Name = $"{_run}_Entity1" });
         await _context.SaveChangesAsync();
@@ -332,7 +335,7 @@ await _context.ExecuteBulkInsertAsync(entities, o =>
     [SkippableFact]
     public async Task InsertsEntitiesWithConflict_Condition()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         _context.TestEntities.Add(new TestEntity { TestRun = _run, Name = $"{_run}_Entity1", Price = 10 });
         await _context.SaveChangesAsync();
@@ -363,7 +366,7 @@ await _context.ExecuteBulkInsertAsync(entities, o =>
     [SkippableFact]
     public async Task InsertsEntitiesWithConflict_MultipleColumns()
     {
-        Skip.If(_context.Database.ProviderName!.Contains("Mysql", StringComparison.InvariantCultureIgnoreCase));
+        Skip.If(_context.IsProvider(ProviderType.MySql));
 
         _context.TestEntities.Add(new TestEntity { TestRun = _run, Name = $"{_run}_Entity1", Price = 10 });
         await _context.SaveChangesAsync();
@@ -548,4 +551,36 @@ public void BulkInsert_WithOpenTransaction_RollsBackOnFailure_Sync()
         Assert.DoesNotContain(insertedEntities, e => e.Name == $"{_run}_EntityWithTxFail1");
         Assert.DoesNotContain(insertedEntities, e => e.Name == $"{_run}_EntityWithTxFail2");
     }
+
+    [SkippableFact]
+    public async Task ThrowsWhenUsingWrongConfigurationType()
+    {
+        // Skip for providers that don't support this feature
+        Skip.If(_context.IsProvider(ProviderType.PostgreSql));
+        Skip.If(_context.IsProvider(ProviderType.Sqlite));
+
+        // Arrange
+        var entities = new List<TestEntity>
+        {
+            new TestEntity { TestRun = _run, Name = $"{_run}_Entity1" },
+            new TestEntity { TestRun = _run, Name = $"{_run}_Entity2" }
+        };
+
+        // Act & Assert
+        if (_context.IsProvider(ProviderType.SqlServer))
+        {
+            await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await _context.ExecuteBulkInsertAsync(entities, (MySqlBulkInsertOptions o) =>
+                {
+                }));
+        }
+
+        if (_context.IsProvider(ProviderType.MySql))
+        {
+            await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+                await _context.ExecuteBulkInsertAsync(entities, (SqlServerBulkInsertOptions o) =>
+                {
+                }));
+        }
+    }
 }


Reviews:
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T15:27:54Z
user: login: PhenX
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-22T15:31:33Z
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T21:36:20Z
user: login: PhenX
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T12:13:13Z
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:15:54Z
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:17:09Z
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:17:48Z
user: login: PhenX
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T15:26:48Z
user: login: SebastianStehle
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T08:06:32Z
user: login: SebastianStehle
body: I have one comment that has not necessarily something todo with this PR. 
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T08:10:15Z
user: login: SebastianStehle
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-24T08:32:43Z

Review comments:
diff_hunk: @@ -148,11 +149,16 @@ public virtual async Task<List<T>> BulkInsertReturnEntities<T>(
         CancellationToken ctk = default
     ) where T : class
     {
+        if (options is not TOptions providerOptions)
path: src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
user: login: SebastianStehle
body: I would still allow the base options
created_at: 2025-05-22T15:27:54Z
author_association: COLLABORATOR
diff_hunk: @@ -148,11 +149,16 @@ public virtual async Task<List<T>> BulkInsertReturnEntities<T>(
         CancellationToken ctk = default
     ) where T : class
     {
+        if (options is not TOptions providerOptions)
path: src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
user: login: PhenX
body: Base options are still allowed with the current syntax 
created_at: 2025-05-22T15:31:33Z
author_association: OWNER
diff_hunk: @@ -148,11 +149,16 @@ public virtual async Task<List<T>> BulkInsertReturnEntities<T>(
         CancellationToken ctk = default
     ) where T : class
     {
+        if (options is not TOptions providerOptions)
path: src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
user: login: SebastianStehle
body: But BaseOption is not a TOption...
created_at: 2025-05-22T21:36:20Z
author_association: COLLABORATOR
diff_hunk: @@ -148,11 +149,16 @@ public virtual async Task<List<T>> BulkInsertReturnEntities<T>(
         CancellationToken ctk = default
     ) where T : class
     {
+        if (options is not TOptions providerOptions)
path: src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
user: login: PhenX
body: The `options` instance is created in the provider specific GetDefaultOptions methode, which returns a provider specific class (or the base class if the provider does not have specific options)
created_at: 2025-05-23T12:13:13Z
author_association: OWNER
diff_hunk: @@ -26,6 +26,9 @@ public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null)
     /// <inheritdoc />
     protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
 
+    /// <inheritdoc />
+    protected override MySqlBulkInsertOptions GetDefaultOptions() => new();
path: src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs
user: login: SebastianStehle
body: I would call it "CreateDefaultOptions()" because you cannot reuse them.

You can also just define that as `TOptions : class, new()` and handle it the base provider.
created_at: 2025-05-23T12:15:54Z
author_association: COLLABORATOR
diff_hunk: @@ -69,4 +71,17 @@ internal static async Task<ConnectionInfo> GetConnection(
 
         return new ConnectionInfo(connection, wasClosed, transaction, wasBegan);
     }
+
+    /// <summary>
+    /// Tells if the current provider is the specified provider type.
+    /// </summary>
+    internal static bool IsProvider(this DbContext context, ProviderType providerType)
+    {
+        if (context.Database.ProviderName == null)
+        {
+            throw new InvalidOperationException("Database provider name is null.");
+        }
+
+        return context.Database.ProviderName.Contains(providerType.ToString(), StringComparison.OrdinalIgnoreCase);
path: src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/DbContextExtensions.cs
user: login: SebastianStehle
body: If you already define the enum, you could also add this to the Provider as property and check just the registered provider.
created_at: 2025-05-23T12:17:09Z
author_association: COLLABORATOR
diff_hunk: @@ -148,11 +149,16 @@ public virtual async Task<List<T>> BulkInsertReturnEntities<T>(
         CancellationToken ctk = default
     ) where T : class
     {
+        if (options is not TOptions providerOptions)
path: src/PhenX.EntityFrameworkCore.BulkInsert/BulkInsertProviderBase.cs
user: login: SebastianStehle
body: Makes sense.
created_at: 2025-05-23T12:17:48Z
author_association: COLLABORATOR
diff_hunk: @@ -26,6 +26,9 @@ public MySqlBulkInsertProvider(ILogger<MySqlBulkInsertProvider>? logger = null)
     /// <inheritdoc />
     protected override string GetTempTableName(string tableName) => $"#_temp_bulk_insert_{tableName}";
 
+    /// <inheritdoc />
+    protected override MySqlBulkInsertOptions GetDefaultOptions() => new();
path: src/PhenX.EntityFrameworkCore.BulkInsert.MySql/MySqlBulkInsertProvider.cs
user: login: PhenX
body: I'll rename it, but will not change the constraint
created_at: 2025-05-23T15:26:48Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,237 @@
+﻿using Microsoft.EntityFrameworkCore;
+
+using PhenX.EntityFrameworkCore.BulkInsert.Options;
+
+namespace PhenX.EntityFrameworkCore.BulkInsert.Extensions;
path: src/PhenX.EntityFrameworkCore.BulkInsert/Extensions/PublicExtensions.DbSet.cs
user: login: SebastianStehle
body: This is the main entry point for the libray. I would move this to the root namespace or even to 'Microsoft.EntityFrameworkCore'.
 My philosopy is that you should only need one using statement for typical (80%) of the tasks.
created_at: 2025-05-24T08:06:32Z
author_association: COLLABORATOR

PR comments:
user: login: SebastianStehle
created_at: 2025-05-22T15:34:47Z
body: I am not the biggest fan. My code is in general provide-agnostics. When you see an Insert(...) call you don't know which provider is actually behing that. I  would only have the provider specific stuff in the Context (when I create multiple contexts, one per provider).

If I have an Insert call, I would have three options:

## Option 1: No provider specific option at all

Moves the options to the UseXYZCall()

## Option 2: Have the opportunity to provide all options

e.g.


```
await context.Insert(..., options => {
   options.MySql.Xyz = 
});
```

But this would create some coupling in my repositories and domain layer.

## Option 3: Have dedicated overrides


e.g. `InsertMySqlAsync(..., MysqlOptions)`

If a provider wants his own option, it should also provide it own overrides for extension method, otherwise the whole option thing is not type safe.
author_association: COLLABORATOR
user: login: PhenX
created_at: 2025-05-22T15:52:31Z
body: Even if we can do this ?

![image](https://github.com/user-attachments/assets/962be18d-bc39-4913-bd71-9cd75da837ab)

Of course, we also can provide option 3
author_association: OWNER
user: login: SebastianStehle
created_at: 2025-05-22T16:38:56Z
body: I think this would also be fine. I would go with Option 1 and your alternative then.
author_association: COLLABORATOR
user: login: PhenX
created_at: 2025-05-24T08:17:05Z
body: Ok, I'll move it in the future, to the root namespace
author_association: OWNER
user: login: PhenX
created_at: 2025-05-24T08:18:41Z
body: Is it ok for you ? it seems I cannot merge it without your approval 😅 
author_association: OWNER
user: login: SebastianStehle
created_at: 2025-05-24T08:30:29Z
body: Yes. But please make squash merges.  my commits are not very clean and now the history is a little bit broken. Have not realized it before.

I have no button to press, no idea why you cannot merge. I have resolved all conversations. Perhaps tis was the culprit?
author_association: COLLABORATOR