Skip to content

Commit 854d763

Browse files
authored
Added DataLoader Scoping Improvements (#6943)
1 parent 7d55af3 commit 854d763

35 files changed

+598
-340
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace GreenDonut.DependencyInjection;
4+
5+
/// <summary>
6+
/// Represents a factory that creates a DataLoader instance.
7+
/// </summary>
8+
public delegate IDataLoader DataLoaderFactory(IServiceProvider serviceProvider);
9+
10+
/// <summary>
11+
/// Represents a factory that creates a DataLoader instance.
12+
/// </summary>
13+
public delegate T DataLoaderFactory<out T>(IServiceProvider serviceProvider)
14+
where T : IDataLoader;

src/HotChocolate/Core/src/Fetching/DataLoaderRegistration.cs renamed to src/GreenDonut/src/Core/DependencyInjection/DataLoaderRegistration.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
using System;
2-
using GreenDonut;
32
using Microsoft.Extensions.DependencyInjection;
43

5-
namespace HotChocolate.Fetching;
4+
namespace GreenDonut.DependencyInjection;
65

76
/// <summary>
87
/// Represents a registration for a DataLoader.
98
/// </summary>
109
public sealed class DataLoaderRegistration
1110
{
12-
private readonly Func<IServiceProvider, object> _factory;
11+
private readonly DataLoaderFactory _factory;
1312

14-
public DataLoaderRegistration(Type instanceType) : this(instanceType, instanceType) { }
13+
public DataLoaderRegistration(Type instanceType)
14+
: this(instanceType, instanceType) { }
1515

1616
public DataLoaderRegistration(Type serviceType, Type instanceType)
1717
{
1818
ServiceType = serviceType;
1919
InstanceType = instanceType;
2020

2121
var factory = ActivatorUtilities.CreateFactory(instanceType, []);
22-
_factory = sp => factory.Invoke(sp, null);
22+
_factory = sp => (IDataLoader)factory.Invoke(sp, null);
2323
}
2424

25-
public DataLoaderRegistration(Type serviceType, Func<IServiceProvider, object> factory)
25+
public DataLoaderRegistration(Type serviceType, DataLoaderFactory factory)
2626
: this(serviceType, serviceType, factory) { }
2727

28-
public DataLoaderRegistration(Type serviceType, Type instanceType, Func<IServiceProvider, object> factory)
28+
public DataLoaderRegistration(Type serviceType, Type instanceType, DataLoaderFactory factory)
2929
{
3030
ServiceType = serviceType;
3131
InstanceType = instanceType;
@@ -52,5 +52,5 @@ public DataLoaderRegistration(Type serviceType, Type instanceType, Func<IService
5252
/// Returns the new DataLoader instance.
5353
/// </returns>
5454
public IDataLoader CreateDataLoader(IServiceProvider services)
55-
=> (IDataLoader)_factory(services);
55+
=> _factory(services);
5656
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
#if NET8_0_OR_GREATER
4+
using System.Collections.Frozen;
5+
#endif
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using GreenDonut;
9+
using GreenDonut.DependencyInjection;
10+
using Microsoft.Extensions.DependencyInjection.Extensions;
11+
using Microsoft.Extensions.ObjectPool;
12+
13+
namespace Microsoft.Extensions.DependencyInjection;
14+
15+
public static class DataLoaderServiceCollectionExtension
16+
{
17+
public static IServiceCollection AddDataLoader<T>(
18+
this IServiceCollection services)
19+
where T : class, IDataLoader
20+
{
21+
services.TryAddDataLoaderCore();
22+
services.AddSingleton(new DataLoaderRegistration(typeof(T)));
23+
services.TryAddScoped<T>(sp => sp.GetDataLoader<T>());
24+
return services;
25+
}
26+
27+
public static IServiceCollection AddDataLoader<TService, TImplementation>(
28+
this IServiceCollection services)
29+
where TService : class, IDataLoader
30+
where TImplementation : class, TService
31+
{
32+
services.TryAddDataLoaderCore();
33+
services.AddSingleton(new DataLoaderRegistration(typeof(TService), typeof(TImplementation)));
34+
services.TryAddScoped<TImplementation>(sp => sp.GetDataLoader<TImplementation>());
35+
services.TryAddScoped<TService>(sp => sp.GetDataLoader<TService>());
36+
return services;
37+
}
38+
39+
public static IServiceCollection AddDataLoader<T>(
40+
this IServiceCollection services,
41+
Func<IServiceProvider, T> factory)
42+
where T : class, IDataLoader
43+
{
44+
services.TryAddDataLoaderCore();
45+
services.AddSingleton(new DataLoaderRegistration(typeof(T), sp => factory(sp)));
46+
services.TryAddScoped<T>(sp => sp.GetDataLoader<T>());
47+
return services;
48+
}
49+
50+
public static IServiceCollection TryAddDataLoaderCore(
51+
this IServiceCollection services)
52+
{
53+
services.TryAddScoped<IDataLoaderScope, DefaultDataLoaderScope>();
54+
services.TryAddScoped<IBatchScheduler, AutoBatchScheduler>();
55+
56+
services.TryAddSingleton(sp => TaskCachePool.Create(sp.GetRequiredService<ObjectPoolProvider>()));
57+
services.TryAddScoped(sp => new TaskCacheOwner(sp.GetRequiredService<ObjectPool<TaskCache>>()));
58+
59+
services.TryAddSingleton<IDataLoaderDiagnosticEvents>(
60+
sp =>
61+
{
62+
var listeners = sp.GetServices<IDataLoaderDiagnosticEventListener>().ToArray();
63+
64+
return listeners.Length switch
65+
{
66+
0 => new DataLoaderDiagnosticEventListener(),
67+
1 => listeners[0],
68+
_ => new AggregateDataLoaderDiagnosticEventListener(listeners),
69+
};
70+
});
71+
72+
services.TryAddScoped(
73+
sp =>
74+
{
75+
var cacheOwner = sp.GetRequiredService<TaskCacheOwner>();
76+
77+
return new DataLoaderOptions
78+
{
79+
Cache = cacheOwner.Cache,
80+
CancellationToken = cacheOwner.CancellationToken,
81+
DiagnosticEvents = sp.GetService<IDataLoaderDiagnosticEvents>(),
82+
MaxBatchSize = 1024,
83+
};
84+
});
85+
86+
return services;
87+
}
88+
}
89+
90+
file static class DataLoaderServiceProviderExtensions
91+
{
92+
public static T GetDataLoader<T>(this IServiceProvider services) where T : IDataLoader
93+
=> services.GetRequiredService<IDataLoaderScope>().GetDataLoader<T>();
94+
}
95+
96+
file sealed class DefaultDataLoaderScope(
97+
IServiceProvider serviceProvider,
98+
#if NET8_0_OR_GREATER
99+
FrozenDictionary<Type, DataLoaderRegistration> registrations)
100+
#else
101+
Dictionary<Type, DataLoaderRegistration> registrations)
102+
#endif
103+
: IDataLoaderScope
104+
{
105+
private readonly ConcurrentDictionary<string, IDataLoader> _dataLoaders = new();
106+
107+
108+
public T GetDataLoader<T>(DataLoaderFactory<T> createDataLoader, string? name = null) where T : IDataLoader
109+
{
110+
name ??= CreateKey<T>();
111+
112+
if (_dataLoaders.GetOrAdd(name, _ => createDataLoader(serviceProvider)) is T dataLoader)
113+
{
114+
return dataLoader;
115+
}
116+
117+
throw new InvalidOperationException("A with the same name already exists.");
118+
}
119+
120+
public T GetDataLoader<T>() where T : IDataLoader
121+
=> (T)_dataLoaders.GetOrAdd(CreateKey<T>(), _ => CreateDataLoader<T>());
122+
123+
private T CreateDataLoader<T>() where T : IDataLoader
124+
{
125+
if (registrations.TryGetValue(typeof(T), out var registration))
126+
{
127+
return (T)registration.CreateDataLoader(serviceProvider);
128+
}
129+
130+
var adHocRegistration = new DataLoaderRegistration(typeof(T));
131+
return (T)adHocRegistration.CreateDataLoader(serviceProvider);
132+
}
133+
134+
private static string CreateKey<T>()
135+
=> typeof(T).FullName ?? typeof(T).Name;
136+
}

src/HotChocolate/Core/src/Fetching/IDataLoaderScope.cs renamed to src/GreenDonut/src/Core/DependencyInjection/IDataLoaderScope.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using GreenDonut;
3-
4-
namespace HotChocolate.Fetching;
1+
namespace GreenDonut.DependencyInjection;
52

63
/// <summary>
74
/// The DataLoader scope provides access to the DataLoader bound to the current execution.
@@ -23,7 +20,7 @@ public interface IDataLoaderScope
2320
/// <returns>
2421
/// Returns a <see cref="IDataLoader"/> instance from the current execution scope.
2522
/// </returns>
26-
T GetDataLoader<T>(Func<T> createDataLoader, string? name = null) where T : IDataLoader;
23+
T GetDataLoader<T>(DataLoaderFactory<T> createDataLoader, string? name = null) where T : IDataLoader;
2724

2825
/// <summary>
2926
/// Gets a <see cref="IDataLoader"/> from the current execution scope; or, creates a new instance for this scope.

src/GreenDonut/src/Core/GreenDonut.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@
99

1010
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
1111
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
12+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
1213
</ItemGroup>
1314

1415
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
1516
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
1618
</ItemGroup>
1719

1820
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
1921
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.0" />
22+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
2023
</ItemGroup>
2124

2225
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
2326
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="6.0.0" />
24-
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
2527
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.0" />
28+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
2629
</ItemGroup>
2730

2831
</Project>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace GreenDonut;
6+
7+
internal class AggregateDataLoaderDiagnosticEventListener(
8+
IDataLoaderDiagnosticEventListener[] listeners)
9+
: DataLoaderDiagnosticEventListener
10+
{
11+
public override void ResolvedTaskFromCache(
12+
IDataLoader dataLoader,
13+
TaskCacheKey cacheKey,
14+
Task task)
15+
{
16+
for (var i = 0; i < listeners.Length; i++)
17+
{
18+
listeners[i].ResolvedTaskFromCache(dataLoader, cacheKey, task);
19+
}
20+
}
21+
22+
public override IDisposable ExecuteBatch<TKey>(
23+
IDataLoader dataLoader,
24+
IReadOnlyList<TKey> keys)
25+
{
26+
var scopes = new IDisposable[listeners.Length];
27+
28+
for (var i = 0; i < listeners.Length; i++)
29+
{
30+
scopes[i] = listeners[i].ExecuteBatch(dataLoader, keys);
31+
}
32+
33+
return new AggregateEventScope(scopes);
34+
}
35+
36+
public override void BatchResults<TKey, TValue>(
37+
IReadOnlyList<TKey> keys,
38+
ReadOnlySpan<Result<TValue>> values)
39+
{
40+
for (var i = 0; i < listeners.Length; i++)
41+
{
42+
listeners[i].BatchResults(keys, values);
43+
}
44+
}
45+
46+
public override void BatchError<TKey>(
47+
IReadOnlyList<TKey> keys,
48+
Exception error)
49+
{
50+
for (var i = 0; i < listeners.Length; i++)
51+
{
52+
listeners[i].BatchError(keys, error);
53+
}
54+
}
55+
56+
public override void BatchItemError<TKey>(
57+
TKey key,
58+
Exception error)
59+
{
60+
for (var i = 0; i < listeners.Length; i++)
61+
{
62+
listeners[i].BatchItemError(key, error);
63+
}
64+
}
65+
66+
private sealed class AggregateEventScope(IDisposable[] scopes) : IDisposable
67+
{
68+
public void Dispose()
69+
{
70+
for (var i = 0; i < scopes.Length; i++)
71+
{
72+
scopes[i].Dispose();
73+
}
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)