Building a typed DbConnection factory

// <summary>
/// Represents a type used to create instances of <see cref="DbConnection"/> based on the given name.
/// </summary>
public interface IDbConnectionFactory
{
    /// <summary>
    /// Creates a new <typeparamref name="TConnection"/> instance using the given <paramref name="name"/>.
    /// </summary>
    /// <param name="name">The name of the connection.</param>
    /// <returns>The <typeparamref name="TConnection"/> that was created.</returns>
    DbConnection Create(string name);
}
/// <summary>
/// A generic interface for creating <see cref="DbConnection"/> where the category name is derived
/// from the specified <typeparamref name="TCategoryName"/> type name.
/// </summary>
/// <typeparam name="TCategoryName">The type who's name is used for the factory category name.</typeparam>
public interface IDbConnectionFactory<out TCategoryName>
{
    /// <summary>
    /// Creates a <see cref="DbConnection"/>.
    /// </summary>
    /// <returns>The newly created <see cref="DbConnection"/>.</returns>
    DbConnection Create();
}

/// <summary>
/// Delegates the create of the <see cref="DbConnection"/> to the provided <see cref="IDbConnectionFactory"/>.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
public class DbConnectionFactory<T> : IDbConnectionFactory<T>
{
    private readonly IDbConnectionFactory _factory;

    /// <summary>
    /// Creates a new instance.
    /// </summary>
    /// <param name="factory">The factory.</param>
    public DbConnectionFactory(IDbConnectionFactory factory)
    {
        _factory = factory;
    }

    /// <inheritdoc/>
    public DbConnection Create()
    {
        return _factory.Create(typeof(T).Name);
    }
}
public static DbConnection Open<T>(this IDbConnectionFactory<T> factory)
{
    var connection = factory.Create();
    connection.Open();
    return connection;
}
/// <summary>
/// Opens a database connection for the given factory.
/// </summary>
/// <typeparam name="TConnection">The type of connection to open.</typeparam>
/// <param name="factory">The connection factory.</param>
/// <returns>The connection of the given type, opened.</returns>
public static TConnection Open<TConnection>(this IDbConnectionFactory<IDbRequire<TConnection>> factory)
    where TConnection : DbConnection
{
    var connection = (TConnection)factory.Create();
    connection.Open();
    return connection;
}
/// <summary>
/// Marker interface to type the required <see cref="DbConnection"/>.
/// </summary>
/// <typeparam name="TConnection">The required type of connection.</typeparam>
public interface IDbRequire<TConnection>
    where TConnection : DbConnection
{
}
public class DbConnectionFactoryTests
{
    [Fact]
    public void Can_resolve_a_factory_of_DbConnection_of_the_required_type()
    {
        var services = new ServiceCollection();
        services.TryAddSingleton(typeof(DbConnectionFactory<>));
        services.AddSingleton<IDbConnectionFactory, StubConnectionFactory>();
        var provider = services.BuildServiceProvider();

        var factory = provider.GetService<DbConnectionFactory<Trait>>();
        Assert.NotNull(factory);

        using (var connection = factory.Create())
        {
            Assert.IsType<SQLiteConnection>(connection);
        }
    }

    #region Test suite helpers

    private class Trait : IDbRequire<SQLiteConnection> { }

    private class StubConnectionFactory : IDbConnectionFactory
    {
        public DbConnection Create(string name)
        {
            return name switch
            {
                nameof(Trait) => new SQLiteConnection("Filename=:memory:"),
                _ => throw new ArgumentOutOfRangeException(nameof(name)),
            };
        }
    }

    #endregion
}

Leave a comment

Please note that we won't show your email to others, or use it for sending unwanted emails. We will only use it to render your Gravatar image and to validate you as a real person.