Dependency Injection
The AdoNet.Async.Adapters package includes a DI extension method that registers an IAsyncDbProviderFactory in your IServiceCollection. This gives you a clean, testable abstraction for database access.
Registration
using System.Data.Async.Adapters;
using Microsoft.Data.SqlClient;
var builder = WebApplication.CreateBuilder(args);
// Register from any DbProviderFactory
services.AddAsyncData(SqlClientFactory.Instance);
The AddAsyncData method wraps the DbProviderFactory in an AdapterDbProviderFactory and registers it as a singleton IAsyncDbProviderFactory.
You can also register a custom IAsyncDbProviderFactory directly:
services.AddAsyncData(myCustomFactory);
Injecting IAsyncDbProviderFactory
Once registered, inject IAsyncDbProviderFactory into your services:
public interface IAsyncDbProviderFactory
{
IAsyncDbConnection CreateConnection();
IAsyncDbCommand CreateCommand();
IDbDataParameter CreateParameter();
}
The factory creates adapter-wrapped instances. Connections created by the factory are AdapterDbConnection instances, commands are AdapterDbCommand instances, and so on.
Repository Pattern Example
Here is a complete repository pattern implementation using constructor injection:
using System.Data.Async;
using System.Data.Async.DataSet;
using System.Data.Async.Adapters;
public class UserRepository
{
private readonly IAsyncDbProviderFactory _factory;
private readonly string _connectionString;
public UserRepository(IAsyncDbProviderFactory factory, IConfiguration config)
{
_factory = factory;
_connectionString = config.GetConnectionString("Default")!;
}
public async Task<AsyncDataTable> GetAllUsersAsync(CancellationToken ct = default)
{
await using var conn = _factory.CreateConnection();
conn.ConnectionString = _connectionString;
await conn.OpenAsync(ct);
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Id, Name, Email FROM Users";
var table = new AsyncDataTable("Users");
var adapter = new AdapterDbDataAdapter(cmd);
await adapter.FillAsync(table, ct);
return table;
}
public async Task<string?> GetUserNameAsync(int id, CancellationToken ct = default)
{
await using var conn = _factory.CreateConnection();
conn.ConnectionString = _connectionString;
await conn.OpenAsync(ct);
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Name FROM Users WHERE Id = @id";
var param = cmd.CreateParameter();
param.ParameterName = "@id";
param.Value = id;
cmd.Parameters.Add(param);
var result = await cmd.ExecuteScalarAsync(ct);
return result as string;
}
public async Task CreateUserAsync(string name, string email, CancellationToken ct = default)
{
await using var conn = _factory.CreateConnection();
conn.ConnectionString = _connectionString;
await conn.OpenAsync(ct);
await using var tx = await conn.BeginTransactionAsync(ct);
var cmd = conn.CreateCommand();
cmd.Transaction = tx;
cmd.CommandText = "INSERT INTO Users (Name, Email) VALUES (@name, @email)";
var nameParam = cmd.CreateParameter();
nameParam.ParameterName = "@name";
nameParam.Value = name;
cmd.Parameters.Add(nameParam);
var emailParam = cmd.CreateParameter();
emailParam.ParameterName = "@email";
emailParam.Value = email;
cmd.Parameters.Add(emailParam);
await cmd.ExecuteNonQueryAsync(ct);
await tx.CommitAsync(ct);
}
}
Register the repository:
builder.Services.AddAsyncData(SqlClientFactory.Instance);
builder.Services.AddScoped<UserRepository>();
Using with Different Providers
The same repository code works with any ADO.NET provider -- just change the factory registration:
// SQL Server
using Microsoft.Data.SqlClient;
services.AddAsyncData(SqlClientFactory.Instance);
// PostgreSQL
using Npgsql;
services.AddAsyncData(NpgsqlFactory.Instance);
// MySQL
using MySqlConnector;
services.AddAsyncData(MySqlConnectorFactory.Instance);
// SQLite
using Microsoft.Data.Sqlite;
services.AddAsyncData(SqliteFactory.Instance);
Your repository code needs no changes -- it only depends on IAsyncDbProviderFactory.
This pattern makes it straightforward to swap database providers in tests. Register an in-memory SQLite factory for unit tests and the real provider for production.
Unit Testing
Because IAsyncDbProviderFactory is an interface, it is straightforward to mock in unit tests:
// Using a real SQLite in-memory database for integration tests
var services = new ServiceCollection();
services.AddAsyncData(SqliteFactory.Instance);
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IAsyncDbProviderFactory>();
await using var conn = factory.CreateConnection();
conn.ConnectionString = "Data Source=:memory:";
await conn.OpenAsync();
// Set up test schema and run your repository methods...
Combining with Typed DataSets
For typed DataSet usage, the DI pattern works naturally:
public class OrderService
{
private readonly IAsyncDbProviderFactory _factory;
private readonly string _connectionString;
public OrderService(IAsyncDbProviderFactory factory, IConfiguration config)
{
_factory = factory;
_connectionString = config.GetConnectionString("Default")!;
}
public async Task<AsyncOrdersDS> GetOrdersAsync(CancellationToken ct = default)
{
await using var conn = _factory.CreateConnection();
conn.ConnectionString = _connectionString;
await conn.OpenAsync(ct);
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM Orders";
var ds = new AsyncOrdersDS();
var adapter = new AdapterDbDataAdapter(cmd);
await adapter.FillAsync(ds, ct);
return ds;
}
}
Next Steps
- Wrapping Providers -- details on the adapter classes
- Migrate Existing Code -- step-by-step migration guide
- JSON API with Typed DataSets -- full ASP.NET Core example