Introduction

In Part 1, we implemented bulk CRUD operations using:

However, when dealing with 100K–1M+ records, even that approach can be further optimized.

In this article, we will explore:

Why Go Beyond Dapper?

While Dapper is fast, it still executes:

⚠️ Multiple parameterized executions internally

Problem:

Solution: Oracle Array Binding (ODP.NET)

What is Array Binding?

Array Binding allows you to:

Bulk Insert Using Array Binding

Implementation

using Oracle.ManagedDataAccess.Client;
using System.Data;

public async Task BulkInsert_ArrayBinding(List<Employee> employees)
{
    using var connection = new OracleConnection(_connectionString);
    await connection.OpenAsync();

    using var transaction = connection.BeginTransaction();

    using var command = connection.CreateCommand();
    command.Transaction = transaction;

    command.CommandText = @"
        INSERT INTO EMPLOYEES (ID, NAME, EMAIL, SALARY)
        VALUES (EMP_SEQ.NEXTVAL, :Name, :Email, :Salary)";

    // Array Binding
    command.ArrayBindCount = employees.Count;

    command.Parameters.Add(":Name", OracleDbType.Varchar2,
        employees.Select(e => e.Name).ToArray(), ParameterDirection.Input);

    command.Parameters.Add(":Email", OracleDbType.Varchar2,
        employees.Select(e => e.Email).ToArray(), ParameterDirection.Input);

    command.Parameters.Add(":Salary", OracleDbType.Decimal,
        employees.Select(e => e.Salary).ToArray(), ParameterDirection.Input);

    await command.ExecuteNonQueryAsync();

    transaction.Commit();
}

Bulk Update Using Array Binding

public async Task BulkUpdate_ArrayBinding(List<Employee> employees)
{
    using var connection = new OracleConnection(_connectionString);
    await connection.OpenAsync();

    using var transaction = connection.BeginTransaction();

    using var command = connection.CreateCommand();
    command.Transaction = transaction;

    command.CommandText = @"
        UPDATE EMPLOYEES 
        SET NAME = :Name, EMAIL = :Email, SALARY = :Salary
        WHERE ID = :Id";

    command.ArrayBindCount = employees.Count;

    command.Parameters.Add(":Name", OracleDbType.Varchar2,
        employees.Select(e => e.Name).ToArray(), ParameterDirection.Input);

    command.Parameters.Add(":Email", OracleDbType.Varchar2,
        employees.Select(e => e.Email).ToArray(), ParameterDirection.Input);

    command.Parameters.Add(":Salary", OracleDbType.Decimal,
        employees.Select(e => e.Salary).ToArray(), ParameterDirection.Input);

    command.Parameters.Add(":Id", OracleDbType.Int32,
        employees.Select(e => e.Id).ToArray(), ParameterDirection.Input);

    await command.ExecuteNonQueryAsync();

    transaction.Commit();
}

Bulk Delete Using Array Binding

public async Task BulkDelete_ArrayBinding(List<int> ids)
{
    using var connection = new OracleConnection(_connectionString);
    await connection.OpenAsync();

    using var transaction = connection.BeginTransaction();

    using var command = connection.CreateCommand();
    command.Transaction = transaction;

    command.CommandText = @"DELETE FROM EMPLOYEES WHERE ID = :Id";

    command.ArrayBindCount = ids.Count;

    command.Parameters.Add(":Id", OracleDbType.Int32,
        ids.ToArray(), ParameterDirection.Input);

    await command.ExecuteNonQueryAsync();

    transaction.Commit();
}

Performance Benchmark Comparison

Approach100K Records TimeDB CallsPerformance
Row-by-Row30–60 sec100KVery Slow
Dapper Bulk8–15 secMultipleMedium
GTT + SP3–6 secFewFast
Array Binding1–2 sec1Ultra Fast

Parallel Batch Processing (Advanced)

For extremely large datasets (500K+), combine:

✔ Array Binding
✔ Parallel Processing

var batches = employees.Chunk(20000);

await Task.WhenAll(batches.Select(batch =>
    BulkInsert_ArrayBinding(batch.ToList())));

Memory Optimization Techniques

1. Stream Data Instead of Loading All

Avoid:

var data = GetAllRecords(); //  Loads everything in memory

Use:

await foreach (var batch in GetBatchesAsync())
{
    await BulkInsert_ArrayBinding(batch);
}

2. Control Batch Size

Batch SizeUse Case
5K–10KSafe default
20K–50KHigh performance
100K+Risky (memory spike)

Transaction Optimization

Best Practices

Error Handling Strategy

try
{
    await BulkInsert_ArrayBinding(data);
}
catch (OracleException ex)
{
    // Log error
    throw;
}

When to Use What?

ScenarioBest Approach
<10K recordsDapper
10K–100KGTT + Stored Procedure
100K–1M+🚀 Array Binding
Real-time streamingBatch + Array Binding

Key Takeaways

Conclusion

By leveraging Oracle array binding, you unlock maximum database performance in ASP.NET Core applications. Combined with proper batching and transaction control, this approach can scale to millions of records efficiently.