From 7e2e449b49a470502820226125482050a62a74fc Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 13 Jan 2023 09:47:22 +0100 Subject: [PATCH] Added DataLoader source generator. (#5681) --- .../test/Core.Tests/BatchDataLoaderTests.cs | 8 +- .../CancellationTokenSourceExtensionsTests.cs | 10 +- .../Core.Tests/DataLoaderExtensionsTests.cs | 16 +- .../test/Core.Tests/DataLoaderOptionsTests.cs | 2 +- .../test/Core.Tests/DataLoaderTests.cs | 132 ++-- .../test/Core.Tests/GroupDataLoaderTests.cs | 4 +- .../test/Core.Tests/ManualBatchScheduler.cs | 6 +- .../test/Core.Tests/TaskCacheOwnerTests.cs | 2 +- .../test/Core.Tests/TaskCacheTests.cs | 12 +- src/GreenDonut/test/Core.Tests/TestHelpers.cs | 2 +- .../src/CodeGeneration/SyntaxExtensions.cs | 2 + .../src/Abstractions/DataLoaderAttribute.cs | 26 - .../AbstractionResources.Designer.cs | 6 - .../Properties/AbstractionResources.resx | 3 - .../Abstractions/ScopedServiceAttribute.cs | 3 +- .../DataLoaderParameterExpressionBuilder.cs | 21 +- .../DataLoaderResolverContextExtensions.cs | 21 - .../Generators/DataLoaderGenerator.cs | 621 ++++++++++++++++++ .../Generators/ISyntaxGenerator.cs | 6 + .../Generators/ModuleGenerator.cs | 29 +- .../HotChocolate.Types.Analyzers.csproj | 15 + .../Inspectors/ClassBaseClassInspector.cs | 2 +- .../Inspectors/DataLoaderInfo.cs | 46 +- .../Inspectors/DataLoaderInspector.cs | 48 ++ .../Inspectors/DataLoaderKind.cs | 8 + .../Inspectors/RegisterDataLoaderInfo.cs | 39 ++ .../Inspectors/TypeAttributeInspector.cs | 2 +- .../Inspectors/TypeExtensionInfo.cs | 2 +- .../Properties/SourceGenResources.Designer.cs | 54 ++ .../Properties/SourceGenResources.resx | 24 + .../Types.Analyzers/TypeModuleGenerator.cs | 31 +- .../Types.Analyzers/WellKnownAttributes.cs | 1 + .../src/Types.Analyzers/WellKnownFileNames.cs | 2 + .../src/Types.Analyzers/WellKnownTypes.cs | 5 + .../SubscribeAndResolveAttribute.cs | 6 +- .../Integration/DataLoader/DataLoaderTests.cs | 56 -- .../Integration/DataLoader/Query.cs | 8 +- .../Pipeline/TimeoutMiddlewareTests.cs | 4 +- .../Types.Analyzers.Tests/PersonLastName.cs | 2 + .../test/Types.Analyzers.Tests/SomeQuery.cs | 48 +- .../ResolverCompilerBuilderExtensions.cs | 2 +- .../IntegrationTests.cs | 18 +- .../test/Data.EntityFramework.Tests/Query.cs | 3 + .../UseDbContextTests.cs | 36 +- 44 files changed, 1081 insertions(+), 313 deletions(-) delete mode 100644 src/HotChocolate/Core/src/Abstractions/DataLoaderAttribute.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderGenerator.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderKind.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs create mode 100644 src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx diff --git a/src/GreenDonut/test/Core.Tests/BatchDataLoaderTests.cs b/src/GreenDonut/test/Core.Tests/BatchDataLoaderTests.cs index 7df37034040..43a5249c840 100644 --- a/src/GreenDonut/test/Core.Tests/BatchDataLoaderTests.cs +++ b/src/GreenDonut/test/Core.Tests/BatchDataLoaderTests.cs @@ -32,8 +32,8 @@ public async Task LoadTwoAsync() new DataLoaderOptions()); // act - Task result1 = dataLoader.LoadAsync("1abc"); - Task result2 = dataLoader.LoadAsync("0abc"); + var result1 = dataLoader.LoadAsync("1abc"); + var result2 = dataLoader.LoadAsync("0abc"); // assert Assert.Equal("Value:1abc", await result1); @@ -49,8 +49,8 @@ public async Task LoadTheSameKeyTwiceWillYieldSamePromise() new DataLoaderOptions()); // act - Task result1 = dataLoader.LoadAsync("1abc"); - Task result2 = dataLoader.LoadAsync("1abc"); + var result1 = dataLoader.LoadAsync("1abc"); + var result2 = dataLoader.LoadAsync("1abc"); // assert Assert.Same(result1, result2); diff --git a/src/GreenDonut/test/Core.Tests/CancellationTokenSourceExtensionsTests.cs b/src/GreenDonut/test/Core.Tests/CancellationTokenSourceExtensionsTests.cs index ec79c298b82..d80e00dae77 100644 --- a/src/GreenDonut/test/Core.Tests/CancellationTokenSourceExtensionsTests.cs +++ b/src/GreenDonut/test/Core.Tests/CancellationTokenSourceExtensionsTests.cs @@ -11,7 +11,7 @@ public void CreateLinkedCancellationTokenSourceNull() { // arrange CancellationTokenSource source = null; - CancellationToken token = new CancellationTokenSource().Token; + var token = new CancellationTokenSource().Token; // act Action verify = () => source.CreateLinkedCancellationToken(token); @@ -25,10 +25,10 @@ public void CreateLinkedCancellationTokenNone() { // arrange var source = new CancellationTokenSource(); - CancellationToken token = CancellationToken.None; + var token = CancellationToken.None; // act - CancellationToken combinedToken = source + var combinedToken = source .CreateLinkedCancellationToken(token); // assert @@ -40,10 +40,10 @@ public void CreateLinkedCancellationToken() { // arrange var source = new CancellationTokenSource(); - CancellationToken token = new CancellationTokenSource().Token; + var token = new CancellationTokenSource().Token; // act - CancellationToken combinedToken = source + var combinedToken = source .CreateLinkedCancellationToken(token); // assert diff --git a/src/GreenDonut/test/Core.Tests/DataLoaderExtensionsTests.cs b/src/GreenDonut/test/Core.Tests/DataLoaderExtensionsTests.cs index 8d34890f457..33aa4fc367b 100644 --- a/src/GreenDonut/test/Core.Tests/DataLoaderExtensionsTests.cs +++ b/src/GreenDonut/test/Core.Tests/DataLoaderExtensionsTests.cs @@ -25,7 +25,7 @@ public void SetDataLoaderNull() public void SetKeyNull() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var value = "Bar"; @@ -34,14 +34,14 @@ public void SetKeyNull() void Verify() => loader.Set(null!, value); // assert - Assert.Throws("key", (Action)Verify); + Assert.Throws("key", Verify); } [Fact(DisplayName = "Set: Should not throw any exception")] public void SetNoException() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -57,7 +57,7 @@ public void SetNoException() public async Task SetNewCacheEntry() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -76,7 +76,7 @@ public async Task SetNewCacheEntry() public async Task SetTwice() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -111,7 +111,7 @@ public void IDataLoaderSetDataLoaderNull() public void IDataLoaderSetKeyNull() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object value = "Bar"; @@ -127,7 +127,7 @@ public void IDataLoaderSetKeyNull() public void IDataLoaderSetNoException() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; @@ -138,4 +138,4 @@ public void IDataLoaderSetNoException() // assert Assert.Throws(Verify); } -} \ No newline at end of file +} diff --git a/src/GreenDonut/test/Core.Tests/DataLoaderOptionsTests.cs b/src/GreenDonut/test/Core.Tests/DataLoaderOptionsTests.cs index 1dfd5aa42b6..e0fc5cac333 100644 --- a/src/GreenDonut/test/Core.Tests/DataLoaderOptionsTests.cs +++ b/src/GreenDonut/test/Core.Tests/DataLoaderOptionsTests.cs @@ -70,7 +70,7 @@ public void Copy() }; // act - DataLoaderOptions copy = options.Copy(); + var copy = options.Copy(); // assert Assert.NotNull(copy.Cache); diff --git a/src/GreenDonut/test/Core.Tests/DataLoaderTests.cs b/src/GreenDonut/test/Core.Tests/DataLoaderTests.cs index c3d770af24d..f0312839144 100644 --- a/src/GreenDonut/test/Core.Tests/DataLoaderTests.cs +++ b/src/GreenDonut/test/Core.Tests/DataLoaderTests.cs @@ -15,7 +15,7 @@ public class DataLoaderTests public void ClearNoException() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -30,7 +30,7 @@ public void ClearNoException() public void ClearAllEntries() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -50,7 +50,7 @@ public void ClearAllEntries() public void DisposeNoExceptionNoBatchingAndCaching() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -65,7 +65,7 @@ public void DisposeNoExceptionNoBatchingAndCaching() public async Task LoadSingleKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -80,13 +80,13 @@ public async Task LoadSingleKeyNull() public async Task LoadSingleResult() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; // act - Task loadResult = loader.LoadAsync(key); + var loadResult = loader.LoadAsync(key); // assert await Task.Delay(25); @@ -98,7 +98,7 @@ public async Task LoadSingleResult() public async Task LoadSingleResultTwice() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new DelayDispatcher(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -117,7 +117,7 @@ public async Task LoadSingleResultTwice() public async Task LoadSingleResultNoCache() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader( fetch, @@ -129,7 +129,7 @@ public async Task LoadSingleResultNoCache() var key = "Foo"; // act - Task loadResult = loader.LoadAsync(key); + var loadResult = loader.LoadAsync(key); // assert await Task.Delay(25); @@ -141,7 +141,7 @@ public async Task LoadSingleResultNoCache() public async Task LoadSingleErrorResult() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -150,7 +150,7 @@ public async Task LoadSingleErrorResult() Task Verify() => loader.LoadAsync(key, CancellationToken.None); // assert - Task task = Assert + var task = Assert .ThrowsAsync((Func>)Verify); await Task.Delay(25); @@ -163,7 +163,7 @@ public async Task LoadSingleErrorResult() public async Task LoadParamsKeysNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -178,13 +178,13 @@ public async Task LoadParamsKeysNull() public async Task LoadParamsZeroKeys() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var keys = Array.Empty(); // act - Task> loadResult = loader.LoadAsync(keys); + var loadResult = loader.LoadAsync(keys); // assert await Task.Delay(25); @@ -196,14 +196,14 @@ public async Task LoadParamsZeroKeys() public async Task LoadParamsResult() { // arrange - FetchDataDelegate fetch = TestHelpers + var fetch = TestHelpers .CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var keys = new[] { "Foo" }; // act - Task> loadResult = loader.LoadAsync(keys); + var loadResult = loader.LoadAsync(keys); // assert await Task.Delay(25); @@ -215,7 +215,7 @@ public async Task LoadParamsResult() public async Task LoadCollectionKeysNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -231,13 +231,13 @@ Task> Verify() public async Task LoadCollectionZeroKeys() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var keys = new List(); // act - Task> loadResult = loader.LoadAsync(keys, CancellationToken.None); + var loadResult = loader.LoadAsync(keys, CancellationToken.None); // assert await Task.Delay(25); @@ -249,13 +249,13 @@ public async Task LoadCollectionZeroKeys() public async Task LoadCollectionResult() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var keys = new List { "Foo" }; // act - Task> loadResult = loader.LoadAsync(keys, CancellationToken.None); + var loadResult = loader.LoadAsync(keys, CancellationToken.None); batchScheduler.Dispatch(); // assert @@ -266,7 +266,7 @@ public async Task LoadCollectionResult() public async Task LoadCollectionResultTwice() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new DelayDispatcher(); var loader = new DataLoader( fetch, @@ -276,7 +276,7 @@ public async Task LoadCollectionResultTwice() (await loader.LoadAsync(keys, CancellationToken.None)).MatchSnapshot(); // act - IReadOnlyList result = await loader.LoadAsync(keys, CancellationToken.None); + var result = await loader.LoadAsync(keys, CancellationToken.None); // assert result.MatchSnapshot(); @@ -286,7 +286,7 @@ public async Task LoadCollectionResultTwice() public async Task LoadCollectionResultNoCache() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader( fetch, @@ -298,7 +298,7 @@ public async Task LoadCollectionResultNoCache() var keys = new List { "Foo" }; // act - Task> loadResult = loader.LoadAsync(keys, CancellationToken.None); + var loadResult = loader.LoadAsync(keys, CancellationToken.None); batchScheduler.Dispatch(); // assert @@ -322,7 +322,7 @@ public async Task LoadWithNullValues() Memory> results, CancellationToken cancellationToken) { - Span> span = results.Span; + var span = results.Span; for (var i = 0; i < keys.Count; i++) { @@ -340,7 +340,7 @@ public async Task LoadWithNullValues() var requestKeys = new[] { "Foo", "Bar", "Baz", "Qux" }; // act - Task> loadResult = loader.LoadAsync(requestKeys); + var loadResult = loader.LoadAsync(requestKeys); batchScheduler.Dispatch(); // assert @@ -353,7 +353,7 @@ public async Task LoadWithNullValues() public async Task LoadKeyAndValueCountNotEqual() { // arrange - InvalidOperationException expectedException = Errors.CreateKeysAndValuesMustMatch(4, 3); + var expectedException = Errors.CreateKeysAndValuesMustMatch(4, 3); var repository = new Dictionary { @@ -367,7 +367,7 @@ public async Task LoadKeyAndValueCountNotEqual() Memory> results, CancellationToken cancellationToken) { - Span> span = results.Span; + var span = results.Span; for (var i = 0; i < keys.Count; i++) { @@ -388,12 +388,12 @@ public async Task LoadKeyAndValueCountNotEqual() Task Verify() => loader.LoadAsync(requestKeys); // assert - Task task = + var task = Assert.ThrowsAsync(Verify); batchScheduler.Dispatch(); - InvalidOperationException actualException = await task; + var actualException = await task; Assert.Equal(expectedException.Message, actualException.Message); } @@ -418,11 +418,11 @@ public async Task LoadBatchingError() Task Verify() => loader.LoadAsync(requestKeys); // assert - Task task = Assert.ThrowsAsync(Verify); + var task = Assert.ThrowsAsync(Verify); batchScheduler.Dispatch(); - Exception actualException = await task; + var actualException = await task; Assert.Equal(expectedException, actualException); } @@ -516,7 +516,7 @@ async ValueTask Wait() public void RemoveKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); @@ -533,7 +533,7 @@ public void RemoveKeyNull() public void RemoveNoException() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -549,7 +549,7 @@ public void RemoveNoException() public void RemoveEntry() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -569,7 +569,7 @@ public void RemoveEntry() public void SetKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var value = Task.FromResult("Foo"); @@ -585,7 +585,7 @@ public void SetKeyNull() public void SetValueNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var loader = new DataLoader(fetch, batchScheduler); var key = "Foo"; @@ -601,7 +601,7 @@ public void SetValueNull() public void SetNewCacheEntry() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -620,7 +620,7 @@ public void SetNewCacheEntry() public void SetTwice() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -641,7 +641,7 @@ public void SetTwice() public async Task IDataLoaderLoadSingleKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); @@ -656,13 +656,13 @@ public async Task IDataLoaderLoadSingleKeyNull() public async Task IDataLoaderLoadSingleResult() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; // act - Task loadResult = loader.LoadAsync(key); + var loadResult = loader.LoadAsync(key); // assert await Task.Delay(25); @@ -674,7 +674,7 @@ public async Task IDataLoaderLoadSingleResult() public async Task IDataLoaderLoadSingleErrorResult() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; @@ -683,7 +683,7 @@ public async Task IDataLoaderLoadSingleErrorResult() Task Verify() => loader.LoadAsync(key); // assert - Task task = + var task = Assert.ThrowsAsync(Verify); await Task.Delay(25); @@ -696,7 +696,7 @@ public async Task IDataLoaderLoadSingleErrorResult() public async Task IDataLoaderLoadParamsKeysNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); @@ -711,13 +711,13 @@ public async Task IDataLoaderLoadParamsKeysNull() public async Task IDataLoaderLoadParamsZeroKeys() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); var keys = Array.Empty(); // act - IReadOnlyList loadResult = await loader.LoadAsync(keys); + var loadResult = await loader.LoadAsync(keys); // assert Assert.Empty(loadResult); @@ -727,13 +727,13 @@ public async Task IDataLoaderLoadParamsZeroKeys() public async Task IDataLoaderLoadParamsResult() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); var keys = new object[] { "Foo" }; // act - Task> loadResult = loader.LoadAsync(keys); + var loadResult = loader.LoadAsync(keys); // assert await Task.Delay(25); @@ -745,7 +745,7 @@ public async Task IDataLoaderLoadParamsResult() public async Task IDataLoaderLoadCollectionKeysNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); @@ -761,13 +761,13 @@ Task> Verify() public async Task IDataLoaderLoadCollectionZeroKeys() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); var keys = new List(); // act - IReadOnlyList loadResult = await loader.LoadAsync(keys); + var loadResult = await loader.LoadAsync(keys); // assert Assert.Empty(loadResult); @@ -777,13 +777,13 @@ public async Task IDataLoaderLoadCollectionZeroKeys() public async Task IDataLoaderLoadCollectionResult() { // arrange - FetchDataDelegate fetch = CreateFetch("Bar"); + var fetch = CreateFetch("Bar"); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); var keys = new List { "Foo" }; // act - Task> loadResult = loader.LoadAsync(keys); + var loadResult = loader.LoadAsync(keys); // assert await Task.Delay(25); @@ -795,7 +795,7 @@ public async Task IDataLoaderLoadCollectionResult() public void IDataLoaderRemoveKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = null; @@ -803,7 +803,7 @@ public void IDataLoaderRemoveKeyNull() loader.Set("Foo", Task.FromResult((object)"Bar")); // act - Action verify = () => loader.Remove(key); + var verify = () => loader.Remove(key); // assert Assert.Throws("key", verify); @@ -813,13 +813,13 @@ public void IDataLoaderRemoveKeyNull() public void IDataLoaderRemoveNoException() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; // act - Action verify = () => loader.Remove(key); + var verify = () => loader.Remove(key); // assert Assert.Null(Record.Exception(verify)); @@ -829,7 +829,7 @@ public void IDataLoaderRemoveNoException() public void IDataLoaderRemoveEntry() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -849,7 +849,7 @@ public void IDataLoaderRemoveEntry() public void IDataLoaderSetKeyNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); var value = Task.FromResult("Foo"); @@ -865,7 +865,7 @@ public void IDataLoaderSetKeyNull() public void IDataLoaderSetValueNull() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; @@ -881,14 +881,14 @@ public void IDataLoaderSetValueNull() public void IDataLoaderSetNoException() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); IDataLoader loader = new DataLoader(fetch, batchScheduler); object key = "Foo"; var value = Task.FromResult("Bar"); // act - Action verify = () => loader.Set(key, value); + var verify = () => loader.Set(key, value); // assert Assert.Null(Record.Exception(verify)); @@ -898,7 +898,7 @@ public void IDataLoaderSetNoException() public void IDataLoaderSetNewCacheEntry() { // arrange - FetchDataDelegate fetch = CreateFetch(); + var fetch = CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; @@ -917,7 +917,7 @@ public void IDataLoaderSetNewCacheEntry() public void IDataLoaderSetTwice() { // arrange - FetchDataDelegate fetch = TestHelpers.CreateFetch(); + var fetch = TestHelpers.CreateFetch(); var batchScheduler = new ManualBatchScheduler(); var cache = new TaskCache(10); var options = new DataLoaderOptions { Cache = cache }; diff --git a/src/GreenDonut/test/Core.Tests/GroupDataLoaderTests.cs b/src/GreenDonut/test/Core.Tests/GroupDataLoaderTests.cs index c44bed9d7f9..cb696cad49c 100644 --- a/src/GreenDonut/test/Core.Tests/GroupDataLoaderTests.cs +++ b/src/GreenDonut/test/Core.Tests/GroupDataLoaderTests.cs @@ -32,8 +32,8 @@ public async Task LoadTwoAsync() new DataLoaderOptions()); // act - Task result1 = dataLoader.LoadAsync("1abc"); - Task result2 = dataLoader.LoadAsync("0abc"); + var result1 = dataLoader.LoadAsync("1abc"); + var result2 = dataLoader.LoadAsync("0abc"); // assert Assert.Collection(await result1, t => Assert.Equal("Value:1abc", t)); diff --git a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs index f39de583a3e..e350eb3ada2 100644 --- a/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs +++ b/src/GreenDonut/test/Core.Tests/ManualBatchScheduler.cs @@ -6,14 +6,14 @@ namespace GreenDonut; public class ManualBatchScheduler : IBatchScheduler { - private readonly object _sync = new object(); + private readonly object _sync = new(); private readonly ConcurrentQueue> _queue = new(); public void Dispatch() { lock(_sync) { - while (_queue.TryDequeue(out Func dispatch)) + while (_queue.TryDequeue(out var dispatch)) { dispatch(); } @@ -27,4 +27,4 @@ public void Schedule(Func dispatch) _queue.Enqueue(dispatch); } } -} \ No newline at end of file +} diff --git a/src/GreenDonut/test/Core.Tests/TaskCacheOwnerTests.cs b/src/GreenDonut/test/Core.Tests/TaskCacheOwnerTests.cs index 0a3db043b9e..83fb5092d86 100644 --- a/src/GreenDonut/test/Core.Tests/TaskCacheOwnerTests.cs +++ b/src/GreenDonut/test/Core.Tests/TaskCacheOwnerTests.cs @@ -9,7 +9,7 @@ public void EnsureTaskCacheIsReused() { // arrange var cacheOwner1 = new TaskCacheOwner(); - ITaskCache cache = cacheOwner1.Cache; + var cache = cacheOwner1.Cache; cacheOwner1.Dispose(); // act diff --git a/src/GreenDonut/test/Core.Tests/TaskCacheTests.cs b/src/GreenDonut/test/Core.Tests/TaskCacheTests.cs index 5e07a69a64d..437e59b10cd 100644 --- a/src/GreenDonut/test/Core.Tests/TaskCacheTests.cs +++ b/src/GreenDonut/test/Core.Tests/TaskCacheTests.cs @@ -136,7 +136,7 @@ public void RemoveEntry() cache.TryRemove(key); // assert - Task retrieved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); + var retrieved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); Assert.NotSame(value, retrieved); } @@ -168,7 +168,7 @@ public void TryAddNewCacheEntry() var added = cache.TryAdd(key, expected); // assert - Task resolved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); + var resolved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); Assert.True(added); Assert.Same(expected, resolved); @@ -187,7 +187,7 @@ public void TryAddNewCacheEntryWithFactory() var added = cache.TryAdd(key, () => expected); // assert - Task resolved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); + var resolved = cache.GetOrAddTask(key, () => Task.FromResult("Baz")); Assert.True(added); Assert.Same(expected, resolved); @@ -208,7 +208,7 @@ public void TryAddTwice() var addedSecond = cache.TryAdd(key, another); // assert - Task resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); + var resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); Assert.True(addedFirst); Assert.False(addedSecond); @@ -224,7 +224,7 @@ public void GetOrAddTaskWhenNothingIsCached() var key = new TaskCacheKey("a", "Foo"); // act - Task resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); + var resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); // assert Assert.Equal("Quox", resolved.Result); @@ -239,7 +239,7 @@ public void GetOrAddTaskWhenNothingIsCached_IntegerKey() var key = new TaskCacheKey("a", 1); // act - Task resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); + var resolved = cache.GetOrAddTask(key, () => Task.FromResult("Quox")); // assert Assert.Equal("Quox", resolved.Result); diff --git a/src/GreenDonut/test/Core.Tests/TestHelpers.cs b/src/GreenDonut/test/Core.Tests/TestHelpers.cs index 6c9ad25fce0..7067f5e2fad 100644 --- a/src/GreenDonut/test/Core.Tests/TestHelpers.cs +++ b/src/GreenDonut/test/Core.Tests/TestHelpers.cs @@ -34,7 +34,7 @@ internal static class TestHelpers { return (_, results, _) => { - Span> span = results.Span; + var span = results.Span; for (var i = 0; i < results.Length; i++) { diff --git a/src/HotChocolate/CodeGeneration/src/CodeGeneration/SyntaxExtensions.cs b/src/HotChocolate/CodeGeneration/src/CodeGeneration/SyntaxExtensions.cs index 6232820af05..19a5534e238 100644 --- a/src/HotChocolate/CodeGeneration/src/CodeGeneration/SyntaxExtensions.cs +++ b/src/HotChocolate/CodeGeneration/src/CodeGeneration/SyntaxExtensions.cs @@ -347,6 +347,7 @@ public static T AddExtendObjectTypeAttribute(this T type, string typeName) return methodSyntax.AddAttributeLists(AttributeList(SingletonSeparatedList(attribute))); } +#pragma warning disable CS0618 public static ParameterSyntax AddScopedServiceAttribute( this ParameterSyntax methodSyntax) { @@ -355,4 +356,5 @@ public static T AddExtendObjectTypeAttribute(this T type, string typeName) return methodSyntax.AddAttributeLists(AttributeList(SingletonSeparatedList(attribute))); } +#pragma warning restore CS0618 } diff --git a/src/HotChocolate/Core/src/Abstractions/DataLoaderAttribute.cs b/src/HotChocolate/Core/src/Abstractions/DataLoaderAttribute.cs deleted file mode 100644 index 748ac90c520..00000000000 --- a/src/HotChocolate/Core/src/Abstractions/DataLoaderAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using HotChocolate.Properties; - -namespace HotChocolate; - -[AttributeUsage(AttributeTargets.Parameter)] -public sealed class DataLoaderAttribute : Attribute -{ - public DataLoaderAttribute() - { - } - - public DataLoaderAttribute(string key) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException( - AbstractionResources.DataLoader_KeyMustNotBeNullOrEmpty, - nameof(key)); - } - - Key = key; - } - - public string? Key { get; } -} diff --git a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs index e4aebb306f3..f629dc28095 100644 --- a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs +++ b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.Designer.cs @@ -45,12 +45,6 @@ internal class AbstractionResources { } } - internal static string DataLoader_KeyMustNotBeNullOrEmpty { - get { - return ResourceManager.GetString("DataLoader_KeyMustNotBeNullOrEmpty", resourceCulture); - } - } - internal static string DirectiveArgument_NameMustNotBeNullOrempty { get { return ResourceManager.GetString("DirectiveArgument_NameMustNotBeNullOrempty", resourceCulture); diff --git a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx index ddc3ec135b6..e2cca96c155 100644 --- a/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx +++ b/src/HotChocolate/Core/src/Abstractions/Properties/AbstractionResources.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The DataLoader key mustn't null or empty. - The directive argument name be mustn't null or empty. diff --git a/src/HotChocolate/Core/src/Abstractions/ScopedServiceAttribute.cs b/src/HotChocolate/Core/src/Abstractions/ScopedServiceAttribute.cs index 6647d02f96a..63697a86c4c 100644 --- a/src/HotChocolate/Core/src/Abstractions/ScopedServiceAttribute.cs +++ b/src/HotChocolate/Core/src/Abstractions/ScopedServiceAttribute.cs @@ -5,7 +5,6 @@ namespace HotChocolate; /// /// Marks a resolver parameter as a pooled service that shall be injected by the execution engine. /// -// TODO : Mark obsolete with 13 -// [Obsolete("Use [Service(ServiceKind.Pooled)]")] +[Obsolete("Use [Service(ServiceKind.Pooled)] or [Service(ServiceKind.Resolver)]")] [AttributeUsage(AttributeTargets.Parameter)] public sealed class ScopedServiceAttribute : Attribute { } diff --git a/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs b/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs index a221e9f29a3..9bbf329833d 100644 --- a/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs +++ b/src/HotChocolate/Core/src/Fetching/DataLoaderParameterExpressionBuilder.cs @@ -11,34 +11,17 @@ namespace HotChocolate.Fetching; public sealed class DataLoaderParameterExpressionBuilder : CustomParameterExpressionBuilder { private static readonly MethodInfo _dataLoader; - private static readonly MethodInfo _dataLoaderWithKey; static DataLoaderParameterExpressionBuilder() { - _dataLoaderWithKey = typeof(DataLoaderResolverContextExtensions) - .GetMethods() - .First(t => t.IsDefined(typeof(GetDataLoaderWithKeyAttribute))); - _dataLoader = typeof(DataLoaderResolverContextExtensions) .GetMethods() .First(t => t.IsDefined(typeof(GetDataLoaderAttribute))); } public override bool CanHandle(ParameterInfo parameter) - => parameter.IsDefined(typeof(DataLoaderAttribute)) || - typeof(IDataLoader).IsAssignableFrom(parameter.ParameterType); + => typeof(IDataLoader).IsAssignableFrom(parameter.ParameterType); public override Expression Build(ParameterInfo parameter, Expression context) - { - var attribute = parameter.GetCustomAttribute(); - - return string.IsNullOrEmpty(attribute?.Key) - ? Expression.Call( - _dataLoader.MakeGenericMethod(parameter.ParameterType), - context) - : Expression.Call( - _dataLoaderWithKey.MakeGenericMethod(parameter.ParameterType), - context, - Expression.Constant(attribute.Key)); - } + => Expression.Call(_dataLoader.MakeGenericMethod(parameter.ParameterType), context); } diff --git a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs index acabfa11e68..8234d813f7c 100644 --- a/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs +++ b/src/HotChocolate/Core/src/Fetching/Extensions/DataLoaderResolverContextExtensions.cs @@ -346,25 +346,6 @@ public static class DataLoaderResolverContextExtensions return FetchOnceAsync(context, fetch, key); } - [GetDataLoaderWithKey] - public static T DataLoader(this IResolverContext context, string key) - where T : IDataLoader - { - if (context is null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - - var services = context.RequestServices; - var reg = services.GetRequiredService(); - return reg.GetOrRegister(key, () => CreateDataLoader(services)); - } - [GetDataLoader] public static T DataLoader(this IResolverContext context) where T : IDataLoader @@ -410,6 +391,4 @@ private static T CreateDataLoader(IServiceProvider services) } } -internal sealed class GetDataLoaderWithKeyAttribute : Attribute { } - internal sealed class GetDataLoaderAttribute : Attribute { } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderGenerator.cs new file mode 100644 index 00000000000..e82850f7ad0 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/DataLoaderGenerator.cs @@ -0,0 +1,621 @@ +using System.Text; +using HotChocolate.Types.Analyzers.Inspectors; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using static System.StringComparison; +using static HotChocolate.Types.Analyzers.Properties.SourceGenResources; +using static HotChocolate.Types.Analyzers.StringConstants; +using static HotChocolate.Types.Analyzers.WellKnownTypes; + +namespace HotChocolate.Types.Analyzers.Generators; + +public class DataLoaderGenerator : ISyntaxGenerator +{ + private static readonly DiagnosticDescriptor _keyParameterMissing = + new( + id: "HC0074", + title: "Parameter Missing.", + messageFormat: + DataLoader_KeyParameterMissing, + category: "DataLoader", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public void Initialize(IncrementalGeneratorPostInitializationContext context) + { + context.AddSource( + WellKnownFileNames.AttributesFile, + """ + // + #nullable enable + + namespace HotChocolate.Fetching + { + [global::System.AttributeUsage( + global::System.AttributeTargets.Method, + AllowMultiple = false)] + public sealed class DataLoaderAttribute : global::System.Attribute + { + public DataLoaderAttribute(string? name = null) + { + Name = name; + } + + public string? Name { get; } + + public bool Scoped { get; set; } + + public bool IsInternal { get; set; } + } + } + """); + } + + public bool Consume(ISyntaxInfo syntaxInfo) + => syntaxInfo is DataLoaderInfo or ModuleInfo; + + public void Generate( + SourceProductionContext context, + Compilation compilation, + IReadOnlyCollection syntaxInfos) + { + var module = + syntaxInfos.OfType().FirstOrDefault() ?? + new ModuleInfo( + compilation.AssemblyName is null + ? "AssemblyTypes" + : compilation.AssemblyName?.Split('.').Last() + "Types", + ModuleOptions.Default); + + var dataLoaders = new List(); + var sourceText = new StringBuilder(); + + sourceText.AppendLine("// "); + sourceText.AppendLine("#nullable enable"); + sourceText.AppendLine("using System;"); + sourceText.AppendLine("using Microsoft.Extensions.DependencyInjection;"); + sourceText.AppendLine("using HotChocolate.Execution.Configuration;"); + sourceText.AppendLine(); + sourceText.AppendLine("namespace HotChocolate.Fetching"); + sourceText.AppendLine("{"); + + foreach (var syntaxInfo in syntaxInfos) + { + if (syntaxInfo is DataLoaderInfo dataLoader) + { + if (dataLoader.MethodSymbol.Parameters.Length == 0) + { + context.ReportDiagnostic( + Diagnostic.Create( + _keyParameterMissing, + Location.Create( + dataLoader.MethodSyntax.SyntaxTree, + dataLoader.MethodSyntax.ParameterList.Span))); + continue; + } + + var dataLoaderAttribute = GetAttribute( + dataLoader, + "DataLoaderAttribute"); + + var dataLoaderName = GetDataLoaderName( + dataLoader.MethodSymbol.Name, + dataLoaderAttribute); + + var isScoped = IsScoped(dataLoaderAttribute); + var isPublic = IsPublic(dataLoaderAttribute); + + var keyArg = dataLoader.MethodSymbol.Parameters[0]; + var keyType = keyArg.Type; + var cancellationTokenIndex = -1; + var serviceMap = new Dictionary(); + + if (IsKeysArgument(keyType)) + { + keyType = ExtractKeyType(keyType); + } + + InspectDataLoaderParameters( + dataLoader, + ref cancellationTokenIndex, + serviceMap); + + DataLoaderKind kind; + if (IsReturnTypeDictionary(dataLoader.MethodSymbol.ReturnType, keyType)) + { + kind = DataLoaderKind.Batch; + } + else if (IsReturnTypeLookup(dataLoader.MethodSymbol.ReturnType, keyType)) + { + kind = DataLoaderKind.Group; + } + else + { + kind = DataLoaderKind.Cache; + } + + var valueType = ExtractValueType(dataLoader.MethodSymbol.ReturnType, kind); + + dataLoaders.Add(dataLoaderName); + + GenerateDataLoader( + dataLoaderName, + kind, + isPublic, + isScoped, + dataLoader.MethodSymbol, + keyType, + valueType, + dataLoader.MethodSymbol.Parameters.Length, + cancellationTokenIndex, + serviceMap, + sourceText); + } + } + + // if we find no valid DataLoader we will not create any file. + if (dataLoaders.Count > 0) + { + sourceText.AppendLine("}"); + sourceText.AppendLine(); + + // write DI integration + sourceText.AppendLine("namespace Microsoft.Extensions.DependencyInjection"); + sourceText.AppendLine("{"); + GenerateDataLoaderRegistrations(module, dataLoaders, sourceText); + sourceText.AppendLine("}"); + + context.AddSource( + WellKnownFileNames.DataLoaderFile, + SourceText.From(sourceText.ToString(), Encoding.UTF8)); + } + } + + private static void GenerateDataLoader( + string name, + DataLoaderKind kind, + bool isPublic, + bool isScoped, + IMethodSymbol method, + ITypeSymbol keyType, + ITypeSymbol valueType, + int parameterCount, + int cancelIndex, + Dictionary services, + StringBuilder sourceText) + { + if (isPublic) + { + sourceText.Append(" public sealed class "); + } + else + { + sourceText.Append(" internal sealed class "); + } + + sourceText.Append(name); + + if (kind is DataLoaderKind.Batch) + { + sourceText.Append(" : global::GreenDonut.BatchDataLoader<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(keyType)); + sourceText.Append(", "); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.AppendLine(">"); + } + else if (kind is DataLoaderKind.Group) + { + sourceText.Append(" : global::GreenDonut.GroupedDataLoader<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(keyType)); + sourceText.Append(", "); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.AppendLine(">"); + } + else if (kind is DataLoaderKind.Cache) + { + sourceText.Append(" : global::GreenDonut.CacheDataLoader<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(keyType)); + sourceText.Append(", "); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.AppendLine(">"); + } + + sourceText.AppendLine(" {"); + + sourceText.AppendLine(" private readonly IServiceProvider _services;"); + sourceText.AppendLine(); + + if (kind is DataLoaderKind.Batch or DataLoaderKind.Group) + { + sourceText + .Append(Indent) + .Append(Indent) + .AppendLine($"public {name}("); + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("IServiceProvider services,"); + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("global::GreenDonut.IBatchScheduler batchScheduler,"); + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("global::GreenDonut.DataLoaderOptions? options = null)"); + + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine(": base(batchScheduler, options)"); + } + else + { + sourceText + .Append(Indent) + .Append(Indent) + .AppendLine($"public {name}("); + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("IServiceProvider services,"); + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("global::GreenDonut.DataLoaderOptions? options = null)"); + + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine(": base(options)"); + } + + + sourceText.AppendLine(" {"); + sourceText.AppendLine(" _services = services ??"); + sourceText.AppendLine(" throw new ArgumentNullException(nameof(services));"); + sourceText.AppendLine(" }"); + sourceText.AppendLine(); + + if (kind is DataLoaderKind.Batch) + { + sourceText.Append($" protected override async {WellKnownTypes.Task}<"); + sourceText.Append($"global::{ReadOnlyDictionary}<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(keyType)); + sourceText.Append(", global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.Append(">> "); + sourceText.AppendLine("LoadBatchAsync("); + sourceText.Append($" global::{ReadOnlyList}<"); + sourceText.AppendLine($"global::{ToTypeName(keyType)}> keys,"); + sourceText.AppendLine($" global::{WellKnownTypes.CancellationToken} ct)"); + } + else if (kind is DataLoaderKind.Group) + { + sourceText.Append($" protected override async {WellKnownTypes.Task}<"); + sourceText.Append($"global::{Lookup}<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(keyType)); + sourceText.Append(", global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.Append(">> "); + sourceText.AppendLine("LoadGroupedBatchAsync("); + sourceText.Append($" global::{ReadOnlyList}<"); + sourceText.AppendLine($"global::{ToTypeName(keyType)}> keys,"); + sourceText.AppendLine($" global::{WellKnownTypes.CancellationToken} ct)"); + } + else if (kind is DataLoaderKind.Cache) + { + sourceText.Append($" protected override async {WellKnownTypes.Task}<"); + sourceText.Append("global::"); + sourceText.Append(ToTypeName(valueType)); + sourceText.Append("> "); + sourceText.AppendLine("LoadSingleAsync("); + sourceText.AppendLine($" global::{ToTypeName(keyType)} key,"); + sourceText.AppendLine($" global::{WellKnownTypes.CancellationToken} ct)"); + } + + sourceText.AppendLine(" {"); + + if (isScoped) + { + sourceText.AppendLine("await using var scope = _services.CreateAsyncScope();"); + + foreach (var item in services.OrderBy(t => t.Key)) + { + sourceText.Append($" var p{item.Key} = "); + sourceText.Append("scope.ServiceProvider.GetRequiredService<"); + sourceText.Append(item.Value); + sourceText.AppendLine(">();"); + } + } + else + { + foreach (var item in services.OrderBy(t => t.Key)) + { + sourceText.Append($" var p{item.Key} = _services.GetRequiredService<"); + sourceText.Append(item.Value); + sourceText.AppendLine(">();"); + } + } + + sourceText.Append(" return await global::"); + sourceText.Append(ToTypeName(method.ContainingType)); + sourceText.Append("."); + sourceText.Append(method.Name); + sourceText.Append("("); + + for (var i = 0; i < parameterCount; i++) + { + if (i > 0) + { + sourceText.Append(", "); + } + + if (i == 0) + { + if (kind is DataLoaderKind.Batch or DataLoaderKind.Group) + { + sourceText.Append("keys"); + } + else + { + sourceText.Append("key"); + } + } + else if (i == cancelIndex) + { + sourceText.Append("ct"); + } + else + { + sourceText.Append("p"); + sourceText.Append(i); + } + } + sourceText.AppendLine(").ConfigureAwait(false);"); + + sourceText.AppendLine(" }"); + sourceText.AppendLine(" }"); + } + + private static void GenerateDataLoaderRegistrations( + ModuleInfo module, + List dataLoaders, + StringBuilder sourceText) + { + sourceText.Append(Indent) + .Append("public static partial class ") + .Append(module.ModuleName) + .AppendLine("RequestExecutorBuilderExtensions"); + + sourceText + .Append(Indent) + .AppendLine("{"); + + sourceText + .Append(Indent) + .Append(Indent) + .Append("static partial void RegisterGeneratedDataLoader(") + .AppendLine("IRequestExecutorBuilder builder)"); + + sourceText + .Append(Indent) + .Append(Indent) + .AppendLine("{"); + + foreach (var dataLoader in dataLoaders) + { + sourceText + .Append(Indent) + .Append(Indent) + .Append(Indent) + .Append("builder.AddDataLoader();"); + } + + sourceText + .Append(Indent) + .Append(Indent) + .AppendLine("}"); + + sourceText + .Append(Indent) + .AppendLine("}"); + } + + private static bool IsScoped(AttributeData attribute) + { + var scoped = attribute.NamedArguments.FirstOrDefault( + t => t.Key.Equals("IsScoped", Ordinal)); + + if (scoped.Value.Value is bool b) + { + return b; + } + + return false; + } + + private static bool IsPublic(AttributeData attribute) + { + var scoped = attribute.NamedArguments.FirstOrDefault( + t => t.Key.Equals("IsInternal", Ordinal)); + + if (scoped.Value.Value is bool b) + { + return !b; + } + + return true; + } + + private static string GetDataLoaderName(string name, AttributeData attribute) + { + if (attribute.ConstructorArguments.Length > 0 && + attribute.ConstructorArguments[0].Value is string s) + { + return s; + } + + if (name.StartsWith("Get")) + { + name = name.Substring(3); + } + + if (name.EndsWith("Async")) + { + name = name.Substring(0, name.Length - 5); + } + + if (name.EndsWith("DataLoader")) + { + return name; + } + + return name + "DataLoader"; + } + + private void InspectDataLoaderParameters( + DataLoaderInfo dataLoader, + ref int cancellationTokenIndex, + Dictionary serviceMap) + { + for (var i = 1; i < dataLoader.MethodSymbol.Parameters.Length; i++) + { + var argument = dataLoader.MethodSymbol.Parameters[i]; + var argumentType = ToTypeName(argument.Type); + + if (IsCancellationToken(argumentType)) + { + if (cancellationTokenIndex != -1) + { + // report error + return; + } + + cancellationTokenIndex = i; + } + else + { + serviceMap[i] = argumentType; + } + } + } + + private static bool IsKeysArgument(ITypeSymbol type) + { + if (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType && + ReadOnlyList.Equals(ToTypeName(namedType), Ordinal)) + { + return true; + } + + return false; + } + + private static ITypeSymbol ExtractKeyType(ITypeSymbol type) + { + if (type is INamedTypeSymbol { IsGenericType: true, TypeArguments.Length: 1 } namedType && + ReadOnlyList.Equals(ToTypeName(namedType), Ordinal)) + { + return namedType.TypeArguments[0]; + } + + throw new InvalidOperationException(); + } + + private static bool IsCancellationToken(string typeName) + => string.Equals(typeName, WellKnownTypes.CancellationToken); + + private static string ToTypeName(ITypeSymbol type) + => $"{type.ContainingNamespace}.{type.Name}"; + + private static bool IsReturnTypeDictionary(ITypeSymbol returnType, ITypeSymbol keyType) + { + if (returnType is INamedTypeSymbol { TypeArguments.Length: 1 } namedType) + { + var resultType = namedType.TypeArguments[0]; + + if (IsReadOnlyDictionary(resultType) && + resultType is INamedTypeSymbol { TypeArguments.Length: 2 } dictionaryType && + dictionaryType.TypeArguments[0].Equals(keyType, SymbolEqualityComparer.Default)) + { + return true; + } + } + return false; + } + + private static bool IsReturnTypeLookup(ITypeSymbol returnType, ITypeSymbol keyType) + { + if (returnType is INamedTypeSymbol { TypeArguments.Length: 1 } namedType) + { + var resultType = namedType.TypeArguments[0]; + + if (ToTypeName(resultType).Equals(Lookup, Ordinal) && + resultType is INamedTypeSymbol { TypeArguments.Length: 2 } dictionaryType && + dictionaryType.TypeArguments[0].Equals(keyType, SymbolEqualityComparer.Default)) + { + return true; + } + } + return false; + } + + private static bool IsReadOnlyDictionary(ITypeSymbol type) + { + if (!ToTypeName(type).Equals(ReadOnlyDictionary, Ordinal)) + { + foreach (var interfaceSymbol in type.Interfaces) + { + if (ToTypeName(interfaceSymbol).Equals(ReadOnlyDictionary, Ordinal)) + { + return true; + } + } + + return false; + } + + return true; + } + + private static ITypeSymbol ExtractValueType(ITypeSymbol returnType, DataLoaderKind kind) + { + if (returnType is INamedTypeSymbol { TypeArguments.Length: 1 } namedType) + { + if (kind is DataLoaderKind.Batch or DataLoaderKind.Group && + namedType.TypeArguments[0] is INamedTypeSymbol { TypeArguments.Length: 2 } dict) + { + return dict.TypeArguments[1]; + } + + if (kind is DataLoaderKind.Cache) + { + return namedType.TypeArguments[0]; + } + } + + throw new InvalidOperationException(); + } + + private static AttributeData GetAttribute(DataLoaderInfo dataLoaderInfo, string className) + => dataLoaderInfo.MethodSymbol.GetAttributes().First( + t => string.Equals(t.AttributeClass?.Name, className, Ordinal)); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs index caedc29b41b..662eae70c6b 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ISyntaxGenerator.cs @@ -8,6 +8,12 @@ namespace HotChocolate.Types.Analyzers.Generators; /// public interface ISyntaxGenerator { + /// + /// Allows to create initial code like attributes. + /// + /// + void Initialize(IncrementalGeneratorPostInitializationContext context); + /// /// Specifies if the given will be consumed by this generator. /// diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleGenerator.cs index 62358c4d859..07a3ca160e9 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Generators/ModuleGenerator.cs @@ -10,8 +10,12 @@ namespace HotChocolate.Types.Analyzers.Generators; public class ModuleGenerator : ISyntaxGenerator { + public void Initialize(IncrementalGeneratorPostInitializationContext context) + { + } + public bool Consume(ISyntaxInfo syntaxInfo) - => syntaxInfo is TypeInfo or TypeExtensionInfo or DataLoaderInfo or ModuleInfo; + => syntaxInfo is TypeInfo or TypeExtensionInfo or RegisterDataLoaderInfo or ModuleInfo; public void Generate( SourceProductionContext context, @@ -33,6 +37,8 @@ public bool Consume(ISyntaxInfo syntaxInfo) } var code = new StringBuilder(); + code.AppendLine("// "); + code.AppendLine("#nullable enable"); code.AppendLine("using System;"); code.AppendLine("using HotChocolate.Execution.Configuration;"); @@ -41,7 +47,7 @@ public bool Consume(ISyntaxInfo syntaxInfo) code.AppendLine("{"); code.Append(Indent) - .Append("public static class ") + .Append("public static partial class ") .Append(module.ModuleName) .AppendLine("RequestExecutorBuilderExtensions"); @@ -56,6 +62,13 @@ public bool Consume(ISyntaxInfo syntaxInfo) code.Append(Indent).Append(Indent).AppendLine("{"); + code + .Append(Indent) + .Append(Indent) + .Append(Indent) + .AppendLine("RegisterGeneratedDataLoader(builder);"); + code.AppendLine(); + var operations = OperationType.No; foreach (var syntaxInfo in batch.Distinct()) @@ -106,7 +119,7 @@ public bool Consume(ISyntaxInfo syntaxInfo) } break; - case DataLoaderInfo dataLoader: + case RegisterDataLoaderInfo dataLoader: if ((module.Options & ModuleOptions.RegisterDataLoader) == ModuleOptions.RegisterDataLoader) { @@ -138,7 +151,17 @@ public bool Consume(ISyntaxInfo syntaxInfo) code.Append(Indent).Append(Indent).Append(Indent).AppendLine("return builder;"); code.Append(Indent).Append(Indent).AppendLine("}"); + + code.AppendLine(); + + code + .Append(Indent) + .Append(Indent) + .Append("static partial void RegisterGeneratedDataLoader(") + .AppendLine("IRequestExecutorBuilder builder);"); + code.Append(Indent).AppendLine("}"); + code.AppendLine("}"); context.AddSource(TypeModuleFile, SourceText.From(code.ToString(), Encoding.UTF8)); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj b/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj index 81e26653e84..eddb3bc294a 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj +++ b/src/HotChocolate/Core/src/Types.Analyzers/HotChocolate.Types.Analyzers.csproj @@ -26,4 +26,19 @@ + + + ResXFileCodeGenerator + SourceGenResources.Designer.cs + + + + + + True + True + SourceGenResources.resx + + + diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs index 9f9dafaf85c..8e707dc9d29 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/ClassBaseClassInspector.cs @@ -60,7 +60,7 @@ public class ClassBaseClassInspector : ISyntaxInspector if (displayString.Equals(WellKnownTypes.DataLoader, StringComparison.Ordinal)) { - syntaxInfo = new DataLoaderInfo(typeDisplayString); + syntaxInfo = new RegisterDataLoaderInfo(typeDisplayString); return true; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs index f8c305ea45c..7cd9bc6f78b 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInfo.cs @@ -1,39 +1,27 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + namespace HotChocolate.Types.Analyzers.Inspectors; -public sealed class DataLoaderInfo : ISyntaxInfo, IEquatable +public sealed class DataLoaderInfo : ISyntaxInfo { - public DataLoaderInfo(string name) + public DataLoaderInfo( + AttributeSyntax attributeSyntax, + IMethodSymbol attributeSymbol, + IMethodSymbol methodSymbol, + MethodDeclarationSyntax methodSyntax) { - Name = name; - } - - public string Name { get; } - - public bool Equals(DataLoaderInfo? other) - { - if (ReferenceEquals(null, other)) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return Name == other.Name; + AttributeSyntax = attributeSyntax; + AttributeSymbol = attributeSymbol; + MethodSymbol = methodSymbol; + MethodSyntax = methodSyntax; } - public override bool Equals(object? obj) - => ReferenceEquals(this, obj) || - obj is DataLoaderInfo other && Equals(other); + public AttributeSyntax AttributeSyntax { get; } - public override int GetHashCode() - => Name.GetHashCode(); + public IMethodSymbol AttributeSymbol { get; } - public static bool operator ==(DataLoaderInfo? left, DataLoaderInfo? right) - => Equals(left, right); + public IMethodSymbol MethodSymbol { get; } - public static bool operator !=(DataLoaderInfo? left, DataLoaderInfo? right) - => !Equals(left, right); + public MethodDeclarationSyntax MethodSyntax { get; } } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs new file mode 100644 index 00000000000..6e86353d399 --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderInspector.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static System.StringComparison; + +namespace HotChocolate.Types.Analyzers.Inspectors; + +public sealed class DataLoaderInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is MethodDeclarationSyntax { AttributeLists.Count: > 0 } methodSyntax) + { + foreach (var attributeListSyntax in methodSyntax.AttributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var symbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol; + + if (symbol is not IMethodSymbol attributeSymbol) + { + continue; + } + + var attributeContainingTypeSymbol = attributeSymbol.ContainingType; + var fullName = attributeContainingTypeSymbol.ToDisplayString(); + + if (fullName.Equals(WellKnownAttributes.DataLoaderAttribute, Ordinal) && + context.SemanticModel.GetDeclaredSymbol(methodSyntax) is { } methodSymbol) + { + syntaxInfo = new DataLoaderInfo( + attributeSyntax, + attributeSymbol, + methodSymbol, + methodSyntax); + return true; + } + } + } + } + + syntaxInfo = null; + return false; + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderKind.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderKind.cs new file mode 100644 index 00000000000..9e84fa3efac --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/DataLoaderKind.cs @@ -0,0 +1,8 @@ +namespace HotChocolate.Types.Analyzers.Inspectors; + +public enum DataLoaderKind +{ + Batch, + Group, + Cache +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs new file mode 100644 index 00000000000..9ee512d4a7b --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/RegisterDataLoaderInfo.cs @@ -0,0 +1,39 @@ +namespace HotChocolate.Types.Analyzers.Inspectors; + +public sealed class RegisterDataLoaderInfo : ISyntaxInfo, IEquatable +{ + public RegisterDataLoaderInfo(string name) + { + Name = name; + } + + public string Name { get; } + + public bool Equals(RegisterDataLoaderInfo? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Name == other.Name; + } + + public override bool Equals(object? obj) + => ReferenceEquals(this, obj) || + obj is RegisterDataLoaderInfo other && Equals(other); + + public override int GetHashCode() + => Name.GetHashCode(); + + public static bool operator ==(RegisterDataLoaderInfo? left, RegisterDataLoaderInfo? right) + => Equals(left, right); + + public static bool operator !=(RegisterDataLoaderInfo? left, RegisterDataLoaderInfo? right) + => !Equals(left, right); +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs index dfd1705c6fd..b8877d621c4 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeAttributeInspector.cs @@ -7,7 +7,7 @@ namespace HotChocolate.Types.Analyzers.Inspectors; -public class TypeAttributeInspector : ISyntaxInspector +public sealed class TypeAttributeInspector : ISyntaxInspector { public bool TryHandle( GeneratorSyntaxContext context, diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs index fded3681fa1..7a05e924503 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Inspectors/TypeExtensionInfo.cs @@ -11,7 +11,7 @@ public TypeExtensionInfo(string name, bool isStatic, OperationType type = Operat public string Name { get; } - public bool IsStatic { get; } + public bool IsStatic { get; } public OperationType Type { get; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs new file mode 100644 index 00000000000..41f0ab8bacf --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.Designer.cs @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace HotChocolate.Types.Analyzers.Properties { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SourceGenResources { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SourceGenResources() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.Types.Analyzers.Properties.SourceGenResources", typeof(SourceGenResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string DataLoader_KeyParameterMissing { + get { + return ResourceManager.GetString("DataLoader_KeyParameterMissing", resourceCulture); + } + } + } +} diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx new file mode 100644 index 00000000000..60a1cf91a6c --- /dev/null +++ b/src/HotChocolate/Core/src/Types.Analyzers/Properties/SourceGenResources.resx @@ -0,0 +1,24 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A DataLoader must at least provides a key or keys parameter. + + diff --git a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs b/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs index baf0dc57c4c..9dfa75e49d5 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/TypeModuleGenerator.cs @@ -14,15 +14,19 @@ public class TypeModuleGenerator : IIncrementalGenerator new TypeAttributeInspector(), new ClassBaseClassInspector(), new ModuleInspector(), + new DataLoaderInspector() }; private static readonly ISyntaxGenerator[] _generators = { new ModuleGenerator(), + new DataLoaderGenerator() }; public void Initialize(IncrementalGeneratorInitializationContext context) { + context.RegisterPostInitializationOutput(c => PostInitialization(c)); + IncrementalValuesProvider modulesAndTypes = context.SyntaxProvider .CreateSyntaxProvider( @@ -37,10 +41,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (context, source) => Execute(context, source.Left, source.Right)); } + private static void PostInitialization(IncrementalGeneratorPostInitializationContext context) + { + foreach (var syntaxGenerator in _generators) + { + syntaxGenerator.Initialize(context); + } + } + private static bool IsRelevant(SyntaxNode node) => IsTypeWithAttribute(node) || IsClassWithBaseClass(node) || - IsAssemblyAttributeList(node); + IsAssemblyAttributeList(node) || + IsMethodWithAttribute(node); private static bool IsClassWithBaseClass(SyntaxNode node) => node is ClassDeclarationSyntax { BaseList.Types.Count: > 0 }; @@ -48,6 +61,9 @@ private static bool IsClassWithBaseClass(SyntaxNode node) private static bool IsTypeWithAttribute(SyntaxNode node) => node is BaseTypeDeclarationSyntax { AttributeLists.Count: > 0 }; + private static bool IsMethodWithAttribute(SyntaxNode node) + => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }; + private static bool IsAssemblyAttributeList(SyntaxNode node) => node is AttributeListSyntax; @@ -72,13 +88,12 @@ private static bool IsAssemblyAttributeList(SyntaxNode node) ImmutableArray syntaxInfos) { var all = syntaxInfos; - var current = all; var batch = new HashSet(); // unpack aggregates - for (var i = 0; i < current.Length; i++) + for (var i = 0; i < all.Length; i++) { - var syntaxInfo = current[i]; + var syntaxInfo = all[i]; if (syntaxInfo is AggregateInfo aggregate) { @@ -89,17 +104,13 @@ private static bool IsAssemblyAttributeList(SyntaxNode node) foreach (var syntaxGenerator in _generators) { - // capture the current list of infos - current = all; - // gather infos for current generator - for (var i = 0; i < current.Length; i++) + for (var i = 0; i < all.Length; i++) { - var syntaxInfo = current[i]; + var syntaxInfo = all[i]; if (syntaxGenerator.Consume(syntaxInfo)) { - all = all.Remove(syntaxInfo); batch.Add(syntaxInfo); } } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs index b12340635fe..2b9e8eb570c 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownAttributes.cs @@ -11,6 +11,7 @@ public static class WellKnownAttributes public const string QueryTypeAttribute = "HotChocolate.Types.QueryTypeAttribute"; public const string MutationTypeAttribute = "HotChocolate.Types.MutationTypeAttribute"; public const string SubscriptionTypeAttribute = "HotChocolate.Types.SubscriptionTypeAttribute"; + public const string DataLoaderAttribute = "HotChocolate.Fetching.DataLoaderAttribute"; public static HashSet TypeAttributes { get; } = new() diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs index be47a4ff55a..d782696a6aa 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownFileNames.cs @@ -3,4 +3,6 @@ namespace HotChocolate.Types.Analyzers; public static class WellKnownFileNames { public const string TypeModuleFile = "HotChocolateTypeModule.g.cs"; + public const string DataLoaderFile = "HotChocolateDataLoader.g.cs"; + public const string AttributesFile = "HotChocolateAttributes.g.cs"; } diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs index 991e7467977..91582653942 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WellKnownTypes.cs @@ -16,6 +16,11 @@ public static class WellKnownTypes public const string InputObjectTypeExtension = "HotChocolate.Types.InputObjectTypeExtension"; public const string EnumTypeExtension = "HotChocolate.Types.EnumTypeExtension"; public const string DataLoader = "GreenDonut.IDataLoader"; + public const string CancellationToken = "System.Threading.CancellationToken"; + public const string ReadOnlyList = "System.Collections.Generic.IReadOnlyList"; + public const string ReadOnlyDictionary = "System.Collections.Generic.IReadOnlyDictionary"; + public const string Lookup = "System.Linq.ILookup"; + public const string Task = "System.Threading.Tasks.Task"; public static HashSet TypeClass { get; } = new() diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/SubscribeAndResolveAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/SubscribeAndResolveAttribute.cs index 7424e63b48f..c55c4c9bc59 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/SubscribeAndResolveAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/SubscribeAndResolveAttribute.cs @@ -7,10 +7,8 @@ namespace HotChocolate.Types; -[AttributeUsage( - AttributeTargets.Property | AttributeTargets.Method, - Inherited = true, - AllowMultiple = false)] +[Obsolete("Use the SubscribeAttribute.")] +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] public sealed class SubscribeAndResolveAttribute : ObjectFieldDescriptorAttribute { 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 d6d62aa3b9e..a308f9fbc29 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/DataLoaderTests.cs @@ -12,7 +12,6 @@ using HotChocolate.Types.Relay; using Microsoft.Extensions.DependencyInjection; using Snapshooter.Xunit; -using Xunit; using static HotChocolate.Tests.TestHelper; #nullable enable @@ -207,61 +206,6 @@ public async Task ClassDataLoader() results.MatchSnapshot(); } - [Fact] - public async Task ClassDataLoaderWithKey() - { - // arrange - var executor = await CreateExecutorAsync(c => c - .AddQueryType() - .AddDataLoader() - .ModifyRequestOptions(o => o.IncludeExceptionDetails = true) - .UseRequest(next => async context => - { - await next(context); - - var dataLoader = - context.Services - .GetRequiredService() - .GetOrRegister("fooBar", () => throw new Exception()); - - context.Result = QueryResultBuilder - .FromResult(((IQueryResult)context.Result!)) - .AddExtension("loads", dataLoader.Loads) - .Create(); - }) - .UseDefaultPipeline()); - - // act - var results = new List - { - await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader2(key: ""a"") - b: withDataLoader2(key: ""b"") - }") - .Create()), - await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - a: withDataLoader2(key: ""a"") - }") - .Create()), - await executor.ExecuteAsync( - QueryRequestBuilder.New() - .SetQuery( - @"{ - c: withDataLoader2(key: ""c"") - }") - .Create()) - }; - - // assert - results.MatchSnapshot(); - } - [Fact] public async Task StackedDataLoader() { diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs index 26bd7976ee9..f1068b3dcf6 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/DataLoader/Query.cs @@ -9,7 +9,7 @@ public class Query public Task GetWithDataLoader( string key, FieldNode fieldSelection, - [DataLoader] TestDataLoader testDataLoader, + TestDataLoader testDataLoader, CancellationToken cancellationToken) { return testDataLoader.LoadAsync(key, cancellationToken); @@ -20,7 +20,7 @@ public class Query public async Task GetWithDataLoader2( string key, FieldNode fieldSelection, - [DataLoader("fooBar")] TestDataLoader testDataLoader, + TestDataLoader testDataLoader, CancellationToken cancellationToken) { return await testDataLoader.LoadAsync(key, cancellationToken); @@ -37,7 +37,7 @@ public class Query public async Task GetWithStackedDataLoader( string key, - [DataLoader("fooBar")] TestDataLoader testDataLoader, + TestDataLoader testDataLoader, CancellationToken cancellationToken) { var s = await testDataLoader.LoadAsync(key + "a", cancellationToken); @@ -59,7 +59,7 @@ public class Bar public Task GetWithDataLoader( string key, FieldNode fieldSelection, - [DataLoader] TestDataLoader testDataLoader, + TestDataLoader testDataLoader, CancellationToken cancellationToken) { return testDataLoader.LoadAsync(key, cancellationToken); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/TimeoutMiddlewareTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/TimeoutMiddlewareTests.cs index edfe0c01286..e57ec7858d3 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/TimeoutMiddlewareTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/TimeoutMiddlewareTests.cs @@ -61,6 +61,7 @@ public async Task Timeout(CancellationToken cancellationToken) } } +#pragma warning disable CS0618 public class Subscriptions { [SubscribeAndResolve] @@ -79,4 +80,5 @@ public class Subscriptions } } } -} \ No newline at end of file +#pragma warning restore CS0618 +} diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/PersonLastName.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/PersonLastName.cs index a4496ebeffa..249b74e1887 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/PersonLastName.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/PersonLastName.cs @@ -1,3 +1,5 @@ +using HotChocolate.Execution.Configuration; + namespace HotChocolate.Types; [ExtendObjectType] diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeQuery.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeQuery.cs index 1a2a90bc6d1..6e5332b0d25 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeQuery.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/SomeQuery.cs @@ -1,4 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; using System.Threading.Tasks; +using GreenDonut; +using HotChocolate.Fetching; +using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Types; @@ -11,6 +18,11 @@ public class SomeQuery public ValueTask GetEnum() => default; public Book GetBook() => new() { Title = "SomeTitle" }; + + public Task WithDataLoader(FoosByIdDataLoader foosById) + { + return foosById.LoadAsync("a"); + } } [MutationType] @@ -19,9 +31,43 @@ public static class SomeMutation public static string DoSomething() => "abc"; } - [SubscriptionType] public static class SomeSubscription { public static string OnSomething() => "abc"; } + +public static class DataLoaderGen +{ + [DataLoader(Scoped = true)] + public static async Task> GetFoosById( + IReadOnlyList ids, + SomeService someService, + CancellationToken cancellationToken) + { + return ids.ToDictionary(t => t, t => t); + } + + [DataLoader(Scoped = true)] + public static async Task GetFoosById2( + string id, + SomeService someService, + CancellationToken cancellationToken) + { + return "abc"; + } + + [DataLoader(Scoped = true)] + public static async Task> GetFoosById3( + IReadOnlyList ids, + SomeService someService, + CancellationToken cancellationToken) + { + return default; + } +} + +public class SomeService { } + + + diff --git a/src/HotChocolate/Data/src/EntityFramework/Extensions/ResolverCompilerBuilderExtensions.cs b/src/HotChocolate/Data/src/EntityFramework/Extensions/ResolverCompilerBuilderExtensions.cs index 4acc173b38c..57b8a68435e 100644 --- a/src/HotChocolate/Data/src/EntityFramework/Extensions/ResolverCompilerBuilderExtensions.cs +++ b/src/HotChocolate/Data/src/EntityFramework/Extensions/ResolverCompilerBuilderExtensions.cs @@ -30,7 +30,7 @@ public static class EntityFrameworkRequestExecutorBuilderExtensions /// public static IRequestExecutorBuilder RegisterDbContext( this IRequestExecutorBuilder builder, - DbContextKind kind = DbContextKind.Synchronized) + DbContextKind kind = DbContextKind.Resolver) where TDbContext : DbContext { builder.Services.AddSingleton( diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs index 6697c25e2db..b05c42afb58 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/IntegrationTests.cs @@ -238,16 +238,16 @@ public async Task OffsetPagingExecutable() // assert var result = await executor.ExecuteAsync( @"query Test { - authorOffsetPagingExecutable { - items { - name - } - pageInfo { - hasNextPage - hasPreviousPage - } + authorOffsetPagingExecutable { + items { + name + } + pageInfo { + hasNextPage + hasPreviousPage } - }"); + } + }"); // assert result.MatchSnapshot(); diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/Query.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/Query.cs index b4c5b8fedeb..2878fc5acb7 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/Query.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/Query.cs @@ -1,3 +1,4 @@ +#pragma warning disable CS0618 using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,7 @@ public class Query { [UseDbContext(typeof(BookContext))] public IQueryable GetAuthors( + [ScopedService] BookContext context) => context.Authors; @@ -190,3 +192,4 @@ protected override void Configure(IObjectTypeDescriptor descriptor) }); } } +#pragma warning restore CS0618 diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/UseDbContextTests.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/UseDbContextTests.cs index c78c40f5f6b..f2cfad815a4 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/UseDbContextTests.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/UseDbContextTests.cs @@ -33,7 +33,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.SaveChangesAsync(); @@ -69,7 +69,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.SaveChangesAsync(); @@ -105,7 +105,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.SaveChangesAsync(); @@ -141,7 +141,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -190,7 +190,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -239,7 +239,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -288,7 +288,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -337,7 +337,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -385,7 +385,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -433,7 +433,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -481,7 +481,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.SaveChangesAsync(); @@ -647,7 +647,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -696,7 +696,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -745,7 +745,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -794,7 +794,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -843,7 +843,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -891,7 +891,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" }); @@ -939,7 +939,7 @@ await services.GetRequiredService() var contextFactory = services.GetRequiredService>(); - await using (var context = contextFactory.CreateDbContext()) + await using (var context = await contextFactory.CreateDbContextAsync()) { await context.Authors.AddAsync(new Author { Name = "foo" }); await context.Authors.AddAsync(new Author { Name = "bar" });