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.
+