Codebelt

Savvyio.Extensions.DependencyInjection.Dapper

Marker-aware Dapper registrations for Savvy I/O applications that use Microsoft Dependency Injection.

.NET 10.0 / .NET 9.0 MIT v5.0.8 10,818 downloads

Overview

Savvyio.Extensions.DependencyInjection.Dapper wires Dapper-backed data sources and stores into Microsoft Dependency Injection. It extends Savvyio.Extensions.Dapper and Savvyio.Extensions.DependencyInjection with registration helpers, a marker-aware IDapperDataSource<TMarker> contract, and a matching DapperDataSource<TMarker> implementation.

The package is about registration and typed resolution, not query authoring or SQL execution. Its extensions either register the default DapperDataSource implementation and bind DapperDataSourceOptions, or register DapperDataStore<T, DapperQueryOptions> derivatives so they can be resolved through Savvy I/O store abstractions.

Key APIs

AddDapperDataSource(this IServiceCollection, Action<DapperDataSourceOptions>, Action<ServiceOptions>?) registers the default non-generic DapperDataSource implementation and stores the supplied data source configuration through AddConfiguredOptions. The tests show that the container resolves the same instance through both IDapperDataSource and IDataSource.

AddDapperDataSource<TMarker>(this IServiceCollection, Action<DapperDataSourceOptions<TMarker>>, Action<ServiceOptions>?) adds the same registration model but scopes it with a marker type. The tests verify that the resulting service resolves as both IDapperDataSource<TMarker> and IDataSource<TMarker>, which is how the package separates multiple Dapper-backed sources in one container.

AddDapperDataSource<TService>(this IServiceCollection, Action<ServiceOptions>?) is the lower-level registration hook for custom IDapperDataSource implementations. It delegates to the generic data-source registration pipeline from the DI package, so consumers can swap in a specialized implementation without changing the Savvy I/O abstractions they resolve.

AddDapperDataStore<TService, T>(this IServiceCollection) registers a DapperDataStore<T, DapperQueryOptions> derivative as a persistent data store. The package tests confirm that a registered store can then be resolved through IPersistentDataStore<T, DapperQueryOptions>.

DapperDataSource<TMarker> is the marker-specific implementation type used by the generic registration overload. Its constructor accepts DapperDataSourceOptions<TMarker> and delegates to the lower-level DapperDataSource base class for option validation and connection creation.

IDapperDataSource<TMarker> combines the Dapper-oriented IDapperDataSource contract with IDataSource<TMarker>. That combination is what lets the same registration participate in Dapper access and DI marker resolution.

DapperDataSourceOptions<TMarker> is the marker-aware options type for IDapperDataSource<TMarker>. It inherits the actual connection contract from DapperDataSourceOptions, including the required ConnectionFactory delegate that supplies the underlying IDbConnection.

Basic usage

using System;
using System.Data;
using System.Data.Common;
using Codebelt.Extensions.Xunit;
using Microsoft.Extensions.DependencyInjection;
using Savvyio.Extensions.DependencyInjection;
using Savvyio.Extensions.DependencyInjection.Dapper;
using Xunit;

namespace MyProject.Tests;

public class DapperDataSourceRegistrationTest : Test
{
    public DapperDataSourceRegistrationTest(ITestOutputHelper output) : base(output) { }

    [Fact]
    public void AddDapperDataSource_WithMarker_RegistersATypeForwardedSource()
    {
        var services = new ServiceCollection();
        services.AddDapperDataSource<ReadModelStore>(o => o.ConnectionFactory = () => new InMemoryConnection());

        using var provider = services.BuildServiceProvider();
        var dapper = provider.GetRequiredService<IDapperDataSource<ReadModelStore>>();
        var forwarded = provider.GetRequiredService<IDataSource<ReadModelStore>>();

        TestOutput.WriteLine($"Resolved {dapper.GetType().Name} with state {dapper.State}.");
        Assert.IsType<DapperDataSource<ReadModelStore>>(dapper);
        Assert.Equal(ConnectionState.Open, dapper.State);
        Assert.Same(dapper, forwarded);
    }

    private sealed class ReadModelStore
    {
    }

    private sealed class InMemoryConnection : DbConnection
    {
        private ConnectionState _state = ConnectionState.Closed;
        public override string ConnectionString { get; set; } = "memory";
        public override string Database => "memory";
        public override string DataSource => "memory";
        public override string ServerVersion => "1.0";
        public override ConnectionState State => _state;
        public override void ChangeDatabase(string databaseName) => throw new NotSupportedException();
        public override void Close() => _state = ConnectionState.Closed;
        public override void Open() => _state = ConnectionState.Open;
        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException();
        protected override DbCommand CreateDbCommand() => throw new NotSupportedException();
    }
}

Use this pattern when you want a Dapper-backed data source registered in the container under a marker type so the correct implementation can be requested explicitly. It matters because the registration resolves through both IDapperDataSource<TMarker> and IDataSource<TMarker> while still using the Dapper package's connection lifecycle.

Installation

dotnet add package Savvyio.Extensions.DependencyInjection.Dapper

Usage guidance

Choose this package when your application already uses Microsoft Dependency Injection and you want Savvy I/O Dapper data sources or DapperDataStore<T, DapperQueryOptions> implementations registered through the container, especially when marker types distinguish multiple data sources. If you only need the Dapper abstractions and connection options without DI registration, use Savvyio.Extensions.Dapper instead.

Family packages