From d5bda0e86f293d2b62a93e3f432621826e513e71 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 26 Jun 2025 17:31:47 +0200 Subject: [PATCH 1/5] Refactored data provider registrations. --- .../DbContextDataProvider.cs | 48 ++++++--- .../DbContextDataProviderOptions.cs | 11 +- .../DbContextDataProviderServices.cs | 94 ---------------- .../EntityFrameworkDataProviderBuilder.cs | 14 +-- ...yFrameworkDataProviderBuilderExtensions.cs | 55 ++++++++-- .../DbContextDataProvider.cs | 73 ++++++------- .../DbContextDataProviderOptions.cs | 11 +- .../DbContextDataProviderServices.cs | 94 ---------------- .../EntityFrameworkCoreDataProviderBuilder.cs | 14 +-- ...meworkCoreDataProviderBuilderExtensions.cs | 46 ++++++-- .../MongoDataProviderBuilder.cs | 14 +-- .../MongoDataProviderBuilderExtensions.cs | 75 +++++++++++-- .../MongoClientProvider.cs | 48 +++++++++ .../MongoDataProvider.cs | 54 ++++++---- .../MongoDataProviderOptions.cs | 13 ++- .../MongoDataProviderServices.cs | 102 ------------------ .../MongoRepository.cs | 24 ++++- src/AppCoreNet.Data/DataProviderResolver.cs | 23 ++-- .../DataProviderBuilderExtensions.cs | 73 +++++-------- src/AppCoreNet.Data/ProviderRegistration.cs | 19 ---- .../MongoRepositoryTests.cs | 2 +- .../MongoTestEntity2Repository.cs | 14 +++ 22 files changed, 427 insertions(+), 494 deletions(-) delete mode 100644 src/AppCoreNet.Data.EntityFramework/DbContextDataProviderServices.cs delete mode 100644 src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderServices.cs create mode 100644 src/AppCoreNet.Data.MongoDB/MongoClientProvider.cs delete mode 100644 src/AppCoreNet.Data.MongoDB/MongoDataProviderServices.cs delete mode 100644 src/AppCoreNet.Data/ProviderRegistration.cs diff --git a/src/AppCoreNet.Data.EntityFramework/DbContextDataProvider.cs b/src/AppCoreNet.Data.EntityFramework/DbContextDataProvider.cs index e67d862..98e7fde 100644 --- a/src/AppCoreNet.Data.EntityFramework/DbContextDataProvider.cs +++ b/src/AppCoreNet.Data.EntityFramework/DbContextDataProvider.cs @@ -3,6 +3,7 @@ using System.Data.Entity; using System.Data.Entity.Infrastructure; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using AppCoreNet.Diagnostics; @@ -16,56 +17,73 @@ namespace AppCoreNet.Data.EntityFramework; public sealed class DbContextDataProvider : IDataProvider where TDbContext : DbContext { - private readonly string _name; - private readonly DbContextDataProviderServices _services; - /// - public string Name => _name; + public string Name { get; } /// /// Gets the of the data provider. /// /// The . - public TDbContext DbContext => _services.DbContext; + public TDbContext DbContext { get; } /// /// Gets the of the data provider. /// - public IEntityMapper EntityMapper => _services.EntityMapper; + public IEntityMapper EntityMapper { get; } /// /// Gets the of the data provider. /// - public ITokenGenerator TokenGenerator => _services.TokenGenerator; + public ITokenGenerator TokenGenerator { get; } /// /// Gets the of the data provider. /// - public DbContextQueryHandlerFactory QueryHandlerFactory => _services.QueryHandlerFactory; + public DbContextQueryHandlerFactory QueryHandlerFactory { get; } /// /// Gets the . /// - public DbContextTransactionManager TransactionManager => _services.TransactionManager; + public DbContextTransactionManager TransactionManager { get; } ITransactionManager IDataProvider.TransactionManager => TransactionManager; - internal DataProviderLogger> Logger => _services.Logger; + internal DataProviderLogger> Logger { get; } /// /// Initializes a new instance of the class. /// /// The name of the data provider. - /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . public DbContextDataProvider( string name, - DbContextDataProviderServices services) + TDbContext dbContext, + IEntityMapper entityMapper, + ITokenGenerator tokenGenerator, + DbContextQueryHandlerFactory queryHandlerFactory, + DbContextTransactionManager transactionManager, + DataProviderLogger> logger) { Ensure.Arg.NotNull(name); - Ensure.Arg.NotNull(services); + Ensure.Arg.NotNull(dbContext); + Ensure.Arg.NotNull(entityMapper); + Ensure.Arg.NotNull(tokenGenerator); + Ensure.Arg.NotNull(queryHandlerFactory); + Ensure.Arg.NotNull(transactionManager); + Ensure.Arg.NotNull(logger); - _name = name; - _services = services; + Name = name; + DbContext = dbContext; + EntityMapper = entityMapper; + TokenGenerator = tokenGenerator; + QueryHandlerFactory = queryHandlerFactory; + TransactionManager = transactionManager; + Logger = logger; } /// diff --git a/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderOptions.cs b/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderOptions.cs index db22bde..dd401e4 100644 --- a/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderOptions.cs +++ b/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderOptions.cs @@ -3,14 +3,21 @@ using System; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Data.EntityFramework; internal sealed class DbContextDataProviderOptions { - public Type? EntityMapperType { get; set; } + private static readonly Func _defaultEntityMapperFactory = + static sp => sp.GetRequiredService(); - public Type? TokenGeneratorType { get; set; } + private static readonly Func _defaultTokenGeneratorFactory = + static sp => sp.GetRequiredService(); + + public Func EntityMapperFactory { get; set; } = _defaultEntityMapperFactory; + + public Func TokenGeneratorFactory { get; set; } = _defaultTokenGeneratorFactory; public ISet QueryHandlerTypes { get; } = new HashSet(); } \ No newline at end of file diff --git a/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderServices.cs b/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderServices.cs deleted file mode 100644 index fb24faf..0000000 --- a/src/AppCoreNet.Data.EntityFramework/DbContextDataProviderServices.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using System; -using System.Data.Entity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace AppCoreNet.Data.EntityFramework; - -/// -/// Provides services for based data provider. -/// -/// The type of the . -public sealed class DbContextDataProviderServices - where TDbContext : DbContext -{ - /// - /// Gets the . - /// - public TDbContext DbContext { get; } - - /// - /// Gets the . - /// - public IEntityMapper EntityMapper { get; } - - /// - /// Gets the . - /// - public ITokenGenerator TokenGenerator { get; } - - /// - /// Gets the . - /// - public DbContextQueryHandlerFactory QueryHandlerFactory { get; } - - /// - /// Gets the . - /// - public DbContextTransactionManager TransactionManager { get; } - - /// - /// Gets the . - /// - public DataProviderLogger> Logger { get; } - - internal DbContextDataProviderServices( - TDbContext dbContext, - IEntityMapper entityMapper, - ITokenGenerator tokenGenerator, - DbContextQueryHandlerFactory queryHandlerFactory, - DbContextTransactionManager transactionManager, - DataProviderLogger> logger) - { - DbContext = dbContext; - EntityMapper = entityMapper; - TokenGenerator = tokenGenerator; - QueryHandlerFactory = queryHandlerFactory; - TransactionManager = transactionManager; - Logger = logger; - } - - private static T GetOrCreateInstance(IServiceProvider serviceProvider, Type? type) - where T : notnull - { - return type != null - ? (T)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type) - : serviceProvider.GetRequiredService(); - } - - internal static DbContextDataProviderServices Create(string name, IServiceProvider serviceProvider) - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - DbContextDataProviderOptions options = optionsMonitor.Get(name); - - var entityMapper = GetOrCreateInstance(serviceProvider, options.EntityMapperType); - var tokenGenerator = GetOrCreateInstance(serviceProvider, options.TokenGeneratorType); - var logger = serviceProvider.GetRequiredService>>(); - - var dbContext = serviceProvider.GetRequiredService(); - var queryHandlerFactory = new DbContextQueryHandlerFactory(serviceProvider, options.QueryHandlerTypes); - var transactionManager = new DbContextTransactionManager(dbContext, logger); - - return new DbContextDataProviderServices( - dbContext, - entityMapper, - tokenGenerator, - queryHandlerFactory, - transactionManager, - logger); - } -} \ No newline at end of file diff --git a/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilder.cs b/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilder.cs index 7341619..9bdfd1a 100644 --- a/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilder.cs +++ b/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilder.cs @@ -82,10 +82,8 @@ public EntityFrameworkDataProviderBuilder AddRepository(sp => { - var provider = - (DbContextDataProvider)sp.GetRequiredService() - .Resolve(Name); - + var resolver = sp.GetRequiredService(); + var provider = (DbContextDataProvider)resolver.Resolve(Name); return ActivatorUtilities.CreateInstance(sp, provider); }), ProviderLifetime)); @@ -120,7 +118,9 @@ public EntityFrameworkDataProviderBuilder AddTokenGenerator() { Services.Configure( Name, - o => o.TokenGeneratorType = typeof(T)); + o => + o.TokenGeneratorFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } @@ -135,7 +135,9 @@ public EntityFrameworkDataProviderBuilder AddEntityMapper() { Services.Configure( Name, - o => o.EntityMapperType = typeof(T)); + o => + o.EntityMapperFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } diff --git a/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilderExtensions.cs b/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilderExtensions.cs index f6226de..69493e1 100644 --- a/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilderExtensions.cs +++ b/src/AppCoreNet.Data.EntityFramework/DependencyInjection/EntityFrameworkDataProviderBuilderExtensions.cs @@ -1,11 +1,14 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. +using System; using System.Data.Entity; +using AppCoreNet.Data; using AppCoreNet.Data.EntityFramework; using AppCoreNet.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace namespace AppCoreNet.Extensions.DependencyInjection; @@ -18,9 +21,6 @@ public static class EntityFrameworkDataProviderBuilderExtensions /// /// Registers a data provider in the . /// - /// - /// Note that you have to register the on your own. - /// /// The type of the . /// The . /// The name of the data provider. @@ -35,23 +35,58 @@ public static EntityFrameworkDataProviderBuilder AddEntityFramework< Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(name); - builder.Services.AddOptions(); - builder.Services.AddLogging(); + IServiceCollection services = builder.Services; + + services.AddOptions(); + services.AddLogging(); - builder.Services.TryAdd(ServiceDescriptor.Describe(typeof(TDbContext), typeof(TDbContext), providerLifetime)); + services.TryAdd( + ServiceDescriptor.Describe( + typeof(TDbContext), + typeof(TDbContext), + providerLifetime)); + + services.TryAdd( + ServiceDescriptor.Describe( + typeof(DbContextTransactionManager), + typeof(DbContextTransactionManager), + providerLifetime)); + + services.TryAdd( + ServiceDescriptor.DescribeKeyed( + typeof(DbContextQueryHandlerFactory), + name, + static (sp, name) => + { + DbContextDataProviderOptions options = GetOptions(sp, (string)name!); + return new DbContextQueryHandlerFactory(sp, options.QueryHandlerTypes); + }, + providerLifetime)); builder.AddProvider>( name, providerLifetime, static (sp, name) => { - DbContextDataProviderServices services = - DbContextDataProviderServices.Create(name, sp); + DbContextDataProviderOptions options = GetOptions(sp, name); - return new DbContextDataProvider(name, services); + return new DbContextDataProvider( + name, + sp.GetRequiredService(), + options.EntityMapperFactory(sp), + options.TokenGeneratorFactory(sp), + sp.GetRequiredKeyedService>(name), + sp.GetRequiredService>(), + sp.GetRequiredService>>()); }); - return new EntityFrameworkDataProviderBuilder(name, builder.Services, providerLifetime); + return new EntityFrameworkDataProviderBuilder(name, services, providerLifetime); + + static DbContextDataProviderOptions GetOptions(IServiceProvider sp, string name) + { + var optionsMonitor = sp.GetRequiredService>(); + return optionsMonitor.Get(name); + } } /// diff --git a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProvider.cs b/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProvider.cs index 67acfa3..2fda9ba 100644 --- a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProvider.cs +++ b/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProvider.cs @@ -1,12 +1,11 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. -using System.Linq; using System.Threading; using System.Threading.Tasks; using AppCoreNet.Diagnostics; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.Extensions.Logging; namespace AppCoreNet.Data.EntityFrameworkCore; @@ -17,56 +16,73 @@ namespace AppCoreNet.Data.EntityFrameworkCore; public sealed class DbContextDataProvider : IDataProvider where TDbContext : DbContext { - private readonly string _name; - private readonly DbContextDataProviderServices _services; - /// - public string Name => _name; + public string Name { get; } /// /// Gets the of the data provider. /// /// The . - public TDbContext DbContext => _services.DbContext; + public TDbContext DbContext { get; } /// /// Gets the of the data provider. /// - public IEntityMapper EntityMapper => _services.EntityMapper; + public IEntityMapper EntityMapper { get; } /// /// Gets the of the data provider. /// - public ITokenGenerator TokenGenerator => _services.TokenGenerator; + public ITokenGenerator TokenGenerator { get; } /// /// Gets the of the data provider. /// - public DbContextQueryHandlerFactory QueryHandlerFactory => _services.QueryHandlerFactory; + public DbContextQueryHandlerFactory QueryHandlerFactory { get; } /// /// Gets the . /// - public DbContextTransactionManager TransactionManager => _services.TransactionManager; + public DbContextTransactionManager TransactionManager { get; } ITransactionManager IDataProvider.TransactionManager => TransactionManager; - internal DataProviderLogger> Logger => _services.Logger; + internal DataProviderLogger> Logger { get; } /// /// Initializes a new instance of the class. /// /// The name of the data provider. - /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . public DbContextDataProvider( string name, - DbContextDataProviderServices services) + TDbContext dbContext, + IEntityMapper entityMapper, + ITokenGenerator tokenGenerator, + DbContextQueryHandlerFactory queryHandlerFactory, + DbContextTransactionManager transactionManager, + DataProviderLogger> logger) { Ensure.Arg.NotNull(name); - Ensure.Arg.NotNull(services); + Ensure.Arg.NotNull(dbContext); + Ensure.Arg.NotNull(entityMapper); + Ensure.Arg.NotNull(tokenGenerator); + Ensure.Arg.NotNull(queryHandlerFactory); + Ensure.Arg.NotNull(transactionManager); + Ensure.Arg.NotNull(logger); - _name = name; - _services = services; + Name = name; + DbContext = dbContext; + EntityMapper = entityMapper; + TokenGenerator = tokenGenerator; + QueryHandlerFactory = queryHandlerFactory; + TransactionManager = transactionManager; + Logger = logger; } /// @@ -90,27 +106,6 @@ await DbContext.SaveChangesAsync(cancellationToken) throw new EntityUpdateException(error); } - // detach all entities after saving changes - #if NET462 || NETSTANDARD2_0 - CascadeTiming cascadeDeleteTiming = DbContext.ChangeTracker.CascadeDeleteTiming; - CascadeTiming deleteOrphansTiming = DbContext.ChangeTracker.DeleteOrphansTiming; - try - { - DbContext.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; - DbContext.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; - EntityEntry[] entries = DbContext.ChangeTracker.Entries().ToArray(); - foreach (EntityEntry entry in entries) - { - entry.State = EntityState.Detached; - } - } - finally - { - DbContext.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - DbContext.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - } - #else DbContext.ChangeTracker.Clear(); - #endif } -} +} \ No newline at end of file diff --git a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderOptions.cs b/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderOptions.cs index f089dc5..2f4719f 100644 --- a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderOptions.cs +++ b/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderOptions.cs @@ -3,14 +3,21 @@ using System; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Data.EntityFrameworkCore; internal sealed class DbContextDataProviderOptions { - public Type? EntityMapperType { get; set; } + private static readonly Func _defaultEntityMapperFactory = + static sp => sp.GetRequiredService(); - public Type? TokenGeneratorType { get; set; } + private static readonly Func _defaultTokenGeneratorFactory = + static sp => sp.GetRequiredService(); + + public Func EntityMapperFactory { get; set; } = _defaultEntityMapperFactory; + + public Func TokenGeneratorFactory { get; set; } = _defaultTokenGeneratorFactory; public ISet QueryHandlerTypes { get; } = new HashSet(); } \ No newline at end of file diff --git a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderServices.cs b/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderServices.cs deleted file mode 100644 index 752c2e0..0000000 --- a/src/AppCoreNet.Data.EntityFrameworkCore/DbContextDataProviderServices.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace AppCoreNet.Data.EntityFrameworkCore; - -/// -/// Provides services for based data provider. -/// -/// The type of the . -public sealed class DbContextDataProviderServices - where TDbContext : DbContext -{ - /// - /// Gets the . - /// - public TDbContext DbContext { get; } - - /// - /// Gets the . - /// - public IEntityMapper EntityMapper { get; } - - /// - /// Gets the . - /// - public ITokenGenerator TokenGenerator { get; } - - /// - /// Gets the . - /// - public DbContextQueryHandlerFactory QueryHandlerFactory { get; } - - /// - /// Gets the . - /// - public DbContextTransactionManager TransactionManager { get; } - - /// - /// Gets the . - /// - public DataProviderLogger> Logger { get; } - - internal DbContextDataProviderServices( - TDbContext dbContext, - IEntityMapper entityMapper, - ITokenGenerator tokenGenerator, - DbContextQueryHandlerFactory queryHandlerFactory, - DbContextTransactionManager transactionManager, - DataProviderLogger> logger) - { - DbContext = dbContext; - EntityMapper = entityMapper; - TokenGenerator = tokenGenerator; - QueryHandlerFactory = queryHandlerFactory; - TransactionManager = transactionManager; - Logger = logger; - } - - private static T GetOrCreateInstance(IServiceProvider serviceProvider, Type? type) - where T : notnull - { - return type != null - ? (T)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type) - : serviceProvider.GetRequiredService(); - } - - internal static DbContextDataProviderServices Create(string name, IServiceProvider serviceProvider) - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - DbContextDataProviderOptions options = optionsMonitor.Get(name); - - var entityMapper = GetOrCreateInstance(serviceProvider, options.EntityMapperType); - var tokenGenerator = GetOrCreateInstance(serviceProvider, options.TokenGeneratorType); - var logger = serviceProvider.GetRequiredService>>(); - - var dbContext = serviceProvider.GetRequiredService(); - var queryHandlerFactory = new DbContextQueryHandlerFactory(serviceProvider, options.QueryHandlerTypes); - var transactionManager = new DbContextTransactionManager(dbContext, logger); - - return new DbContextDataProviderServices( - dbContext, - entityMapper, - tokenGenerator, - queryHandlerFactory, - transactionManager, - logger); - } -} \ No newline at end of file diff --git a/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilder.cs b/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilder.cs index 957f4ba..5bf4b56 100644 --- a/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilder.cs +++ b/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilder.cs @@ -83,10 +83,8 @@ public EntityFrameworkCoreDataProviderBuilder AddRepository( sp => { - var provider = - (DbContextDataProvider)sp.GetRequiredService() - .Resolve(Name); - + var resolver = sp.GetRequiredService(); + var provider = (DbContextDataProvider)resolver.Resolve(Name); return ActivatorUtilities.CreateInstance(sp, provider); }), ProviderLifetime)); @@ -121,7 +119,9 @@ public EntityFrameworkCoreDataProviderBuilder AddTokenGenerator() { Services.Configure( Name, - o => o.TokenGeneratorType = typeof(T)); + o => + o.TokenGeneratorFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } @@ -136,7 +136,9 @@ public EntityFrameworkCoreDataProviderBuilder AddEntityMapper() { Services.Configure( Name, - o => o.EntityMapperType = typeof(T)); + o => + o.EntityMapperFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } diff --git a/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilderExtensions.cs b/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilderExtensions.cs index fff8032..5a7d007 100644 --- a/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilderExtensions.cs +++ b/src/AppCoreNet.Data.EntityFrameworkCore/DependencyInjection/EntityFrameworkCoreDataProviderBuilderExtensions.cs @@ -2,11 +2,14 @@ // Copyright (c) The AppCore .NET project. using System; +using AppCoreNet.Data; using AppCoreNet.Data.EntityFrameworkCore; using AppCoreNet.Diagnostics; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace namespace AppCoreNet.Extensions.DependencyInjection; @@ -38,21 +41,52 @@ private static EntityFrameworkCoreDataProviderBuilder AddEntityFrame Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(name); - builder.Services.AddOptions(); - builder.Services.AddLogging(); + IServiceCollection services = builder.Services; + + services.AddOptions(); + services.AddLogging(); + + services.TryAdd( + ServiceDescriptor.Describe( + typeof(DbContextTransactionManager), + typeof(DbContextTransactionManager), + providerLifetime)); + + services.TryAdd( + ServiceDescriptor.DescribeKeyed( + typeof(DbContextQueryHandlerFactory), + name, + static (sp, name) => + { + DbContextDataProviderOptions options = GetOptions(sp, (string)name!); + return new DbContextQueryHandlerFactory(sp, options.QueryHandlerTypes); + }, + providerLifetime)); builder.AddProvider>( name, providerLifetime, static (sp, name) => { - DbContextDataProviderServices services = - DbContextDataProviderServices.Create(name, sp); + DbContextDataProviderOptions options = GetOptions(sp, name); - return new DbContextDataProvider(name, services); + return new DbContextDataProvider( + name, + sp.GetRequiredService(), + options.EntityMapperFactory(sp), + options.TokenGeneratorFactory(sp), + sp.GetRequiredKeyedService>(name), + sp.GetRequiredService>(), + sp.GetRequiredService>>()); }); - return new EntityFrameworkCoreDataProviderBuilder(name, builder.Services, providerLifetime); + return new EntityFrameworkCoreDataProviderBuilder(name, services, providerLifetime); + + static DbContextDataProviderOptions GetOptions(IServiceProvider sp, string name) + { + var optionsMonitor = sp.GetRequiredService>(); + return optionsMonitor.Get(name); + } } /// diff --git a/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilder.cs b/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilder.cs index d1cb22f..79dabae 100644 --- a/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilder.cs +++ b/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilder.cs @@ -68,10 +68,8 @@ public MongoDataProviderBuilder AddRepository() ServiceDescriptor.Transient( sp => { - var provider = - (MongoDataProvider)sp.GetRequiredService() - .Resolve(Name); - + var resolver = sp.GetRequiredService(); + var provider = (MongoDataProvider)resolver.Resolve(Name); return ActivatorUtilities.CreateInstance(sp, provider); })); @@ -105,7 +103,9 @@ public MongoDataProviderBuilder AddTokenGenerator() { Services.Configure( Name, - o => o.TokenGeneratorType = typeof(T)); + o => + o.TokenGeneratorFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } @@ -120,7 +120,9 @@ public MongoDataProviderBuilder AddEntityMapper() { Services.Configure( Name, - o => o.EntityMapperType = typeof(T)); + o => + o.EntityMapperFactory = static sp => + ActivatorUtilities.GetServiceOrCreateInstance(sp)); return this; } diff --git a/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilderExtensions.cs b/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilderExtensions.cs index 94ddf6f..d427cda 100644 --- a/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilderExtensions.cs +++ b/src/AppCoreNet.Data.MongoDB/DependencyInjection/MongoDataProviderBuilderExtensions.cs @@ -2,9 +2,13 @@ // Copyright (c) The AppCore .NET project. using System; +using AppCoreNet.Data; using AppCoreNet.Data.MongoDB; using AppCoreNet.Diagnostics; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using MongoDB.Driver; // ReSharper disable once CheckNamespace namespace AppCoreNet.Extensions.DependencyInjection; @@ -29,23 +33,82 @@ public static MongoDataProviderBuilder AddMongoDb( Ensure.Arg.NotNull(builder); Ensure.Arg.NotNull(name); - builder.Services.AddOptions(); - builder.Services.AddLogging(); + IServiceCollection services = builder.Services; + + services.AddOptions(); + services.AddLogging(); if (configure != null) { - builder.Services.Configure(name, configure); + services.Configure(name, configure); } + services.TryAddKeyedSingleton( + name, + static (sp, name) => + { + MongoDataProviderOptions options = GetOptions(sp, (string)name!); + return new MongoClientProvider(options.ClientSettings); + }); + + services.TryAddKeyedSingleton( + name, + static (sp, name) => + { + var clientFactory = sp.GetRequiredKeyedService(name); + IMongoClient client = clientFactory.GetClient(); + return new MongoTransactionManager( + client, + sp.GetRequiredService>()); + }); + + services.TryAddKeyedSingleton( + name, + static (sp, name) => + { + MongoDataProviderOptions options = GetOptions(sp, (string)name!); + return new MongoQueryHandlerFactory(sp, options.QueryHandlerTypes); + }); + builder.AddProvider( name, ServiceLifetime.Singleton, static (sp, name) => { - var services = MongoDataProviderServices.Create(name, sp); - return new MongoDataProvider(name, services); + MongoDataProviderOptions options = GetOptions(sp, name); + var clientProvider = sp.GetRequiredKeyedService(name); + IMongoClient client = clientProvider.GetClient(); + IMongoDatabase database = client.GetDatabase(options.DatabaseName, options.DatabaseSettings); + + return new MongoDataProvider( + name, + database, + options.EntityMapperFactory(sp), + options.TokenGeneratorFactory(sp), + sp.GetRequiredKeyedService(name), + sp.GetRequiredKeyedService(name), + sp.GetRequiredService>()); }); - return new MongoDataProviderBuilder(name, builder.Services); + return new MongoDataProviderBuilder(name, services); + + static MongoDataProviderOptions GetOptions(IServiceProvider sp, string name) + { + var optionsMonitor = sp.GetRequiredService>(); + return optionsMonitor.Get(name); + } + } + + /// + /// Registers a default MongoDB data provider. + /// + /// The . + /// Optional delegate to configure the . + /// A to further configure the data provider. + public static MongoDataProviderBuilder AddMongoDb( + this IDataProviderBuilder builder, + Action? configure) + { + return builder.AddMongoDb(string.Empty, configure); } } \ No newline at end of file diff --git a/src/AppCoreNet.Data.MongoDB/MongoClientProvider.cs b/src/AppCoreNet.Data.MongoDB/MongoClientProvider.cs new file mode 100644 index 0000000..83be52e --- /dev/null +++ b/src/AppCoreNet.Data.MongoDB/MongoClientProvider.cs @@ -0,0 +1,48 @@ +// Licensed under the MIT license. +// Copyright (c) The AppCore .NET project. + +using System; +using MongoDB.Driver; + +namespace AppCoreNet.Data.MongoDB; + +internal sealed class MongoClientProvider : IDisposable +{ + private readonly MongoClientSettings _settings; + private IMongoClient? _client; + private bool _disposed; + + public MongoClientProvider(MongoClientSettings settings) + { + _settings = settings; + } + + public IMongoClient GetClient() + { + ThrowIfDisposed(); + + lock (this) + { + return _client ??= new MongoClient(_settings); + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _client?.Dispose(); + _disposed = true; + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().FullName); + } + } +} \ No newline at end of file diff --git a/src/AppCoreNet.Data.MongoDB/MongoDataProvider.cs b/src/AppCoreNet.Data.MongoDB/MongoDataProvider.cs index 09edff5..0e3ee84 100644 --- a/src/AppCoreNet.Data.MongoDB/MongoDataProvider.cs +++ b/src/AppCoreNet.Data.MongoDB/MongoDataProvider.cs @@ -11,58 +11,72 @@ namespace AppCoreNet.Data.MongoDB; /// public sealed class MongoDataProvider : IDataProvider { - private readonly MongoDataProviderServices _services; - private readonly string _name; - /// - public string Name => _name; - - /// - /// Gets the used by the data provider. - /// - public IMongoClient Client => _services.Client; + public string Name { get; } /// /// Gets the used by the data provider. /// - public IMongoDatabase Database => _services.Database; + public IMongoDatabase Database { get; } /// /// Gets the of the data provider. /// - public IEntityMapper EntityMapper => _services.EntityMapper; + public IEntityMapper EntityMapper { get; } /// /// Gets the of the data provider. /// - public ITokenGenerator TokenGenerator => _services.TokenGenerator; + public ITokenGenerator TokenGenerator { get; } /// /// Gets the of the data provider. /// - public MongoTransactionManager TransactionManager => _services.TransactionManager; + public MongoTransactionManager TransactionManager { get; } ITransactionManager IDataProvider.TransactionManager => TransactionManager; /// /// Gets the of the data provider. /// - public MongoQueryHandlerFactory QueryHandlerFactory => _services.QueryHandlerFactory; + public MongoQueryHandlerFactory QueryHandlerFactory { get; } - internal DataProviderLogger Logger => _services.Logger; + internal DataProviderLogger Logger { get; } /// /// Initializes a new instance of the class. /// /// The name of the data provider. - /// The . - public MongoDataProvider(string name, MongoDataProviderServices services) + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public MongoDataProvider( + string name, + IMongoDatabase database, + IEntityMapper entityMapper, + ITokenGenerator tokenGenerator, + MongoQueryHandlerFactory queryHandlerFactory, + MongoTransactionManager transactionManager, + DataProviderLogger logger) { Ensure.Arg.NotNull(name); - Ensure.Arg.NotNull(services); + Ensure.Arg.NotNull(database); + Ensure.Arg.NotNull(entityMapper); + Ensure.Arg.NotNull(tokenGenerator); + Ensure.Arg.NotNull(queryHandlerFactory); + Ensure.Arg.NotNull(transactionManager); + Ensure.Arg.NotNull(logger); - _services = services; - _name = name; + Name = name; + Database = database; + EntityMapper = entityMapper; + TokenGenerator = tokenGenerator; + QueryHandlerFactory = queryHandlerFactory; + TransactionManager = transactionManager; + Logger = logger; } internal string GetCollectionName() diff --git a/src/AppCoreNet.Data.MongoDB/MongoDataProviderOptions.cs b/src/AppCoreNet.Data.MongoDB/MongoDataProviderOptions.cs index 687e841..03409e2 100644 --- a/src/AppCoreNet.Data.MongoDB/MongoDataProviderOptions.cs +++ b/src/AppCoreNet.Data.MongoDB/MongoDataProviderOptions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; namespace AppCoreNet.Data.MongoDB; @@ -12,6 +13,12 @@ namespace AppCoreNet.Data.MongoDB; /// public sealed class MongoDataProviderOptions { + private static readonly Func _defaultEntityMapperFactory = + static sp => sp.GetRequiredService(); + + private static readonly Func _defaultTokenGeneratorFactory = + static sp => sp.GetRequiredService(); + /// /// Gets or sets the used by the data provider. /// @@ -20,16 +27,16 @@ public sealed class MongoDataProviderOptions /// /// Gets or sets the name of the database. /// - public string Database { get; set; } = "db"; + public string DatabaseName { get; set; } = "db"; /// /// Gets or sets the used by the data provider. /// public MongoDatabaseSettings? DatabaseSettings { get; set; } - internal Type? EntityMapperType { get; set; } + internal Func EntityMapperFactory { get; set; } = _defaultEntityMapperFactory; - internal Type? TokenGeneratorType { get; set; } + internal Func TokenGeneratorFactory { get; set; } = _defaultTokenGeneratorFactory; internal ISet QueryHandlerTypes { get; } = new HashSet(); } \ No newline at end of file diff --git a/src/AppCoreNet.Data.MongoDB/MongoDataProviderServices.cs b/src/AppCoreNet.Data.MongoDB/MongoDataProviderServices.cs deleted file mode 100644 index 0caa09d..0000000 --- a/src/AppCoreNet.Data.MongoDB/MongoDataProviderServices.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using MongoDB.Driver; - -namespace AppCoreNet.Data.MongoDB; - -/// -/// Provides all dependent services used by the . -/// -public sealed class MongoDataProviderServices -{ - /// - /// Gets the . - /// - public IMongoClient Client { get; } - - /// - /// Gets the . - /// - public IMongoDatabase Database { get; } - - /// - /// Gets the . - /// - public IEntityMapper EntityMapper { get; } - - /// - /// Gets the . - /// - public ITokenGenerator TokenGenerator { get; } - - /// - /// Gets the . - /// - public MongoQueryHandlerFactory QueryHandlerFactory { get; } - - /// - /// Gets the . - /// - public MongoTransactionManager TransactionManager { get; } - - /// - /// Gets the . - /// - public DataProviderLogger Logger { get; } - - internal MongoDataProviderServices( - IMongoClient client, - IMongoDatabase database, - IEntityMapper entityMapper, - ITokenGenerator tokenGenerator, - MongoQueryHandlerFactory queryHandlerFactory, - MongoTransactionManager transactionManager, - DataProviderLogger logger) - { - Client = client; - Database = database; - EntityMapper = entityMapper; - TokenGenerator = tokenGenerator; - QueryHandlerFactory = queryHandlerFactory; - TransactionManager = transactionManager; - Logger = logger; - } - - private static T GetOrCreateInstance(IServiceProvider serviceProvider, Type? type) - where T : notnull - { - return type != null - ? (T)ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type) - : serviceProvider.GetRequiredService(); - } - - internal static MongoDataProviderServices Create(string name, IServiceProvider serviceProvider) - { - var optionsMonitor = serviceProvider.GetRequiredService>(); - MongoDataProviderOptions options = optionsMonitor.Get(name); - - var entityMapper = GetOrCreateInstance(serviceProvider, options.EntityMapperType); - var tokenGenerator = GetOrCreateInstance(serviceProvider, options.TokenGeneratorType); - var logger = serviceProvider.GetRequiredService>(); - - var client = new MongoClient(options.ClientSettings); - IMongoDatabase database = client.GetDatabase(options.Database); - - var queryHandlerFactory = new MongoQueryHandlerFactory(serviceProvider, options.QueryHandlerTypes); - var transactionManager = new MongoTransactionManager(client, logger); - - return new MongoDataProviderServices( - client, - database, - entityMapper, - tokenGenerator, - queryHandlerFactory, - transactionManager, - logger); - } -} \ No newline at end of file diff --git a/src/AppCoreNet.Data.MongoDB/MongoRepository.cs b/src/AppCoreNet.Data.MongoDB/MongoRepository.cs index f106d36..ec91c9a 100644 --- a/src/AppCoreNet.Data.MongoDB/MongoRepository.cs +++ b/src/AppCoreNet.Data.MongoDB/MongoRepository.cs @@ -10,6 +10,8 @@ using AppCoreNet.Diagnostics; using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; namespace AppCoreNet.Data.MongoDB; @@ -143,10 +145,28 @@ public MongoRepository( collectionSettings); } + /// + /// Can be overridden to get the primary key from the specified entity id. + /// + /// + /// For complex keys this needs to be overridden to return a that uniquely + /// identifies the entity. + /// + /// The unique entity id. + /// The primary key values. + protected virtual BsonValue GetPrimaryKey(TId id) + { + IDiscriminatorConvention objectDiscriminatorConvention = + BsonSerializer.LookupDiscriminatorConvention(typeof(object)); + + var objectSerializer = new ObjectSerializer(objectDiscriminatorConvention, GuidRepresentation.Standard); + return objectSerializer.ToBsonValue(id); + } + private FilterDefinition GetModificationFilter(TEntity entity) { FilterDefinitionBuilder builder = Builders.Filter; - FilterDefinition filter = builder.Eq(ObjectIdField, entity.Id); + FilterDefinition filter = builder.Eq(ObjectIdField, GetPrimaryKey(entity.Id)); switch (entity) { @@ -292,7 +312,7 @@ await disposable.DisposeAsync() { IClientSessionHandle? sessionHandle = Provider.TransactionManager.CurrentTransaction?.SessionHandle; - FilterDefinition filter = Builders.Filter.Eq(ObjectIdField, id); + FilterDefinition filter = Builders.Filter.Eq(ObjectIdField, GetPrimaryKey(id)); IFindFluent find = sessionHandle != null ? Collection.Find(sessionHandle, filter) diff --git a/src/AppCoreNet.Data/DataProviderResolver.cs b/src/AppCoreNet.Data/DataProviderResolver.cs index 4270eff..b8573a1 100644 --- a/src/AppCoreNet.Data/DataProviderResolver.cs +++ b/src/AppCoreNet.Data/DataProviderResolver.cs @@ -5,37 +5,28 @@ using System.Collections.Generic; using System.Linq; using AppCoreNet.Diagnostics; -using Microsoft.Extensions.DependencyInjection; namespace AppCoreNet.Data; internal sealed class DataProviderResolver : IDataProviderResolver { - private readonly IEnumerable _registrations; - private readonly IServiceProvider _serviceProvider; + private readonly IEnumerable _providers; - public DataProviderResolver(IEnumerable registrations, IServiceProvider serviceProvider) + public DataProviderResolver(IEnumerable providers) { - _registrations = registrations; - _serviceProvider = serviceProvider; + _providers = providers; } public IDataProvider Resolve(string name) { Ensure.Arg.NotNull(name); - ProviderRegistration? registration = _registrations.FirstOrDefault(r => r.Name == name); - if (registration == null) + IDataProvider? provider = _providers.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); + if (provider == null) { - throw new InvalidOperationException($"Data provider with name '{name}' is not registered."); + throw new InvalidOperationException($"There is no data provider registered with '{name}'."); } -#if !NET8_0_OR_GREATER - return _serviceProvider.GetServices(registration.ProviderType) - .OfType() - .First(p => p.Name == name); -#else - return (IDataProvider)_serviceProvider.GetRequiredKeyedService(registration.ProviderType, name); -#endif + return provider; } } \ No newline at end of file diff --git a/src/AppCoreNet.Data/DependencyInjection/DataProviderBuilderExtensions.cs b/src/AppCoreNet.Data/DependencyInjection/DataProviderBuilderExtensions.cs index ec737da..27c67b7 100644 --- a/src/AppCoreNet.Data/DependencyInjection/DataProviderBuilderExtensions.cs +++ b/src/AppCoreNet.Data/DependencyInjection/DataProviderBuilderExtensions.cs @@ -2,7 +2,6 @@ // Copyright (c) The AppCore .NET project. using System; -using System.Collections.Generic; using System.Linq; using AppCoreNet.Data; using AppCoreNet.Diagnostics; @@ -16,17 +15,6 @@ namespace AppCoreNet.Extensions.DependencyInjection; /// public static class DataProviderBuilderExtensions { - private static ProviderRegistration? FindRegistration(IServiceCollection services, string name) - { - IEnumerable registrations = - services.Where(sd => sd.ServiceType == typeof(ProviderRegistration)); - - return (ProviderRegistration?)registrations - .FirstOrDefault( - r => ((ProviderRegistration)r.ImplementationInstance!).Name == name) - ?.ImplementationInstance; - } - /// /// Registers a data provider with a factory. /// @@ -47,45 +35,40 @@ public static IDataProviderBuilder AddProvider( Ensure.Arg.NotNull(name); Ensure.Arg.NotNull(factory); - ProviderRegistration? registration = FindRegistration(builder.Services, name); - if (registration != null) + IServiceCollection services = builder.Services; + + // check whether a data provider with the same name and different type is already registered + if (services.Any(sd => typeof(IDataProvider).IsAssignableFrom(sd.ServiceType) + && sd.IsKeyedService + && sd.ServiceType != typeof(IDataProvider) + && sd.ServiceType != typeof(T) + && (string?)sd.ServiceKey == name)) { - if (registration.ProviderType != typeof(T)) - { - throw new InvalidOperationException( - $"Data provider with name '{name}' is already registered with type '{typeof(T).GetDisplayName()}'."); - } + throw new InvalidOperationException( + $"Data provider with name '{name}' is already registered with type '{typeof(T).GetDisplayName()}'."); } - else - { - builder.Services.AddSingleton(new ProviderRegistration(name, typeof(T))); -#if !NET8_0_OR_GREATER - builder.Services.Add( - ServiceDescriptor.Describe( - typeof(T), - sp => factory(sp, name), - lifetime)); - - builder.Services.AddTransient( - sp => sp.GetRequiredService() - .Resolve(name)); -#else - builder.Services.Add( - ServiceDescriptor.DescribeKeyed( - typeof(T), - name, - (sp, key) => factory(sp, (string)key!), - lifetime)); + // check whether a data provider with the same name and type is already registered + if (services.Any(sd => sd.IsKeyedService + && sd.ServiceType == typeof(T) + && (string?)sd.ServiceKey! == name)) + { + return builder; + } - builder.Services.AddKeyedTransient( + services.Add( + ServiceDescriptor.DescribeKeyed( + typeof(T), name, - (sp, key) => sp.GetRequiredKeyedService(key)); + (sp, key) => factory(sp, (string)key!), + lifetime)); - builder.Services.AddTransient(sp => sp.GetRequiredKeyedService(name)); - builder.Services.AddTransient(sp => sp.GetRequiredKeyedService(name)); -#endif - } + services.AddKeyedTransient( + name, + (sp, key) => sp.GetRequiredKeyedService(key)); + + services.AddTransient(sp => sp.GetRequiredKeyedService(name)); + services.AddTransient(sp => sp.GetRequiredKeyedService(name)); return builder; } diff --git a/src/AppCoreNet.Data/ProviderRegistration.cs b/src/AppCoreNet.Data/ProviderRegistration.cs deleted file mode 100644 index ed095ee..0000000 --- a/src/AppCoreNet.Data/ProviderRegistration.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed under the MIT license. -// Copyright (c) The AppCore .NET project. - -using System; - -namespace AppCoreNet.Data; - -internal sealed class ProviderRegistration -{ - public string Name { get; } - - public Type ProviderType { get; } - - public ProviderRegistration(string name, Type providerType) - { - Name = name; - ProviderType = providerType; - } -} \ No newline at end of file diff --git a/test/AppCoreNet.Data.MongoDB.Tests/MongoRepositoryTests.cs b/test/AppCoreNet.Data.MongoDB.Tests/MongoRepositoryTests.cs index d34c52c..ea732a6 100644 --- a/test/AppCoreNet.Data.MongoDB.Tests/MongoRepositoryTests.cs +++ b/test/AppCoreNet.Data.MongoDB.Tests/MongoRepositoryTests.cs @@ -48,7 +48,7 @@ protected override void ConfigureServices(IServiceCollection services) o => { o.ClientSettings = MongoClientSettings.FromConnectionString(_mongoTestFixture.ConnectionString); - o.Database = DatabaseName; + o.DatabaseName = DatabaseName; }) .AddRepository() .AddQueryHandler() diff --git a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs index 94eced7..5f7e883 100644 --- a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs +++ b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs @@ -1,6 +1,11 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. +using AppCoreNet.Data.Entities; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + namespace AppCoreNet.Data.MongoDB; public class MongoTestEntity2Repository @@ -10,4 +15,13 @@ public MongoTestEntity2Repository(MongoDataProvider provider) : base(provider) { } + + protected override BsonValue GetPrimaryKey(ComplexId id) + { + return new BsonDocument() + { + { "_id", GuidSerializer.StandardInstance.ToBsonValue(id.Id) }, + { "Version", id.Version }, + }; + } } \ No newline at end of file From 15fd759bac299c86238cb3aa05127453ba3f397a Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 26 Jun 2025 17:50:48 +0200 Subject: [PATCH 2/5] Modified MongoRepository.GetPrimaryKey to handle complex types. --- src/AppCoreNet.Data.MongoDB/MongoRepository.cs | 13 +++++++++---- .../MongoTestEntity2Repository.cs | 5 +++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/AppCoreNet.Data.MongoDB/MongoRepository.cs b/src/AppCoreNet.Data.MongoDB/MongoRepository.cs index ec91c9a..501b905 100644 --- a/src/AppCoreNet.Data.MongoDB/MongoRepository.cs +++ b/src/AppCoreNet.Data.MongoDB/MongoRepository.cs @@ -156,11 +156,16 @@ public MongoRepository( /// The primary key values. protected virtual BsonValue GetPrimaryKey(TId id) { - IDiscriminatorConvention objectDiscriminatorConvention = - BsonSerializer.LookupDiscriminatorConvention(typeof(object)); + if (typeof(TId).IsPrimitive || typeof(TId) == typeof(string) || typeof(TId) == typeof(Guid)) + { + IDiscriminatorConvention objectDiscriminatorConvention = + BsonSerializer.LookupDiscriminatorConvention(typeof(object)); + + var objectSerializer = new ObjectSerializer(objectDiscriminatorConvention, GuidRepresentation.Standard); + return objectSerializer.ToBsonValue(id); + } - var objectSerializer = new ObjectSerializer(objectDiscriminatorConvention, GuidRepresentation.Standard); - return objectSerializer.ToBsonValue(id); + return id.ToBsonDocument(); } private FilterDefinition GetModificationFilter(TEntity entity) diff --git a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs index 5f7e883..64fb6c8 100644 --- a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs +++ b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs @@ -1,7 +1,6 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. -using AppCoreNet.Data.Entities; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; @@ -16,7 +15,8 @@ public MongoTestEntity2Repository(MongoDataProvider provider) { } - protected override BsonValue GetPrimaryKey(ComplexId id) + /* + protected override BsonValue GetPrimaryKey(Entities.ComplexId id) { return new BsonDocument() { @@ -24,4 +24,5 @@ protected override BsonValue GetPrimaryKey(ComplexId id) { "Version", id.Version }, }; } + */ } \ No newline at end of file From 6f821dcece4f1d28e3333b30f34532701b84c976 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 26 Jun 2025 17:56:19 +0200 Subject: [PATCH 3/5] Apply suggestions from code review. --- src/AppCoreNet.Data/DataProviderResolver.cs | 2 +- .../MongoTestEntity2Repository.cs | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/AppCoreNet.Data/DataProviderResolver.cs b/src/AppCoreNet.Data/DataProviderResolver.cs index b8573a1..e57783f 100644 --- a/src/AppCoreNet.Data/DataProviderResolver.cs +++ b/src/AppCoreNet.Data/DataProviderResolver.cs @@ -24,7 +24,7 @@ public IDataProvider Resolve(string name) IDataProvider? provider = _providers.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); if (provider == null) { - throw new InvalidOperationException($"There is no data provider registered with '{name}'."); + throw new InvalidOperationException($"Data provider with name '{name}' is not registered."); } return provider; diff --git a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs index 64fb6c8..94eced7 100644 --- a/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs +++ b/test/AppCoreNet.Data.MongoDB.Tests/MongoTestEntity2Repository.cs @@ -1,10 +1,6 @@ // Licensed under the MIT license. // Copyright (c) The AppCore .NET project. -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; - namespace AppCoreNet.Data.MongoDB; public class MongoTestEntity2Repository @@ -14,15 +10,4 @@ public MongoTestEntity2Repository(MongoDataProvider provider) : base(provider) { } - - /* - protected override BsonValue GetPrimaryKey(Entities.ComplexId id) - { - return new BsonDocument() - { - { "_id", GuidSerializer.StandardInstance.ToBsonValue(id.Id) }, - { "Version", id.Version }, - }; - } - */ } \ No newline at end of file From 86be0745112283038c207e3fc35f9ee4d60fe0fc Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 26 Jun 2025 18:02:15 +0200 Subject: [PATCH 4/5] Updated build and test workflow. --- .github/workflows/build-test.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index f23a063..575f43a 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -31,6 +31,9 @@ jobs: dotnet-version: | 8.0.x 9.0.x + - name: Set BUILD_CONFIGURATION + if: startsWith(github.ref, 'refs/tags/') + run: echo "BUILD_CONFIGURATION=Release" >> $GITHUB_ENV - name: Restore run: dotnet restore - name: Build @@ -58,6 +61,30 @@ jobs: path: ./test-results if: ${{ always() }} # Always run this step even on failure + integration-tests: + runs-on: ubuntu-latest + needs: build + name: Integration Tests + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + - name: Restore + run: dotnet restore + - name: Run Integration Tests + run: dotnet test --configuration ${{env.BUILD_CONFIGURATION}} --filter "Category=Integration" --logger trx --results-directory integration-test-results + - name: Upload integration test results + uses: actions/upload-artifact@v4 + with: + name: integration-test-results + path: ./integration-test-results + deploy-testing: if: github.event_name == 'push' runs-on: ubuntu-latest From b5db9b395170071b2066e6aa9ac41425b3e02b26 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 26 Jun 2025 18:08:48 +0200 Subject: [PATCH 5/5] Updated integration test job. --- .github/workflows/build-test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 575f43a..22419cc 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -75,6 +75,9 @@ jobs: dotnet-version: | 8.0.x 9.0.x + - name: Set BUILD_CONFIGURATION + if: startsWith(github.ref, 'refs/tags/') + run: echo "BUILD_CONFIGURATION=Release" >> $GITHUB_ENV - name: Restore run: dotnet restore - name: Run Integration Tests