Skip to content

Commit

Permalink
Aligned DataLoader options behavior. (#8044)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Feb 19, 2025
1 parent 8006931 commit 95b6ca7
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 28 deletions.
17 changes: 12 additions & 5 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@
}
},
{
"label": "Build src/All.sln",
"type": "dotnet",
"task": "build",
"group": "build",
"problemMatcher": [],
"label": "dotnet: build"
},
{
"label": "Build src/All.slnx",
"command": "dotnet",
"type": "shell",
"args": [
"build",
"src/All.sln",
"src/All.slnx",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
Expand All @@ -49,12 +56,12 @@
"problemMatcher": "$msCompile"
},
{
"label": "Test src/All.sln",
"label": "Test src/All.slnx",
"command": "dotnet",
"type": "shell",
"args": [
"test",
"src/All.sln",
"src/All.slnx",
"--verbosity q",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
Expand All @@ -64,6 +71,6 @@
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},
}
]
}
4 changes: 0 additions & 4 deletions src/GreenDonut/src/GreenDonut/BatchDataLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ protected BatchDataLoader(
DataLoaderOptions options)
: base(batchScheduler, options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
}

/// <inheritdoc />
Expand Down
42 changes: 42 additions & 0 deletions src/GreenDonut/src/GreenDonut/BranchedDataLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace GreenDonut;

/// <summary>
/// This class represents a branched <see cref="IDataLoader{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TKey">
/// The type of the key.
/// </typeparam>
/// <typeparam name="TValue">
/// The type of the value.
/// </typeparam>
public class BranchedDataLoader<TKey, TValue>
: DataLoaderBase<TKey, TValue>
where TKey : notnull
{
private readonly DataLoaderBase<TKey, TValue> _root;

public BranchedDataLoader(
DataLoaderBase<TKey, TValue> root,
string key)
: base(root.BatchScheduler, root.Options)
{
_root = root;
CacheKeyType = $"{root.CacheKeyType}:{key}";
ContextData = root.ContextData;
}

public IDataLoader<TKey, TValue> Root => _root;

protected internal override string CacheKeyType { get; }

protected sealed override bool AllowCachePropagation => false;

protected override bool AllowBranching => true;

protected internal override ValueTask FetchAsync(
IReadOnlyList<TKey> keys,
Memory<Result<TValue?>> results,
DataLoaderFetchContext<TValue> context,
CancellationToken cancellationToken)
=> _root.FetchAsync(keys, results, context, cancellationToken);
}
8 changes: 5 additions & 3 deletions src/GreenDonut/src/GreenDonut/DataLoaderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ public abstract partial class DataLoaderBase<TKey, TValue>
/// <exception cref="ArgumentNullException">
/// Throws if <paramref name="options"/> is <c>null</c>.
/// </exception>
protected DataLoaderBase(IBatchScheduler batchScheduler, DataLoaderOptions? options = null)
protected DataLoaderBase(IBatchScheduler batchScheduler, DataLoaderOptions options)
{
options ??= new DataLoaderOptions();
ArgumentNullException.ThrowIfNull(batchScheduler);
ArgumentNullException.ThrowIfNull(options);

_diagnosticEvents = options.DiagnosticEvents ?? Default;
Cache = options.Cache;
_batchScheduler = batchScheduler;
Expand Down Expand Up @@ -226,7 +228,7 @@ void Initialize()
ct);
}
}

/// <inheritdoc />
public void SetCacheEntry(TKey key, Task<TValue?> value)
{
Expand Down
4 changes: 2 additions & 2 deletions src/GreenDonut/src/GreenDonut/GroupedDataLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public abstract class GroupedDataLoader<TKey, TValue>
/// </exception>
protected GroupedDataLoader(
IBatchScheduler batchScheduler,
DataLoaderOptions? options = null)
DataLoaderOptions options)
: base(batchScheduler, options)
{ }

Expand Down Expand Up @@ -92,7 +92,7 @@ public abstract class StatefulGroupedDataLoader<TKey, TValue>
/// </exception>
protected StatefulGroupedDataLoader(
IBatchScheduler batchScheduler,
DataLoaderOptions? options = null)
DataLoaderOptions options)
: base(batchScheduler, options)
{ }

Expand Down
2 changes: 1 addition & 1 deletion src/GreenDonut/test/GreenDonut.Tests/DataLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace GreenDonut;
public class DataLoader<TKey, TValue>(
FetchDataDelegate<TKey, TValue> fetch,
IBatchScheduler batchScheduler,
DataLoaderOptions? options = null)
DataLoaderOptions options)
: DataLoaderBase<TKey, TValue>(batchScheduler, options)
where TKey : notnull
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// ReSharper disable InconsistentNaming

using System.Reflection.Metadata;

namespace GreenDonut;

public class DataLoaderExtensionsTests
Expand All @@ -24,7 +26,7 @@ public void SetCacheEntryKeyNull()
// arrange
var fetch = TestHelpers.CreateFetch<string, string>();
var batchScheduler = new ManualBatchScheduler();
var loader = new DataLoader<string, string>(fetch, batchScheduler);
var loader = new DataLoader<string, string>(fetch, batchScheduler, new DataLoaderOptions());
var value = "Bar";

// act
Expand All @@ -40,7 +42,7 @@ public void SetCacheEntryNoException()
// arrange
var fetch = TestHelpers.CreateFetch<string, string>();
var batchScheduler = new ManualBatchScheduler();
var loader = new DataLoader<string, string>(fetch, batchScheduler);
var loader = new DataLoader<string, string>(fetch, batchScheduler, new DataLoaderOptions());
var key = "Foo";

// act
Expand Down Expand Up @@ -149,7 +151,7 @@ public void IDataLoaderSetCacheEntryNoException()
// arrange
var fetch = TestHelpers.CreateFetch<string, string>();
var batchScheduler = new ManualBatchScheduler();
IDataLoader loader = new DataLoader<string, string>(fetch, batchScheduler);
IDataLoader loader = new DataLoader<string, string>(fetch, batchScheduler, new DataLoaderOptions());
object key = "Foo";

// act
Expand Down
14 changes: 7 additions & 7 deletions src/GreenDonut/test/GreenDonut.Tests/DataLoaderStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public static class DataLoaderStateTests
public static async Task SetStateInferredKey()
{
// arrange
var loader = new DummyDataLoader(typeof(string).FullName!);
var loader = new DummyDataLoader(typeof(string).FullName!, new DataLoaderOptions());

// act
await loader.SetState("abc").LoadAsync("def");
Expand All @@ -19,7 +19,7 @@ public static async Task SetStateInferredKey()
public static async Task SetStateExplicitKey()
{
// arrange
var loader = new DummyDataLoader("abc");
var loader = new DummyDataLoader("abc", new DataLoaderOptions());

// act
await loader.SetState("abc", "def").LoadAsync("ghi");
Expand All @@ -32,7 +32,7 @@ public static async Task SetStateExplicitKey()
public static async Task TrySetStateInferredKey()
{
// arrange
var loader = new DummyDataLoader(typeof(string).FullName!);
var loader = new DummyDataLoader(typeof(string).FullName!, new DataLoaderOptions());

// act
await loader.SetState("abc").TrySetState("xyz").LoadAsync("def");
Expand All @@ -45,7 +45,7 @@ public static async Task TrySetStateInferredKey()
public static async Task TrySetStateExplicitKey()
{
// arrange
var loader = new DummyDataLoader("abc");
var loader = new DummyDataLoader("abc", new DataLoaderOptions());

// act
await loader.SetState("abc", "def").TrySetState("abc", "xyz").LoadAsync("def");
Expand All @@ -58,7 +58,7 @@ public static async Task TrySetStateExplicitKey()
public static async Task AddStateEnumerableInferredKey()
{
// arrange
var loader = new DummyDataLoader(typeof(string).FullName!);
var loader = new DummyDataLoader(typeof(string).FullName!, new DataLoaderOptions());

// act
await loader.AddStateEnumerable("abc").AddStateEnumerable("xyz").LoadAsync("def");
Expand All @@ -74,7 +74,7 @@ public static async Task AddStateEnumerableInferredKey()
public static async Task AddStateEnumerableExplicitKey()
{
// arrange
var loader = new DummyDataLoader("abc");
var loader = new DummyDataLoader("abc", new DataLoaderOptions());

// act
await loader.AddStateEnumerable("abc", "def").AddStateEnumerable("abc", "xyz").LoadAsync("def");
Expand All @@ -86,7 +86,7 @@ public static async Task AddStateEnumerableExplicitKey()
item => Assert.Equal("xyz", item));
}

public class DummyDataLoader(string expectedKey, DataLoaderOptions? options = null)
public class DummyDataLoader(string expectedKey, DataLoaderOptions options)
: DataLoaderBase<string, string>(AutoBatchScheduler.Default, options)
{
public object? State { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ public void ImplFactoryIsCalledWhenServiceIsResolved()
.AddDataLoader(sp =>
{
factoryCalled = true;
return new DataLoader<string, string>(fetch, sp.GetRequiredService<IBatchScheduler>());
return new DataLoader<string, string>(
fetch,
sp.GetRequiredService<IBatchScheduler>(),
sp.GetRequiredService<DataLoaderOptions>());
});
var scope = services.BuildServiceProvider().CreateScope();

Expand All @@ -40,7 +43,10 @@ public void InterfaceImplFactoryIsCalledWhenServiceIsResolved()
.AddDataLoader<IDataLoader<string, string>, DataLoader<string, string>>(sp =>
{
factoryCalled = true;
return new DataLoader<string, string>(fetch, sp.GetRequiredService<IBatchScheduler>());
return new DataLoader<string, string>(
fetch,
sp.GetRequiredService<IBatchScheduler>(),
sp.GetRequiredService<DataLoaderOptions>());
});
var scope = services.BuildServiceProvider().CreateScope();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public class TestGroupedLoader : GroupedDataLoader<int, Foo>
{
public TestGroupedLoader(
IBatchScheduler batchScheduler,
DataLoaderOptions? options = null)
DataLoaderOptions options)
: base(batchScheduler, options)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,74 @@ Please ensure that your clients are sending date/time strings in the correct for
- `DateOnly` is now bound to `LocalDateType` instead of `DateType`.
- `TimeOnly` is now bound to `LocalTimeType` instead of `TimeSpanType`.

## DataLoaderOptions are now required

Starting with Hot Chocolate 15, the `DataLoaderOptions` must be passed down to the DataLoaderBase constructor.

```csharp
public class ProductByIdDataLoader : BatchDataLoader<int, Product>
{
private readonly IServiceProvider _services;

public ProductDataLoader1(
IBatchScheduler batchScheduler,
DataLoaderOptions options) // the options are now required ...
: base(batchScheduler, options)
{
}
}
```

## DataLoader Dependency Injection

DataLoader must not be manually registered with the dependency injection and must use the extension methods provided by GreenDonut.

```csharp
services.AddDataLoader<ProductByIdDataLoader>();
services.AddDataLoader<IProductByIdDataLoader, ProductByIdDataLoader>();
services.AddDataLoader<IProductByIdDataLoader>(sp => ....);
```

We recommend to use the source-generated DataLoaders and let the source generator write the registration code for you.

> If you register DataLoader manually they will be stuck in the auto-dispatch mode, which basically means that they will no longer batch.
DataLoader are available as scoped services and can be injected like any other scoped service.

```csharp
public class ProductService(IProductByIdDataLoader productByIdData)
{
public async Task<Product> GetProductById(int id)
{
return await productByIdDataLoader.LoadAsync(id);
}
}
```

# Deprecations

## GroupDataLoader

We no longer recommend using the `GroupDataLoader`, as the same functionality can be achieved with a BatchDataLoader, which provides greater flexibility in determining the type of list returned.

Use the following patter to replace the `GroupDataLoader`:

```csharp
internal static class ProductDataLoader
{
[DataLoader]
public static async Task<Dictionary<int, Product[]>> GetProductsByBrandIdAsync(
IReadOnlyList<int> brandIds,
CatalogContext context,
CancellationToken cancellationToken)
=> await context.Products
.Where(t => brandIds.Contains(t.BrandId))
.GroupBy(t => t.BrandId)
.Select(t => new { t.Key, Items = t.OrderBy(p => p.Name).ToArray() })
.ToDictionaryAsync(t => t.Key, t => t.Items, cancellationToken);
}
```

## AdHoc DataLoader

The ad-hoc DataLoader methods on IResolverContext have been deprecated.
Expand Down

0 comments on commit 95b6ca7

Please sign in to comment.