From 9a1444aeae7f8685de6d5194cdc589b720260c9d Mon Sep 17 00:00:00 2001 From: steffenskov Date: Fri, 6 Feb 2026 20:02:40 +0100 Subject: [PATCH 1/2] feat: Added DI overloads for custom config type --- CHANGELOG.md | 15 ++ Dapper.DDD.Repository.sln | 1 + Directory.Build.props | 2 +- .../DapperRepositoryDependencyInjection.cs | 208 +----------------- ...pperRepositoryDependencyInjection_Table.cs | 108 +++++++++ ...apperRepositoryDependencyInjection_View.cs | 166 ++++++++++++++ .../GlobalUsings.cs | 7 + .../Dapper.DDD.Repository.csproj | 1 + .../Configuration/ContainerFixture.cs | 2 +- .../Configuration/ContainerFixture.cs | 3 +- ...epositoryDependencyInjection_TableTests.cs | 72 ++++++ ...RepositoryDependencyInjection_ViewTests.cs | 70 ++++++ 12 files changed, 444 insertions(+), 211 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs create mode 100644 src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_View.cs create mode 100644 src/Dapper.DDD.Repository.DependencyInjection/GlobalUsings.cs create mode 100644 tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_TableTests.cs create mode 100644 tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_ViewTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1702ddc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.11.0] - 2026-02-06 + +### Added + +- Added two overloads for Dependency injection offering the use of a custom configuration type for Table and View repositories respectively. + - This can be useful if you want to add extra configuration to your repos and don't want to deal with the overload taking a constructor delegate. \ No newline at end of file diff --git a/Dapper.DDD.Repository.sln b/Dapper.DDD.Repository.sln index 1a36a67..4682726 100644 --- a/Dapper.DDD.Repository.sln +++ b/Dapper.DDD.Repository.sln @@ -28,6 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props + CHANGELOG.md = CHANGELOG.md EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.DDD.Repository.PostGreSql", "src\Dapper.DDD.Repository.PostGreSql\Dapper.DDD.Repository.PostGreSql.csproj", "{92DAD05A-C310-40D0-BB38-D27C4B565EC3}" diff --git a/Directory.Build.props b/Directory.Build.props index c3197d6..de8bf99 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 1.10.3 + 1.11.0 diff --git a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection.cs b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection.cs index e535c3f..5389d55 100644 --- a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection.cs +++ b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection.cs @@ -1,21 +1,6 @@ -using Dapper.DDD.Repository.Configuration; -using Dapper.DDD.Repository.Interfaces; -using Dapper.DDD.Repository.Repositories; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - namespace Dapper.DDD.Repository.DependencyInjection; -public delegate TRepository TableRepositoryConstructorDelegate( - IOptions> options, IOptions? defaultOptions, IServiceProvider provider) - where TAggregate : notnull; - -public delegate TRepository ViewRepositoryConstructorDelegate( - IOptions> options, IOptions? defaultOptions, - IServiceProvider provider) - where TAggregate : notnull; - -public static class DapperRepositoryDependencyInjection +public static partial class DapperRepositoryDependencyInjection { /// /// Configure defaults to use for all aggregate types. @@ -25,195 +10,4 @@ public static IServiceCollection ConfigureDapperRepositoryDefaults(this IService { return services.Configure(configureOptions); } - - /// - /// Add a table repository for the given aggregate type to the dependency injection system. - /// You can request an ITableRepository through the dependency injection system afterwards. - /// - public static IServiceCollection AddTableRepository(this IServiceCollection services, - Action> configureOptions) - where TAggregate : notnull - where TAggregateId : notnull - { - services.Configure(configureOptions); - services - .AddSingleton, TableRepository>(); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// You can request an IViewRepository through the dependency injection system afterwards. - /// - public static IServiceCollection AddViewRepository(this IServiceCollection services, - Action> configureOptions) - where TAggregate : notnull - where TAggregateId : notnull - { - services.Configure(configureOptions); - services - .AddSingleton, ViewRepository>(); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// You can request an IViewRepository through the dependency injection system afterwards. - /// - public static IServiceCollection AddViewRepository(this IServiceCollection services, - Action> configureOptions) - where TAggregate : notnull - { - services.Configure(configureOptions); - services.AddSingleton, ViewRepository>(); - return services; - } - - /// - /// Add a table repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in TableRepository type. - /// - /// Type of your aggregate - /// Type of your aggregate's Id - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddTableRepository(this IServiceCollection services, - Action> configureOptions) - where TAggregate : notnull - where TAggregateId : notnull - where TRepositoryInterface : class - where TRepositoryClass : TableRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(); - return services; - } - - - /// - /// Add a table repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in TableRepository type. - /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. - /// - /// Type of your aggregate - /// Type of your aggregate's Id - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddTableRepository(this IServiceCollection services, - Action> configureOptions, - TableRepositoryConstructorDelegate constructor) - where TAggregate : notnull - where TAggregateId : notnull - where TRepositoryInterface : class - where TRepositoryClass : TableRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(provider => - { - var configuration = provider.GetRequiredService>>(); - var defaultConfiguration = provider.GetService>(); - return constructor(configuration, defaultConfiguration, provider); - }); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. - /// - /// Type of your aggregate - /// Type of your aggregate's Id - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddViewRepository(this IServiceCollection services, - Action> configureOptions) - where TAggregate : notnull - where TAggregateId : notnull - where TRepositoryInterface : class - where TRepositoryClass : ViewRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. - /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. - /// - /// Type of your aggregate - /// Type of your aggregate's Id - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddViewRepository(this IServiceCollection services, - Action> configureOptions, - ViewRepositoryConstructorDelegate constructor) - where TAggregate : notnull - where TAggregateId : notnull - where TRepositoryInterface : class - where TRepositoryClass : ViewRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(provider => - { - var configuration = provider.GetRequiredService>>(); - var defaultConfiguration = provider.GetService>(); - return constructor(configuration, defaultConfiguration, provider); - }); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. - /// - /// Type of your aggregate - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddViewRepository( - this IServiceCollection services, Action> configureOptions) - where TAggregate : notnull - where TRepositoryInterface : class - where TRepositoryClass : ViewRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(); - return services; - } - - /// - /// Add a view repository for the given aggregate type to the dependency injection system. - /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. - /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. - /// - /// Type of your aggregate - /// Interface type of your repository - /// Actual implementation type of your repository - /// Used to configure the repository via lambda - public static IServiceCollection AddViewRepository( - this IServiceCollection services, Action> configureOptions, - ViewRepositoryConstructorDelegate constructor) - where TAggregate : notnull - where TRepositoryInterface : class - where TRepositoryClass : ViewRepository, TRepositoryInterface - { - services.Configure(configureOptions); - services.AddSingleton(provider => - { - var configuration = provider.GetRequiredService>>(); - var defaultConfiguration = provider.GetService>(); - return constructor(configuration, defaultConfiguration, provider); - }); - return services; - } } \ No newline at end of file diff --git a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs new file mode 100644 index 0000000..98029bd --- /dev/null +++ b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs @@ -0,0 +1,108 @@ +namespace Dapper.DDD.Repository.DependencyInjection; + +public delegate TRepository TableRepositoryConstructorDelegate( + IOptions> options, IOptions? defaultOptions, IServiceProvider provider) + where TAggregate : notnull; + +public static partial class DapperRepositoryDependencyInjection +{ + /// + /// Add a table repository for the given aggregate type to the dependency injection system. + /// You can request an ITableRepository through the dependency injection system afterwards. + /// + public static IServiceCollection AddTableRepository(this IServiceCollection services, + Action> configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + { + services.Configure(configureOptions); + services + .AddSingleton, TableRepository>(); + return services; + } + + /// + /// Add a table repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in TableRepository type. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddTableRepository(this IServiceCollection services, + Action> configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : TableRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(); + return services; + } + + /// + /// Add a table repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in TableRepository type. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// + /// Custom type of configuration for your repository, useful if you want to append custom + /// configuration. Your repository constructor should expect this type instead of ViewAggregateConfiguration. + /// + /// Used to configure the repository via lambda + public static IServiceCollection AddTableRepository(this IServiceCollection services, + Action configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : TableRepository, TRepositoryInterface + where TConfiguration : TableAggregateConfiguration, new() + { + var ctorInfo = typeof(TRepositoryClass).GetConstructor([typeof(IOptions), typeof(IOptions)]); + if (ctorInfo is null || ctorInfo.GetParameters()[0].ParameterType != typeof(IOptions)) + { + throw new ArgumentException($"The constructor for {typeof(TRepositoryClass).Name} does not take IOptions<{typeof(TConfiguration).Name}> as argument!", nameof(configureOptions)); + } + + services.Configure(configureOptions); + services.AddSingleton(); + return services; + } + + + /// + /// Add a table repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in TableRepository type. + /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddTableRepository(this IServiceCollection services, + Action> configureOptions, + TableRepositoryConstructorDelegate constructor) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : TableRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(provider => + { + var configuration = provider.GetRequiredService>>(); + var defaultConfiguration = provider.GetService>(); + return constructor(configuration, defaultConfiguration, provider); + }); + return services; + } +} \ No newline at end of file diff --git a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_View.cs b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_View.cs new file mode 100644 index 0000000..baf4e20 --- /dev/null +++ b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_View.cs @@ -0,0 +1,166 @@ +namespace Dapper.DDD.Repository.DependencyInjection; + +public delegate TRepository ViewRepositoryConstructorDelegate( + IOptions> options, IOptions? defaultOptions, + IServiceProvider provider) + where TAggregate : notnull; + +public static partial class DapperRepositoryDependencyInjection +{ + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// You can request an IViewRepository through the dependency injection system afterwards. + /// + public static IServiceCollection AddViewRepository(this IServiceCollection services, + Action> configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + { + services.Configure(configureOptions); + services + .AddSingleton, ViewRepository>(); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// You can request an IViewRepository through the dependency injection system afterwards. + /// + public static IServiceCollection AddViewRepository(this IServiceCollection services, + Action> configureOptions) + where TAggregate : notnull + { + services.Configure(configureOptions); + services.AddSingleton, ViewRepository>(); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddViewRepository(this IServiceCollection services, + Action> configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : ViewRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// + /// Custom type of configuration for your repository, useful if you want to append custom + /// configuration. Your repository constructor should expect this type instead of ViewAggregateConfiguration. + /// + /// Used to configure the repository via lambda + public static IServiceCollection AddViewRepository(this IServiceCollection services, + Action configureOptions) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : ViewRepository, TRepositoryInterface + where TConfiguration : ViewAggregateConfiguration, new() + { + var ctorInfo = typeof(TRepositoryClass).GetConstructor([typeof(IOptions), typeof(IOptions)]); + if (ctorInfo is null || ctorInfo.GetParameters()[0].ParameterType != typeof(IOptions)) + { + throw new ArgumentException($"The constructor for {typeof(TRepositoryClass).Name} does not take IOptions<{typeof(TConfiguration).Name}> as argument!", nameof(configureOptions)); + } + + services.Configure(configureOptions); + services.AddSingleton(); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. + /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. + /// + /// Type of your aggregate + /// Type of your aggregate's Id + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddViewRepository(this IServiceCollection services, + Action> configureOptions, + ViewRepositoryConstructorDelegate constructor) + where TAggregate : notnull + where TAggregateId : notnull + where TRepositoryInterface : class + where TRepositoryClass : ViewRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(provider => + { + var configuration = provider.GetRequiredService>>(); + var defaultConfiguration = provider.GetService>(); + return constructor(configuration, defaultConfiguration, provider); + }); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. + /// + /// Type of your aggregate + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddViewRepository( + this IServiceCollection services, Action> configureOptions) + where TAggregate : notnull + where TRepositoryInterface : class + where TRepositoryClass : ViewRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(); + return services; + } + + /// + /// Add a view repository for the given aggregate type to the dependency injection system. + /// Uses a custom class and interface, allowing you to inherit from the built-in ViewRepository type. + /// Takes a custom function to create the actual instance, allowing you to set any instance-specific state. + /// + /// Type of your aggregate + /// Interface type of your repository + /// Actual implementation type of your repository + /// Used to configure the repository via lambda + public static IServiceCollection AddViewRepository( + this IServiceCollection services, Action> configureOptions, + ViewRepositoryConstructorDelegate constructor) + where TAggregate : notnull + where TRepositoryInterface : class + where TRepositoryClass : ViewRepository, TRepositoryInterface + { + services.Configure(configureOptions); + services.AddSingleton(provider => + { + var configuration = provider.GetRequiredService>>(); + var defaultConfiguration = provider.GetService>(); + return constructor(configuration, defaultConfiguration, provider); + }); + return services; + } +} \ No newline at end of file diff --git a/src/Dapper.DDD.Repository.DependencyInjection/GlobalUsings.cs b/src/Dapper.DDD.Repository.DependencyInjection/GlobalUsings.cs new file mode 100644 index 0000000..68c1877 --- /dev/null +++ b/src/Dapper.DDD.Repository.DependencyInjection/GlobalUsings.cs @@ -0,0 +1,7 @@ +// Global using directives + +global using Dapper.DDD.Repository.Configuration; +global using Dapper.DDD.Repository.Interfaces; +global using Dapper.DDD.Repository.Repositories; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; \ No newline at end of file diff --git a/src/Dapper.DDD.Repository/Dapper.DDD.Repository.csproj b/src/Dapper.DDD.Repository/Dapper.DDD.Repository.csproj index f2bbd87..ee12582 100644 --- a/src/Dapper.DDD.Repository/Dapper.DDD.Repository.csproj +++ b/src/Dapper.DDD.Repository/Dapper.DDD.Repository.csproj @@ -18,6 +18,7 @@ + diff --git a/tests/Dapper.DDD.Repository.MySql.IntegrationTests/Configuration/ContainerFixture.cs b/tests/Dapper.DDD.Repository.MySql.IntegrationTests/Configuration/ContainerFixture.cs index 0a1184b..17c4dc7 100644 --- a/tests/Dapper.DDD.Repository.MySql.IntegrationTests/Configuration/ContainerFixture.cs +++ b/tests/Dapper.DDD.Repository.MySql.IntegrationTests/Configuration/ContainerFixture.cs @@ -70,7 +70,7 @@ public async ValueTask DisposeAsync() private async Task InitializeTestContainerAsync() { - _container = new MySqlBuilder() + _container = new MySqlBuilder("mysql:latest") .WithDatabase("northwind") .Build(); diff --git a/tests/Dapper.DDD.Repository.Sql.IntegrationTests/Configuration/ContainerFixture.cs b/tests/Dapper.DDD.Repository.Sql.IntegrationTests/Configuration/ContainerFixture.cs index f389fa1..f75d386 100644 --- a/tests/Dapper.DDD.Repository.Sql.IntegrationTests/Configuration/ContainerFixture.cs +++ b/tests/Dapper.DDD.Repository.Sql.IntegrationTests/Configuration/ContainerFixture.cs @@ -102,8 +102,7 @@ public async ValueTask DisposeAsync() private async Task InitializeTestContainerAsync() { - _container = new MsSqlBuilder() - .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + _container = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest") .WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(MsSqlBuilder.MsSqlPort)) .Build(); diff --git a/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_TableTests.cs b/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_TableTests.cs new file mode 100644 index 0000000..dc81d29 --- /dev/null +++ b/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_TableTests.cs @@ -0,0 +1,72 @@ +using Dapper.DDD.Repository.DependencyInjection; +using Dapper.DDD.Repository.Repositories; + +namespace Dapper.DDD.Repository.UnitTests.DependencyInjection; + +public partial class DapperRepositoryDependencyInjectionTests +{ + [Fact] + public void AddTableRepository_CustomConfigurationWithMismatchedConstructor_Throws() + { + // Arrange + var services = new ServiceCollection(); + + // Act && Assert + var ex = Assert.Throws(() => { services.AddTableRepository(_ => { }); }); + + Assert.Contains($"The constructor for {typeof(UserRepositoryWithWrongConstructor).Name} does not take IOptions<{typeof(CustomConfig).Name}> as argument!", ex.Message); + } + + [Fact] + public void AddTableRepository_CustomConfigurationWithProperConstructor_ReceivesCustomConfig() + { + // Arrange + var services = new ServiceCollection(); + var guid = Guid.NewGuid(); + + // Act + services.AddTableRepository(config => + { + config.ConnectionFactory = Substitute.For(); + config.DapperInjectionFactory = Substitute.For(); + config.QueryGeneratorFactory = new MockQueryGeneratorFactory(); + config.CustomArgument = guid; + config.HasKey(e => e.Id); + config.TableName = "Users"; + }); + var provider = services.BuildServiceProvider(); + + // Assert + var repo = provider.GetRequiredService() as UserRepositoryWithRightConstructor; + Assert.NotNull(repo); + Assert.Equal(guid, repo.CustomArgument); + } + + public record User(Guid Id, string Name); +} + +file class CustomConfig : TableAggregateConfiguration +{ + public Guid CustomArgument { get; set; } +} + +file interface IUserRepository : ITableRepository +{ +} + +file class UserRepositoryWithWrongConstructor : TableRepository, IUserRepository +{ + public UserRepositoryWithWrongConstructor(IOptions> options, IOptions? defaultOptions) : base(options, defaultOptions) + { + } +} + +file class UserRepositoryWithRightConstructor : TableRepository, IUserRepository +{ + public UserRepositoryWithRightConstructor(IOptions options, IOptions? defaultOptions) : base(options, defaultOptions) + { + CustomArgument = options.Value.CustomArgument; + } + + public Guid CustomArgument { get; } +} \ No newline at end of file diff --git a/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_ViewTests.cs b/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_ViewTests.cs new file mode 100644 index 0000000..be6f4e4 --- /dev/null +++ b/tests/Dapper.DDD.Repository.UnitTests/DependencyInjection/DapperRepositoryDependencyInjection_ViewTests.cs @@ -0,0 +1,70 @@ +using Dapper.DDD.Repository.DependencyInjection; +using Dapper.DDD.Repository.Repositories; + +namespace Dapper.DDD.Repository.UnitTests.DependencyInjection; + +public partial class DapperRepositoryDependencyInjectionTests +{ + [Fact] + public void AddViewRepository_CustomConfigurationWithMismatchedConstructor_Throws() + { + // Arrange + var services = new ServiceCollection(); + + // Act && Assert + var ex = Assert.Throws(() => { services.AddViewRepository(_ => { }); }); + + Assert.Contains($"The constructor for {typeof(UserRepositoryWithWrongConstructor).Name} does not take IOptions<{typeof(CustomConfig).Name}> as argument!", ex.Message); + } + + [Fact] + public void AddViewRepository_CustomConfigurationWithProperConstructor_ReceivesCustomConfig() + { + // Arrange + var services = new ServiceCollection(); + var guid = Guid.NewGuid(); + + // Act + services.AddViewRepository(config => + { + config.ConnectionFactory = Substitute.For(); + config.DapperInjectionFactory = Substitute.For(); + config.QueryGeneratorFactory = new MockQueryGeneratorFactory(); + config.CustomArgument = guid; + config.HasKey(e => e.Id); + config.ViewName = "Users"; + }); + var provider = services.BuildServiceProvider(); + + // Assert + var repo = provider.GetRequiredService() as UserRepositoryWithRightConstructor; + Assert.NotNull(repo); + Assert.Equal(guid, repo.CustomArgument); + } +} + +file class CustomConfig : ViewAggregateConfiguration +{ + public Guid CustomArgument { get; set; } +} + +file interface IUserRepository : IViewRepository +{ +} + +file class UserRepositoryWithWrongConstructor : ViewRepository, IUserRepository +{ + public UserRepositoryWithWrongConstructor(IOptions> options, IOptions? defaultOptions) : base(options, defaultOptions) + { + } +} + +file class UserRepositoryWithRightConstructor : ViewRepository, IUserRepository +{ + public UserRepositoryWithRightConstructor(IOptions options, IOptions? defaultOptions) : base(options, defaultOptions) + { + CustomArgument = options.Value.CustomArgument; + } + + public Guid CustomArgument { get; } +} \ No newline at end of file From 503ca87d7d3195de965b501d59b59303b9e22a71 Mon Sep 17 00:00:00 2001 From: steffenskov Date: Fri, 6 Feb 2026 20:09:25 +0100 Subject: [PATCH 2/2] fixed xml doc --- .../DapperRepositoryDependencyInjection_Table.cs | 3 ++- .../DapperRepositoryDependencyInjection_View.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs index 98029bd..1317c15 100644 --- a/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs +++ b/src/Dapper.DDD.Repository.DependencyInjection/DapperRepositoryDependencyInjection_Table.cs @@ -53,7 +53,8 @@ public static IServiceCollection AddTableRepository(th /// Actual implementation type of your repository /// /// Custom type of configuration for your repository, useful if you want to append custom - /// configuration. Your repository constructor should expect this type instead of ViewAggregateConfiguration. + /// configuration. Your repository constructor should expect this type instead of TableAggregateConfiguration< + /// TAggregate>. /// /// Used to configure the repository via lambda public static IServiceCollection AddTableRepository(this IServiceColl /// Actual implementation type of your repository /// /// Custom type of configuration for your repository, useful if you want to append custom - /// configuration. Your repository constructor should expect this type instead of ViewAggregateConfiguration. + /// configuration. Your repository constructor should expect this type instead of ViewAggregateConfiguration< + /// TAggregate>. /// /// Used to configure the repository via lambda public static IServiceCollection AddViewRepository