diff --git a/src/GreenDonut/src/Core/DependencyInjection/DataLoaderFactory.cs b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderFactory.cs new file mode 100644 index 00000000000..aa450656a17 --- /dev/null +++ b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderFactory.cs @@ -0,0 +1,14 @@ +using System; + +namespace GreenDonut.DependencyInjection; + +/// +/// Represents a factory that creates a DataLoader instance. +/// +public delegate IDataLoader DataLoaderFactory(IServiceProvider serviceProvider); + +/// +/// Represents a factory that creates a DataLoader instance. +/// +public delegate T DataLoaderFactory(IServiceProvider serviceProvider) + where T : IDataLoader; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/DataLoaderRegistration.cs b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderRegistration.cs similarity index 72% rename from src/HotChocolate/Core/src/Fetching/DataLoaderRegistration.cs rename to src/GreenDonut/src/Core/DependencyInjection/DataLoaderRegistration.cs index 163784aea90..65bb57371ec 100644 --- a/src/HotChocolate/Core/src/Fetching/DataLoaderRegistration.cs +++ b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderRegistration.cs @@ -1,17 +1,17 @@ using System; -using GreenDonut; using Microsoft.Extensions.DependencyInjection; -namespace HotChocolate.Fetching; +namespace GreenDonut.DependencyInjection; /// /// Represents a registration for a DataLoader. /// public sealed class DataLoaderRegistration { - private readonly Func _factory; + private readonly DataLoaderFactory _factory; - public DataLoaderRegistration(Type instanceType) : this(instanceType, instanceType) { } + public DataLoaderRegistration(Type instanceType) + : this(instanceType, instanceType) { } public DataLoaderRegistration(Type serviceType, Type instanceType) { @@ -19,13 +19,13 @@ public DataLoaderRegistration(Type serviceType, Type instanceType) InstanceType = instanceType; var factory = ActivatorUtilities.CreateFactory(instanceType, []); - _factory = sp => factory.Invoke(sp, null); + _factory = sp => (IDataLoader)factory.Invoke(sp, null); } - public DataLoaderRegistration(Type serviceType, Func factory) + public DataLoaderRegistration(Type serviceType, DataLoaderFactory factory) : this(serviceType, serviceType, factory) { } - public DataLoaderRegistration(Type serviceType, Type instanceType, Func factory) + public DataLoaderRegistration(Type serviceType, Type instanceType, DataLoaderFactory factory) { ServiceType = serviceType; InstanceType = instanceType; @@ -52,5 +52,5 @@ public DataLoaderRegistration(Type serviceType, Type instanceType, Func public IDataLoader CreateDataLoader(IServiceProvider services) - => (IDataLoader)_factory(services); + => _factory(services); } \ No newline at end of file diff --git a/src/GreenDonut/src/Core/DependencyInjection/DataLoaderServiceCollectionExtension.cs b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderServiceCollectionExtension.cs new file mode 100644 index 00000000000..82fbc375f34 --- /dev/null +++ b/src/GreenDonut/src/Core/DependencyInjection/DataLoaderServiceCollectionExtension.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Concurrent; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif +using System.Collections.Generic; +using System.Linq; +using GreenDonut; +using GreenDonut.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class DataLoaderServiceCollectionExtension +{ + public static IServiceCollection AddDataLoader( + this IServiceCollection services) + where T : class, IDataLoader + { + services.TryAddDataLoaderCore(); + services.AddSingleton(new DataLoaderRegistration(typeof(T))); + services.TryAddScoped(sp => sp.GetDataLoader()); + return services; + } + + public static IServiceCollection AddDataLoader( + this IServiceCollection services) + where TService : class, IDataLoader + where TImplementation : class, TService + { + services.TryAddDataLoaderCore(); + services.AddSingleton(new DataLoaderRegistration(typeof(TService), typeof(TImplementation))); + services.TryAddScoped(sp => sp.GetDataLoader()); + services.TryAddScoped(sp => sp.GetDataLoader()); + return services; + } + + public static IServiceCollection AddDataLoader( + this IServiceCollection services, + Func factory) + where T : class, IDataLoader + { + services.TryAddDataLoaderCore(); + services.AddSingleton(new DataLoaderRegistration(typeof(T), sp => factory(sp))); + services.TryAddScoped(sp => sp.GetDataLoader()); + return services; + } + + public static IServiceCollection TryAddDataLoaderCore( + this IServiceCollection services) + { + services.TryAddScoped(); + services.TryAddScoped(); + + services.TryAddSingleton(sp => TaskCachePool.Create(sp.GetRequiredService())); + services.TryAddScoped(sp => new TaskCacheOwner(sp.GetRequiredService>())); + + services.TryAddSingleton( + sp => + { + var listeners = sp.GetServices().ToArray(); + + return listeners.Length switch + { + 0 => new DataLoaderDiagnosticEventListener(), + 1 => listeners[0], + _ => new AggregateDataLoaderDiagnosticEventListener(listeners), + }; + }); + + services.TryAddScoped( + sp => + { + var cacheOwner = sp.GetRequiredService(); + + return new DataLoaderOptions + { + Cache = cacheOwner.Cache, + CancellationToken = cacheOwner.CancellationToken, + DiagnosticEvents = sp.GetService(), + MaxBatchSize = 1024, + }; + }); + + return services; + } +} + +file static class DataLoaderServiceProviderExtensions +{ + public static T GetDataLoader(this IServiceProvider services) where T : IDataLoader + => services.GetRequiredService().GetDataLoader(); +} + +file sealed class DefaultDataLoaderScope( + IServiceProvider serviceProvider, +#if NET8_0_OR_GREATER + FrozenDictionary registrations) +#else + Dictionary registrations) +#endif + : IDataLoaderScope +{ + private readonly ConcurrentDictionary _dataLoaders = new(); + + + public T GetDataLoader(DataLoaderFactory createDataLoader, string? name = null) where T : IDataLoader + { + name ??= CreateKey(); + + if (_dataLoaders.GetOrAdd(name, _ => createDataLoader(serviceProvider)) is T dataLoader) + { + return dataLoader; + } + + throw new InvalidOperationException("A with the same name already exists."); + } + + public T GetDataLoader() where T : IDataLoader + => (T)_dataLoaders.GetOrAdd(CreateKey(), _ => CreateDataLoader()); + + private T CreateDataLoader() where T : IDataLoader + { + if (registrations.TryGetValue(typeof(T), out var registration)) + { + return (T)registration.CreateDataLoader(serviceProvider); + } + + var adHocRegistration = new DataLoaderRegistration(typeof(T)); + return (T)adHocRegistration.CreateDataLoader(serviceProvider); + } + + private static string CreateKey() + => typeof(T).FullName ?? typeof(T).Name; +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/IDataLoaderScope.cs b/src/GreenDonut/src/Core/DependencyInjection/IDataLoaderScope.cs similarity index 88% rename from src/HotChocolate/Core/src/Fetching/IDataLoaderScope.cs rename to src/GreenDonut/src/Core/DependencyInjection/IDataLoaderScope.cs index 109282dceba..e2b82bfe5e5 100644 --- a/src/HotChocolate/Core/src/Fetching/IDataLoaderScope.cs +++ b/src/GreenDonut/src/Core/DependencyInjection/IDataLoaderScope.cs @@ -1,7 +1,4 @@ -using System; -using GreenDonut; - -namespace HotChocolate.Fetching; +namespace GreenDonut.DependencyInjection; /// /// The DataLoader scope provides access to the DataLoader bound to the current execution. @@ -23,7 +20,7 @@ public interface IDataLoaderScope /// /// Returns a instance from the current execution scope. /// - T GetDataLoader(Func createDataLoader, string? name = null) where T : IDataLoader; + T GetDataLoader(DataLoaderFactory createDataLoader, string? name = null) where T : IDataLoader; /// /// Gets a from the current execution scope; or, creates a new instance for this scope. diff --git a/src/GreenDonut/src/Core/GreenDonut.csproj b/src/GreenDonut/src/Core/GreenDonut.csproj index 2efd10a7385..e0a6162f10a 100644 --- a/src/GreenDonut/src/Core/GreenDonut.csproj +++ b/src/GreenDonut/src/Core/GreenDonut.csproj @@ -9,20 +9,23 @@ + + + - + diff --git a/src/GreenDonut/src/Core/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs b/src/GreenDonut/src/Core/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs new file mode 100644 index 00000000000..06f9249d2b7 --- /dev/null +++ b/src/GreenDonut/src/Core/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace GreenDonut; + +internal class AggregateDataLoaderDiagnosticEventListener( + IDataLoaderDiagnosticEventListener[] listeners) + : DataLoaderDiagnosticEventListener +{ + public override void ResolvedTaskFromCache( + IDataLoader dataLoader, + TaskCacheKey cacheKey, + Task task) + { + for (var i = 0; i < listeners.Length; i++) + { + listeners[i].ResolvedTaskFromCache(dataLoader, cacheKey, task); + } + } + + public override IDisposable ExecuteBatch( + IDataLoader dataLoader, + IReadOnlyList keys) + { + var scopes = new IDisposable[listeners.Length]; + + for (var i = 0; i < listeners.Length; i++) + { + scopes[i] = listeners[i].ExecuteBatch(dataLoader, keys); + } + + return new AggregateEventScope(scopes); + } + + public override void BatchResults( + IReadOnlyList keys, + ReadOnlySpan> values) + { + for (var i = 0; i < listeners.Length; i++) + { + listeners[i].BatchResults(keys, values); + } + } + + public override void BatchError( + IReadOnlyList keys, + Exception error) + { + for (var i = 0; i < listeners.Length; i++) + { + listeners[i].BatchError(keys, error); + } + } + + public override void BatchItemError( + TKey key, + Exception error) + { + for (var i = 0; i < listeners.Length; i++) + { + listeners[i].BatchItemError(key, error); + } + } + + private sealed class AggregateEventScope(IDisposable[] scopes) : IDisposable + { + public void Dispose() + { + for (var i = 0; i < scopes.Length; i++) + { + scopes[i].Dispose(); + } + } + } +} diff --git a/src/GreenDonut/src/Core/DataLoaderDiagnosticEventListener.cs b/src/GreenDonut/src/Core/Instrumentation/DataLoaderDiagnosticEventListener.cs similarity index 100% rename from src/GreenDonut/src/Core/DataLoaderDiagnosticEventListener.cs rename to src/GreenDonut/src/Core/Instrumentation/DataLoaderDiagnosticEventListener.cs diff --git a/src/GreenDonut/src/Core/DefaultDataLoaderDiagnosticEventListener.cs b/src/GreenDonut/src/Core/Instrumentation/DefaultDataLoaderDiagnosticEventListener.cs similarity index 100% rename from src/GreenDonut/src/Core/DefaultDataLoaderDiagnosticEventListener.cs rename to src/GreenDonut/src/Core/Instrumentation/DefaultDataLoaderDiagnosticEventListener.cs diff --git a/src/GreenDonut/src/Core/IDataLoaderDiagnosticEventListener.cs b/src/GreenDonut/src/Core/Instrumentation/IDataLoaderDiagnosticEventListener.cs similarity index 100% rename from src/GreenDonut/src/Core/IDataLoaderDiagnosticEventListener.cs rename to src/GreenDonut/src/Core/Instrumentation/IDataLoaderDiagnosticEventListener.cs diff --git a/src/GreenDonut/src/Core/IDataLoaderDiagnosticEvents.cs b/src/GreenDonut/src/Core/Instrumentation/IDataLoaderDiagnosticEvents.cs similarity index 100% rename from src/GreenDonut/src/Core/IDataLoaderDiagnosticEvents.cs rename to src/GreenDonut/src/Core/Instrumentation/IDataLoaderDiagnosticEvents.cs diff --git a/src/GreenDonut/test/Core.Tests/DataLoader.cs b/src/GreenDonut/test/Core.Tests/DataLoader.cs index 9e9b2426ca1..b945dbe600f 100644 --- a/src/GreenDonut/test/Core.Tests/DataLoader.cs +++ b/src/GreenDonut/test/Core.Tests/DataLoader.cs @@ -7,18 +7,15 @@ namespace GreenDonut; -public class DataLoader : DataLoaderBase where TKey : notnull +public class DataLoader( + FetchDataDelegate fetch, + IBatchScheduler batchScheduler, + DataLoaderOptions? options = null) + : DataLoaderBase(batchScheduler, options) + where TKey : notnull { - private readonly FetchDataDelegate _fetch; - - public DataLoader( - FetchDataDelegate fetch, - IBatchScheduler batchScheduler, - DataLoaderOptions? options = null) - : base(batchScheduler, options) - { - _fetch = fetch ?? throw new ArgumentNullException(nameof(fetch)); - } + private readonly FetchDataDelegate _fetch = + fetch ?? throw new ArgumentNullException(nameof(fetch)); protected override ValueTask FetchAsync( IReadOnlyList keys, diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs index 6f01d82194d..2f8d21d9e97 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/InternalServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using GreenDonut; +using GreenDonut.DependencyInjection; using HotChocolate.Execution; using HotChocolate.Execution.Caching; using HotChocolate.Execution.Configuration; @@ -107,49 +108,7 @@ internal static IServiceCollection TryAddDeferredWorkStatePool( return services; } - - internal static IServiceCollection TryAddDataLoaderTaskCachePool( - this IServiceCollection services) - { - services.TryAddSingleton( - sp => TaskCachePool.Create(sp.GetRequiredService())); - services.TryAddScoped( - sp => new TaskCacheOwner(sp.GetRequiredService>())); - return services; - } - - internal static IServiceCollection TryAddDataLoaderOptions( - this IServiceCollection services) - { - services.TryAddSingleton( - sp => - { - var listeners = sp.GetServices().ToArray(); - - return listeners.Length switch - { - 0 => new DataLoaderDiagnosticEventListener(), - 1 => listeners[0], - _ => new AggregateDataLoaderDiagnosticEventListener(listeners), - }; - }); - - services.TryAddScoped( - sp => - { - var cacheOwner = sp.GetRequiredService(); - - return new DataLoaderOptions - { - Cache = cacheOwner.Cache, - CancellationToken = cacheOwner.CancellationToken, - DiagnosticEvents = sp.GetService(), - MaxBatchSize = 1024, - }; - }); - return services; - } - + internal static IServiceCollection TryAddTypeConverter( this IServiceCollection services) { @@ -206,18 +165,21 @@ internal static IServiceCollection TryAddDefaultDocumentHashProvider( internal static IServiceCollection TryAddDefaultBatchDispatcher( this IServiceCollection services) { - services.TryAddScoped(); - services.TryAddScoped(sp => sp.GetRequiredService()); - services.TryAddScoped(sp => sp.GetRequiredService()); + services.TryAddScoped(); + services.TryAddScoped(); + services.TryAddScoped(sp => sp.GetRequiredService()); return services; } internal static IServiceCollection TryAddDefaultDataLoaderRegistry( this IServiceCollection services) { + services.TryAddDataLoaderCore(); + services.RemoveAll(); services.TryAddSingleton(); + services.TryAddScoped(); services.TryAddScoped( - sp => sp.GetRequiredService().CurrentScope); + sp => sp.GetRequiredService().GetOrCreateScope(sp)); return services; } diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs index 13f1de6001a..cbec97abef9 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.DataLoader.cs @@ -1,7 +1,7 @@ using System; using GreenDonut; +using GreenDonut.DependencyInjection; using HotChocolate.Execution.Configuration; -using HotChocolate.Fetching; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.Extensions.DependencyInjection; @@ -33,7 +33,7 @@ public static IRequestExecutorBuilder AddDataLoader( Func factory) where T : class, IDataLoader { - builder.Services.AddSingleton(new DataLoaderRegistration(typeof(T), factory)); + builder.Services.AddSingleton(new DataLoaderRegistration(typeof(T), sp => factory(sp))); builder.Services.TryAddScoped(sp => sp.GetDataLoader()); return builder; } diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs index c0d30463c0a..d41798347d3 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs @@ -56,7 +56,6 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services .TryAddDefaultDataLoaderRegistry() .TryAddIdSerializer() .TryAddDataLoaderParameterExpressionBuilder() - .TryAddDataLoaderOptions() .AddSingleton(); // pools @@ -65,7 +64,6 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services .TryAddResolverTaskPool() .TryAddOperationContextPool() .TryAddDeferredWorkStatePool() - .TryAddDataLoaderTaskCachePool() .TryAddOperationCompilerPool(); // global executor services diff --git a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs b/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs deleted file mode 100644 index 3bca714d636..00000000000 --- a/src/HotChocolate/Core/src/Execution/Instrumentation/AggregateDataLoaderDiagnosticEventListener.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using GreenDonut; - -namespace HotChocolate.Execution.Instrumentation; - -internal class AggregateDataLoaderDiagnosticEventListener : DataLoaderDiagnosticEventListener -{ - private readonly IDataLoaderDiagnosticEventListener[] _listeners; - - public AggregateDataLoaderDiagnosticEventListener( - IDataLoaderDiagnosticEventListener[] listeners) - { - _listeners = listeners ?? throw new ArgumentNullException(nameof(listeners)); - } - - public override void ResolvedTaskFromCache( - IDataLoader dataLoader, - TaskCacheKey cacheKey, - Task task) - { - for (var i = 0; i < _listeners.Length; i++) - { - _listeners[i].ResolvedTaskFromCache(dataLoader, cacheKey, task); - } - } - - public override IDisposable ExecuteBatch( - IDataLoader dataLoader, - IReadOnlyList keys) - { - var scopes = new IDisposable[_listeners.Length]; - - for (var i = 0; i < _listeners.Length; i++) - { - scopes[i] = _listeners[i].ExecuteBatch(dataLoader, keys); - } - - return new AggregateEventScope(scopes); - } - - public override void BatchResults( - IReadOnlyList keys, - ReadOnlySpan> values) - { - for (var i = 0; i < _listeners.Length; i++) - { - _listeners[i].BatchResults(keys, values); - } - } - - public override void BatchError( - IReadOnlyList keys, - Exception error) - { - for (var i = 0; i < _listeners.Length; i++) - { - _listeners[i].BatchError(keys, error); - } - } - - public override void BatchItemError( - TKey key, - Exception error) - { - for (var i = 0; i < _listeners.Length; i++) - { - _listeners[i].BatchItemError(key, error); - } - } - - private sealed class AggregateEventScope : IDisposable - { - private readonly IDisposable[] _scopes; - - public AggregateEventScope(IDisposable[] scopes) - { - _scopes = scopes; - } - - public void Dispose() - { - for (var i = 0; i < _scopes.Length; i++) - { - _scopes[i].Dispose(); - } - } - } -} diff --git a/src/HotChocolate/Core/src/Execution/Processing/NoopBatchDispatcher.cs b/src/HotChocolate/Core/src/Execution/Processing/NoopBatchDispatcher.cs index 40d0e349736..c5ec2307a76 100644 --- a/src/HotChocolate/Core/src/Execution/Processing/NoopBatchDispatcher.cs +++ b/src/HotChocolate/Core/src/Execution/Processing/NoopBatchDispatcher.cs @@ -5,7 +5,7 @@ namespace HotChocolate.Execution.Processing; #pragma warning disable CS0067 -internal class NoopBatchDispatcher : IBatchDispatcher +internal sealed class NoopBatchDispatcher : IBatchDispatcher { public event EventHandler? TaskEnqueued; diff --git a/src/HotChocolate/Core/src/Fetching/Attributes/UseDataLoaderAttribute.cs b/src/HotChocolate/Core/src/Fetching/Attributes/UseDataLoaderAttribute.cs index 05334281f72..add0361a5e1 100644 --- a/src/HotChocolate/Core/src/Fetching/Attributes/UseDataLoaderAttribute.cs +++ b/src/HotChocolate/Core/src/Fetching/Attributes/UseDataLoaderAttribute.cs @@ -22,6 +22,6 @@ protected override void OnConfigure( IObjectFieldDescriptor descriptor, MemberInfo member) { - descriptor.UseDataloader(_dataLoaderType); + descriptor.UseDataLoader(_dataLoaderType); } } diff --git a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs index 0655ba085b6..f378f5b8d6e 100644 --- a/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs +++ b/src/HotChocolate/Core/src/Fetching/BatchScheduler.cs @@ -10,9 +10,7 @@ namespace HotChocolate.Fetching; /// /// The execution engine batch dispatcher. /// -public class BatchScheduler - : IBatchScheduler - , IBatchDispatcher +public sealed class BatchScheduler : IBatchHandler { private static List>? _localTasks; private static List>? _localProcessing; diff --git a/src/HotChocolate/Core/src/Fetching/DataLoaderScopeHolder.cs b/src/HotChocolate/Core/src/Fetching/DataLoaderScopeHolder.cs index 6161b378126..0cbd2f2f668 100644 --- a/src/HotChocolate/Core/src/Fetching/DataLoaderScopeHolder.cs +++ b/src/HotChocolate/Core/src/Fetching/DataLoaderScopeHolder.cs @@ -1,10 +1,14 @@ using System; #if NET8_0_OR_GREATER using System.Collections.Frozen; +#else +using System.Linq; #endif using System.Collections.Generic; -using System.Linq; using System.Threading; +using GreenDonut; +using GreenDonut.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Fetching; @@ -17,7 +21,7 @@ public sealed class DataLoaderScopeHolder #if NET8_0_OR_GREATER private readonly FrozenDictionary _registrations; #else - private readonly Dictionary _registrations; + private readonly Dictionary _registrations; #endif public DataLoaderScopeHolder(IEnumerable registrations) @@ -48,9 +52,20 @@ public DataLoaderScopeHolder(IEnumerable registrations) /// /// Creates and pins a new . /// - /// - public IDataLoaderScope PinNewScope(IServiceProvider scopedServiceProvider) - => CurrentScope = new DefaultDataLoaderScope(scopedServiceProvider, _registrations); + public IDataLoaderScope PinNewScope(IServiceProvider scopedServiceProvider, IBatchScheduler? scheduler = null) + { + scheduler ??= scopedServiceProvider.GetRequiredService(); + return CurrentScope = new ExecutionDataLoaderScope(scopedServiceProvider, scheduler, _registrations); + } + + public IDataLoaderScope GetOrCreateScope(IServiceProvider scopedServiceProvider, IBatchScheduler? scheduler = null) + { + if(_currentScope.Value?.Scope is null) + { + CurrentScope = PinNewScope(scopedServiceProvider, scheduler); + } + return CurrentScope; + } /// /// Gets access to the current instance. @@ -61,8 +76,7 @@ public IDataLoaderScope PinNewScope(IServiceProvider scopedServiceProvider) public IDataLoaderScope CurrentScope { get => _currentScope.Value?.Scope ?? - throw new InvalidCastException( - "Can only be accessed in an async context."); + throw new InvalidOperationException("No DataLoader scope exists."); set { var holder = _currentScope.Value; diff --git a/src/HotChocolate/Core/src/Fetching/DefaultDataLoaderScope.cs b/src/HotChocolate/Core/src/Fetching/DefaultDataLoaderScope.cs deleted file mode 100644 index 77d7af0535d..00000000000 --- a/src/HotChocolate/Core/src/Fetching/DefaultDataLoaderScope.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Concurrent; -#if NET8_0_OR_GREATER -using System.Collections.Frozen; -#else -using System.Collections.Generic; -#endif -using GreenDonut; -using HotChocolate.Fetching.Properties; - -namespace HotChocolate.Fetching; - -internal sealed class DefaultDataLoaderScope( - IServiceProvider serviceProvider, -#if NET8_0_OR_GREATER - FrozenDictionary registrations) -#else - Dictionary registrations) -#endif - : IDataLoaderScope -{ - private readonly ConcurrentDictionary _dataLoaders = new(); - - public T GetDataLoader(Func createDataLoader, string? name = null) where T : IDataLoader - { - name ??= CreateKey(); - - if (_dataLoaders.GetOrAdd(name, _ => createDataLoader()) is T dataLoader) - { - return dataLoader; - } - - throw new RegisterDataLoaderException( - string.Format( - FetchingResources.DefaultDataLoaderRegistry_GetOrRegister, - name, - typeof(T).FullName)); - } - - public T GetDataLoader() where T : IDataLoader - => (T)_dataLoaders.GetOrAdd(CreateKey(), _ => CreateDataLoader()); - - private T CreateDataLoader() where T : IDataLoader - { - if (!registrations.TryGetValue(typeof(T), out var registration)) - { - throw new RegisterDataLoaderException("NO DATALOADER!"); - } - - return (T)registration.CreateDataLoader(serviceProvider); - } - - private static string CreateKey() - => typeof(T).FullName ?? typeof(T).Name; -} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScope.cs b/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScope.cs new file mode 100644 index 00000000000..a4cd07f5ec0 --- /dev/null +++ b/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScope.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#else +using System.Collections.Generic; +#endif +using GreenDonut; +using GreenDonut.DependencyInjection; +using HotChocolate.Fetching.Properties; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Fetching; + +internal sealed class ExecutionDataLoaderScope( + IServiceProvider serviceProvider, + IBatchScheduler batchScheduler, +#if NET8_0_OR_GREATER + FrozenDictionary registrations) +#else + Dictionary registrations) +#endif + : IDataLoaderScope +{ + private readonly ConcurrentDictionary _dataLoaders = new(); + + private readonly IServiceProvider _serviceProvider = new DataLoaderServiceProvider(serviceProvider, batchScheduler); + + public T GetDataLoader(DataLoaderFactory createDataLoader, string? name = null) where T : IDataLoader + { + name ??= CreateKey(); + + if (_dataLoaders.GetOrAdd(name, _ => createDataLoader(_serviceProvider)) is T dataLoader) + { + return dataLoader; + } + + throw new RegisterDataLoaderException( + string.Format( + FetchingResources.DefaultDataLoaderRegistry_GetOrRegister, + name, + typeof(T).FullName)); + } + + public T GetDataLoader() where T : IDataLoader + => (T)_dataLoaders.GetOrAdd(CreateKey(), _ => CreateDataLoader()); + + private T CreateDataLoader() where T : IDataLoader + { + if (registrations.TryGetValue(typeof(T), out var registration)) + { + return (T)registration.CreateDataLoader(_serviceProvider); + } + + var adHocRegistration = new DataLoaderRegistration(typeof(T)); + return (T)adHocRegistration.CreateDataLoader(_serviceProvider); + } + + private static string CreateKey() + => typeof(T).FullName ?? typeof(T).Name; + + private class DataLoaderServiceProvider : IServiceProvider + { + private readonly IServiceProvider _innerServiceProvider; + private readonly IServiceProviderIsService? _serviceInspector; + private readonly IBatchScheduler _batchScheduler; + + public DataLoaderServiceProvider(IServiceProvider innerServiceProvider, IBatchScheduler batchScheduler) + { + _innerServiceProvider = innerServiceProvider; + _batchScheduler = batchScheduler; + var serviceInspector = innerServiceProvider.GetService(); + _serviceInspector = serviceInspector is not null + ? new CombinedServiceProviderIsService(serviceInspector) + : null; + } + + public object? GetService(Type serviceType) + { + if (serviceType is null) + { + throw new ArgumentNullException(nameof(serviceType)); + } + + if (serviceType == typeof(IServiceProviderIsService)) + { + return _serviceInspector; + } + + if(serviceType == typeof(IBatchScheduler)) + { + return _batchScheduler; + } + + return _innerServiceProvider.GetService(serviceType); + } + + private sealed class CombinedServiceProviderIsService( + IServiceProviderIsService innerIsServiceInspector) + : IServiceProviderIsService + { + public bool IsService(Type serviceType) + => typeof(IBatchDispatcher) == serviceType || + innerIsServiceInspector.IsService(serviceType); + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScopeFactory.cs b/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScopeFactory.cs new file mode 100644 index 00000000000..0f1a397b7f7 --- /dev/null +++ b/src/HotChocolate/Core/src/Fetching/ExecutionDataLoaderScopeFactory.cs @@ -0,0 +1,22 @@ +using System; +using GreenDonut; +using GreenDonut.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Fetching; + +public sealed class ExecutionDataLoaderScopeFactory(IServiceProvider services) : IDataLoaderScopeFactory +{ + public void BeginScope(IBatchScheduler? scheduler = default) + { + var batchHandler = scheduler ?? services.GetRequiredService(); + var dataLoaderScopeHolder = services.GetRequiredService(); + var dataLoaderScope = dataLoaderScopeHolder.PinNewScope(services, batchHandler); + + // the pinned scope and the scope in the DI must match ... otherwise we fail here! + if (!ReferenceEquals(dataLoaderScope, services.GetRequiredService())) + { + throw new InvalidOperationException("The DataLoaderScope has an inconsistent state."); + } + } +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs index 8c3cd23d050..167d9004681 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs @@ -2,11 +2,10 @@ using System.Threading; using System.Threading.Tasks; using GreenDonut; +using GreenDonut.DependencyInjection; using HotChocolate.Fetching; using HotChocolate.Resolvers; -using HotChocolate.Utilities; using Microsoft.Extensions.DependencyInjection; -using static HotChocolate.Fetching.Properties.FetchingResources; // ReSharper disable once CheckNamespace namespace HotChocolate.Types; @@ -77,12 +76,12 @@ public static IDataLoader BatchDataLoader( var scope = services.GetRequiredService(); return scope.GetDataLoader(Create, name); - IDataLoader Create() + IDataLoader Create(IServiceProvider sp) => new AdHocBatchDataLoader( name ?? "default", fetch, - services.GetRequiredService(), - services.GetRequiredService()); + sp.GetRequiredService(), + sp.GetRequiredService()); } /// @@ -149,12 +148,12 @@ public static IDataLoader GroupDataLoader( var scope = services.GetRequiredService(); return scope.GetDataLoader(Create, name); - IDataLoader Create() + IDataLoader Create(IServiceProvider sp) => new AdHocGroupedDataLoader( name ?? "default", fetch, - services.GetRequiredService(), - services.GetRequiredService()); + sp.GetRequiredService(), + sp.GetRequiredService()); } /// @@ -206,7 +205,7 @@ public static IDataLoader CacheDataLoader( var scope = services.GetRequiredService(); return scope.GetDataLoader(Create, name); - IDataLoader Create() + IDataLoader Create(IServiceProvider sp) => new AdHocCacheDataLoader( name ?? "default", fetch, @@ -249,35 +248,6 @@ public static T DataLoader(this IResolverContext context) var services = context.RequestServices; var reg = services.GetRequiredService(); - return reg.GetDataLoader(() => CreateDataLoader(services)); - } - - private static T CreateDataLoader(IServiceProvider services) - where T : IDataLoader - { - var registeredDataLoader = services.GetService(); - - if (registeredDataLoader is not null) - { - return registeredDataLoader; - } - - if (typeof(T).IsInterface || typeof(T).IsAbstract) - { - throw new RegisterDataLoaderException( - string.Format( - DataLoaderResolverContextExtensions_CreateDataLoader_AbstractType, - typeof(T).FullName ?? typeof(T).Name)); - } - - if (ServiceFactory.CreateInstance(services, typeof(T)) is T dataLoader) - { - return dataLoader; - } - - throw new RegisterDataLoaderException( - string.Format( - DataLoaderResolverContextExtensions_CreateDataLoader_UnableToCreate, - typeof(T).FullName ?? typeof(T).Name)); + return reg.GetDataLoader(); } } \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderServiceProviderExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderServiceProviderExtensions.cs index 4db9515a3ba..e97e873591b 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderServiceProviderExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderServiceProviderExtensions.cs @@ -7,12 +7,8 @@ internal static class DataLoaderServiceProviderExtensions { public static void InitializeDataLoaderScope(this IServiceProvider services) { - var dataLoaderScope = services.GetRequiredService().PinNewScope(services); - - // the pinned scope and the scope in the DI must match ... otherwise we fail here! - if (!ReferenceEquals(dataLoaderScope, services.GetRequiredService())) - { - throw new InvalidOperationException("The DataLoaderScope has an inconsistent state."); - } + var batchHandler = services.GetRequiredService(); + var dataLoaderScopeHolder = services.GetRequiredService(); + dataLoaderScopeHolder.BeginScope(batchHandler); } } \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs index bfd2f2f0dd4..6b90a80b81c 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/ObjectFieldDataLoaderExtensions.cs @@ -17,12 +17,12 @@ namespace HotChocolate.Types; public static class DataLoaderObjectFieldExtensions { - public static IObjectFieldDescriptor UseDataloader( + public static IObjectFieldDescriptor UseDataLoader( this IObjectFieldDescriptor descriptor) where TDataLoader : IDataLoader - => UseDataloader(descriptor, typeof(TDataLoader)); + => UseDataLoader(descriptor, typeof(TDataLoader)); - public static IObjectFieldDescriptor UseDataloader( + public static IObjectFieldDescriptor UseDataLoader( this IObjectFieldDescriptor descriptor, Type dataLoaderType) { @@ -124,26 +124,19 @@ private static bool TryGetDataLoaderTypes( return false; } - private sealed class GroupedDataLoaderMiddleware + private sealed class GroupedDataLoaderMiddleware(FieldDelegate next) where TKey : notnull where TDataLoader : IDataLoader { - private readonly FieldDelegate _next; - - public GroupedDataLoaderMiddleware(FieldDelegate next) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - } - public async Task InvokeAsync(IMiddlewareContext context) { - var dataloader = context.DataLoader(); + var dataLoader = context.DataLoader(); - await _next(context).ConfigureAwait(false); + await next(context).ConfigureAwait(false); if (context.Result is IReadOnlyCollection values) { - var data = await dataloader + var data = await dataLoader .LoadAsync(values, context.RequestAborted) .ConfigureAwait(false); @@ -160,39 +153,34 @@ public async Task InvokeAsync(IMiddlewareContext context) } else if (context.Result is TKey value) { - context.Result = await dataloader + context.Result = await dataLoader .LoadAsync(value, context.RequestAborted) .ConfigureAwait(false); } } } - private sealed class DataLoaderMiddleware + private sealed class DataLoaderMiddleware(FieldDelegate next) where TKey : notnull where TDataLoader : IDataLoader { - private readonly FieldDelegate _next; - - public DataLoaderMiddleware(FieldDelegate next) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - } + private readonly FieldDelegate _next = next ?? throw new ArgumentNullException(nameof(next)); public async Task InvokeAsync(IMiddlewareContext context) { - var dataloader = context.DataLoader(); + var dataLoader = context.DataLoader(); await _next(context).ConfigureAwait(false); if (context.Result is IReadOnlyCollection values) { - context.Result = await dataloader + context.Result = await dataLoader .LoadAsync(values, context.RequestAborted) .ConfigureAwait(false); } else if (context.Result is TKey value) { - context.Result = await dataloader + context.Result = await dataLoader .LoadAsync(value, context.RequestAborted) .ConfigureAwait(false); } diff --git a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs index 9352551cd4b..653208ca6bc 100644 --- a/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs +++ b/src/HotChocolate/Core/src/Fetching/IBatchDispatcher.cs @@ -22,4 +22,4 @@ public interface IBatchDispatcher /// Begins dispatching batched tasks. /// void BeginDispatch(CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/IBatchHandler.cs b/src/HotChocolate/Core/src/Fetching/IBatchHandler.cs new file mode 100644 index 00000000000..1b4a407dae8 --- /dev/null +++ b/src/HotChocolate/Core/src/Fetching/IBatchHandler.cs @@ -0,0 +1,8 @@ +using GreenDonut; + +namespace HotChocolate.Fetching; + +/// +/// The execution engine batch scheduler and dispatcher. +/// +public interface IBatchHandler : IBatchDispatcher, IBatchScheduler; \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/IDataLoaderScopeFactory.cs b/src/HotChocolate/Core/src/Fetching/IDataLoaderScopeFactory.cs new file mode 100644 index 00000000000..f871325531e --- /dev/null +++ b/src/HotChocolate/Core/src/Fetching/IDataLoaderScopeFactory.cs @@ -0,0 +1,8 @@ +using GreenDonut; + +namespace HotChocolate.Fetching; + +public interface IDataLoaderScopeFactory +{ + void BeginScope(IBatchScheduler? scheduler = default); +} \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Fetching/RegisterDataLoaderException.cs b/src/HotChocolate/Core/src/Fetching/RegisterDataLoaderException.cs index 0205d5c3d9e..690c3f40c26 100644 --- a/src/HotChocolate/Core/src/Fetching/RegisterDataLoaderException.cs +++ b/src/HotChocolate/Core/src/Fetching/RegisterDataLoaderException.cs @@ -1,10 +1,3 @@ -#nullable enable - namespace HotChocolate.Fetching; -public class RegisterDataLoaderException : GraphQLException -{ - public RegisterDataLoaderException(string message) : base(message) - { - } -} +public class RegisterDataLoaderException(string message) : GraphQLException(message); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs index 976478ed7b1..a71778cb2ad 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/RequestMiddlewareSyntaxGenerator.cs @@ -44,6 +44,7 @@ public void WriteEndNamespace() _writer.DecreaseIndent(); _writer.WriteIndentedLine("}"); _writer.WriteLine(); + _writer.Write(Properties.SourceGenResources.InterceptsAttribute); } public string WriteBeginClass() diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs index c87848e58cd..2bd644d46c3 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs @@ -56,5 +56,11 @@ internal static string DataLoader_InvalidAccessModifier { return ResourceManager.GetString("DataLoader_InvalidAccessModifier", resourceCulture); } } + + internal static string InterceptsAttribute { + get { + return ResourceManager.GetString("InterceptsAttribute", resourceCulture); + } + } } } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx index 7b40cbbfb04..11067097b9e 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx +++ b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx @@ -24,4 +24,13 @@ The DataLoader access modifier must be public or internal. + + #pragma warning disable CS9113 // Parameter is unread. +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute; +} +#pragma warning restore CS9113 // Parameter is unread. + diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs index a180b607bcd..22cfa3722c7 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using CookieCrumble; using GreenDonut; +using GreenDonut.DependencyInjection; using HotChocolate.Fetching; using HotChocolate.Resolvers; using HotChocolate.Types; @@ -181,10 +182,10 @@ public async Task ClassDataLoader() var dataLoader = context.Services .GetRequiredService() - .GetDataLoader(() => throw new Exception()); + .GetDataLoader(_ => throw new Exception()); context.Result = QueryResultBuilder - .FromResult(((IQueryResult)context.Result!)) + .FromResult((IQueryResult)context.Result!) .AddExtension("loads", dataLoader.Loads) .Create(); }) @@ -224,6 +225,114 @@ await executor.ExecuteAsync( // assert snapshot.MatchMarkdown(); } + + [Fact] + public async Task ClassDataLoader_Out_Off_GraphQL_Context_Not_Initialized() + { + // arrange + var services = new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddDataLoader() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .UseRequest( + next => async context => + { + await next(context); + + var dataLoader = + context.Services + .GetRequiredService() + .GetDataLoader(_ => throw new Exception()); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result!) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline() + .Services + .BuildServiceProvider(); + + // act + using var serviceScope = services.CreateScope(); + var dataLoader = serviceScope.ServiceProvider.GetRequiredService(); + var result = await dataLoader.LoadAsync("a"); + Assert.Equal("a", result); + } + + [Fact] + public async Task ClassDataLoader_Out_Off_GraphQL_Context() + { + // arrange + var services = new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddDataLoader() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .UseRequest( + next => async context => + { + await next(context); + + var dataLoader = + context.Services + .GetRequiredService() + .GetDataLoader(_ => throw new Exception()); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result!) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline() + .Services + .BuildServiceProvider(); + + // act + using var serviceScope = services.CreateScope(); + var dataLoaderScopeFactory = serviceScope.ServiceProvider.GetRequiredService(); + dataLoaderScopeFactory.BeginScope(); + + var dataLoader = serviceScope.ServiceProvider.GetRequiredService(); + var result = await dataLoader.LoadAsync("a"); + Assert.Equal("a", result); + } + + [Fact] + public async Task ClassDataLoader_Out_Off_GraphQL_Context_Just_Works() + { + // arrange + var services = new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddDataLoader() + .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) + .UseRequest( + next => async context => + { + await next(context); + + var dataLoader = + context.Services + .GetRequiredService() + .GetDataLoader(_ => throw new Exception()); + + context.Result = QueryResultBuilder + .FromResult((IQueryResult)context.Result!) + .AddExtension("loads", dataLoader.Loads) + .Create(); + }) + .UseDefaultPipeline() + .Services + .BuildServiceProvider(); + + // act + using var serviceScope = services.CreateScope(); + var dataLoader = serviceScope.ServiceProvider.GetRequiredService(); + var result = await dataLoader.LoadAsync("a"); + Assert.Equal("a", result); + } [LocalFact] public async Task StackedDataLoader() @@ -286,8 +395,7 @@ public async Task ClassDataLoader_Resolve_From_DependencyInjection() { await next(context); - var dataLoader = - (TestDataLoader)context.Services.GetRequiredService(); + var dataLoader = (TestDataLoader)context.Services.GetRequiredService(); context.Result = QueryResultBuilder .FromResult(((IQueryResult)context.Result!)) @@ -374,8 +482,8 @@ await executor.ExecuteAsync( [Fact] public async Task DataLoader_Request_Ensures_That_There_Is_A_Single_Instance() { - //using var cts = new CancellationTokenSource(5000); - //var ct = cts.Token; + using var cts = new CancellationTokenSource(5000); + var ct = cts.Token; var snapshot = new Snapshot(); var executor = @@ -385,8 +493,7 @@ public async Task DataLoader_Request_Ensures_That_There_Is_A_Single_Instance() .AddQueryType() .RegisterService(ServiceKind.Resolver) .AddDataLoader() - .BuildRequestExecutorAsync(); - //.BuildRequestExecutorAsync(cancellationToken: ct); + .BuildRequestExecutorAsync(cancellationToken: ct); snapshot.Add( await executor.ExecuteAsync( @@ -398,9 +505,8 @@ await executor.ExecuteAsync( d: do e: do } - """)); - //, - //cancellationToken: ct)); + """, + cancellationToken: ct)); snapshot.MatchMarkdown(); } diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/UseDataLoaderTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/UseDataLoaderTests.cs index 56e2f41389a..b498355e048 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/UseDataLoaderTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/UseDataLoaderTests.cs @@ -17,7 +17,7 @@ public void UseDataLoader_Should_ThrowException_When_NotADataLoader() .AddQueryType(x => x .BindFieldsExplicitly() .Field(y => y.Single) - .UseDataloader(typeof(Foo))) + .UseDataLoader(typeof(Foo))) .Create()); // assert @@ -33,7 +33,7 @@ public void UseDataLoader_Schema_BatchDataloader_Single() .AddQueryType(x => x .BindFieldsExplicitly() .Field(y => y.Single) - .UseDataloader()) + .UseDataLoader()) .Create(); // assert @@ -49,7 +49,7 @@ public void UseDataLoader_Schema_BatchDataloader_Many() .AddQueryType(x => x .BindFieldsExplicitly() .Field(y => y.Multiple) - .UseDataloader()) + .UseDataLoader()) .Create(); // assert @@ -65,7 +65,7 @@ public void UseDataLoader_Schema_GroupedDataloader_Single() .AddQueryType(x => x .BindFieldsExplicitly() .Field(y => y.Single) - .UseDataloader()) + .UseDataLoader()) .Create(); // assert @@ -81,7 +81,7 @@ public void UseDataLoader_Schema_GroupedDataloader_Many() .AddQueryType(x => x .BindFieldsExplicitly() .Field(y => y.Multiple) - .UseDataloader()) + .UseDataLoader()) .Create(); // assert @@ -156,7 +156,7 @@ public async Task UseDataLoader_Schema_BatchDataloader_Single_Execute() .AddQueryType( x => x.BindFieldsExplicitly() .Field(y => y.Single) - .UseDataloader()) + .UseDataLoader()) .Create() .MakeExecutable(); @@ -176,7 +176,7 @@ public async Task UseDataLoader_Schema_BatchDataloader_Multiple_Execute() .AddQueryType( x => x.BindFieldsExplicitly() .Field(y => y.Multiple) - .UseDataloader()) + .UseDataLoader()) .Create() .MakeExecutable(); @@ -196,7 +196,7 @@ public async Task UseDataLoader_Schema_GroupedDataloader_Single_Execute() .AddQueryType( x => x.BindFieldsExplicitly() .Field(y => y.Single) - .UseDataloader()) + .UseDataLoader()) .Create() .MakeExecutable(); @@ -216,7 +216,7 @@ public async Task UseDataLoader_Schema_GroupedDataloader_Multiple_Execute() .AddQueryType( x => x.BindFieldsExplicitly() .Field(y => y.Multiple) - .UseDataloader()) + .UseDataLoader()) .Create() .MakeExecutable(); diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/InterceptsLocationAttribute.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/InterceptsLocationAttribute.cs index c44aa3717d2..8b137891791 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/InterceptsLocationAttribute.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/InterceptsLocationAttribute.cs @@ -1,7 +1 @@ -#pragma warning disable CS9113 // Parameter is unread. -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - sealed class InterceptsLocationAttribute(string filePath, int line, int column) : Attribute; -} -#pragma warning restore CS9113 // Parameter is unread. +