diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 4fe90ae..b53da50 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -3,8 +3,8 @@
true
-
-
+
+
diff --git a/src/GenericQueryable.EntityFramework/DbContextOptionsBuilderExtensions.cs b/src/GenericQueryable.EntityFramework/DbContextOptionsBuilderExtensions.cs
index 8685344..c095d7e 100644
--- a/src/GenericQueryable.EntityFramework/DbContextOptionsBuilderExtensions.cs
+++ b/src/GenericQueryable.EntityFramework/DbContextOptionsBuilderExtensions.cs
@@ -1,14 +1,16 @@
-using Microsoft.EntityFrameworkCore;
+using GenericQueryable.DependencyInjection;
+
+using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace GenericQueryable.EntityFramework;
public static class DbContextOptionsBuilderExtensions
{
- public static DbContextOptionsBuilder UseGenericQueryable(this DbContextOptionsBuilder optionsBuilder)
+ public static DbContextOptionsBuilder UseGenericQueryable(this DbContextOptionsBuilder optionsBuilder, Action? setupAction = null)
{
var extension = optionsBuilder.Options.FindExtension()
- ?? new GenericQueryableOptionsExtension();
+ ?? new GenericQueryableOptionsExtension(setupAction);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
diff --git a/src/GenericQueryable.EntityFramework/EfFetchService.cs b/src/GenericQueryable.EntityFramework/EfFetchService.cs
index 0242584..761bd02 100644
--- a/src/GenericQueryable.EntityFramework/EfFetchService.cs
+++ b/src/GenericQueryable.EntityFramework/EfFetchService.cs
@@ -10,12 +10,14 @@
namespace GenericQueryable.EntityFramework;
-public class EfFetchService : IFetchService
+public class EfFetchService(IEnumerable expanders) : IFetchService
{
public virtual IQueryable ApplyFetch(IQueryable source, FetchRule fetchRule)
where TSource : class
- {
- return fetchRule switch
+ {
+ var expandedFetchRule = expanders.Aggregate(fetchRule, (state, expander) => expander.TryExpand(state) ?? state);
+
+ return expandedFetchRule switch
{
UntypedFetchRule untypedFetchRule => source.Include(untypedFetchRule.Path),
diff --git a/src/GenericQueryable.EntityFramework/GenericQueryableOptionsExtension.cs b/src/GenericQueryable.EntityFramework/GenericQueryableOptionsExtension.cs
index 0dce99f..139e3ec 100644
--- a/src/GenericQueryable.EntityFramework/GenericQueryableOptionsExtension.cs
+++ b/src/GenericQueryable.EntityFramework/GenericQueryableOptionsExtension.cs
@@ -10,16 +10,24 @@ namespace GenericQueryable.EntityFramework;
public class GenericQueryableOptionsExtension : IDbContextOptionsExtension
{
- public GenericQueryableOptionsExtension()
- {
- this.Info = new ExtensionInfo(this);
- }
+ private readonly Action? setupAction;
+
+ public GenericQueryableOptionsExtension(Action? setupAction)
+ {
+ this.setupAction = setupAction;
+ this.Info = new ExtensionInfo(this);
+ }
public DbContextOptionsExtensionInfo Info { get; }
public void ApplyServices(IServiceCollection services)
{
- services.AddGenericQueryable(v => v.SetFetchService().SetTargetMethodExtractor());
+ services.AddGenericQueryable(v =>
+ {
+ v.SetFetchService().SetTargetMethodExtractor();
+
+ setupAction?.Invoke(v);
+ });
services.ReplaceScoped();
}
diff --git a/src/GenericQueryable.IntegrationTests/AppFetchRule.cs b/src/GenericQueryable.IntegrationTests/AppFetchRule.cs
new file mode 100644
index 0000000..cd20b04
--- /dev/null
+++ b/src/GenericQueryable.IntegrationTests/AppFetchRule.cs
@@ -0,0 +1,9 @@
+using GenericQueryable.Fetching;
+using GenericQueryable.IntegrationTests.Domain;
+
+namespace GenericQueryable.IntegrationTests;
+
+public static class AppFetchRule
+{
+ public static FetchRule TestFetchRule { get; } = new FetchRuleHeader(nameof(TestFetchRule));
+}
\ No newline at end of file
diff --git a/src/GenericQueryable.IntegrationTests/MainTests.cs b/src/GenericQueryable.IntegrationTests/MainTests.cs
index add4fc8..c803727 100644
--- a/src/GenericQueryable.IntegrationTests/MainTests.cs
+++ b/src/GenericQueryable.IntegrationTests/MainTests.cs
@@ -1,6 +1,7 @@
using CommonFramework.DependencyInjection;
using GenericQueryable.EntityFramework;
+using GenericQueryable.Fetching;
using GenericQueryable.IntegrationTests.Domain;
using Microsoft.EntityFrameworkCore;
@@ -10,61 +11,62 @@ namespace GenericQueryable.IntegrationTests;
public class MainTests
{
- private readonly CancellationToken ct = TestContext.Current.CancellationToken;
-
- [Fact]
- public async Task DefaultGenericQueryable_InvokeToListAsync_MethodInvoked()
- {
- // Arrange
- var sp = new ServiceCollection()
- .AddDbContext(optionsBuilder => optionsBuilder
- .UseSqlite("Data Source=test.db")
- .UseGenericQueryable(),
- contextLifetime: ServiceLifetime.Singleton,
- optionsLifetime: ServiceLifetime.Singleton)
+ private readonly CancellationToken ct = TestContext.Current.CancellationToken;
+
+ [Fact]
+ public async Task DefaultGenericQueryable_InvokeToListAsync_MethodInvoked()
+ {
+ // Arrange
+ var sp = new ServiceCollection()
+ .AddDbContext(optionsBuilder => optionsBuilder
+ .UseSqlite("Data Source=test.db")
+ .UseGenericQueryable(b =>
+ b.AddFetchRule(AppFetchRule.TestFetchRule, FetchRule.Create(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))),
+ contextLifetime: ServiceLifetime.Singleton,
+ optionsLifetime: ServiceLifetime.Singleton)
.AddValidator()
.Validate()
- .BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true });
+ .BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true });
- var dbContext = sp.GetRequiredService();
+ var dbContext = sp.GetRequiredService();
- await dbContext.Database.EnsureDeletedAsync(ct);
- await dbContext.Database.EnsureCreatedAsync(ct);
+ await dbContext.Database.EnsureDeletedAsync(ct);
+ await dbContext.Database.EnsureCreatedAsync(ct);
- var testSet = dbContext.Set();
+ var testSet = dbContext.Set();
- var fetchObj = new FetchObject();
+ var fetchObj = new FetchObject();
- await dbContext.Set().AddAsync(fetchObj, ct);
+ await dbContext.Set().AddAsync(fetchObj, ct);
- var testObj = new TestObject { Id = Guid.NewGuid() };
+ var testObj = new TestObject { Id = Guid.NewGuid() };
- await testSet.AddAsync(testObj, ct);
+ await testSet.AddAsync(testObj, ct);
- await dbContext.SaveChangesAsync(ct);
+ await dbContext.SaveChangesAsync(ct);
- // Act
- var result0 = await testSet
- .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
- .GenericToArrayAsync(cancellationToken: ct);
+ // Act
+ var result0 = await testSet
+ .WithFetch(AppFetchRule.TestFetchRule)
+ .GenericToArrayAsync(cancellationToken: ct);
- var result1 = await testSet
- .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
- .GenericToListAsync(cancellationToken: ct);
+ var result1 = await testSet
+ .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
+ .GenericToListAsync(cancellationToken: ct);
- var result2 = await testSet
- .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
- .GenericToHashSetAsync(cancellationToken: ct);
+ var result2 = await testSet
+ .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
+ .GenericToHashSetAsync(cancellationToken: ct);
- var result3 = await testSet
- .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
- .GenericToDictionaryAsync(v => v.Id, cancellationToken: ct);
+ var result3 = await testSet
+ .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
+ .GenericToDictionaryAsync(v => v.Id, cancellationToken: ct);
- var result4 = await testSet
- .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
- .GenericToDictionaryAsync(v => v.Id, v => v, cancellationToken: ct);
+ var result4 = await testSet
+ .WithFetch(r => r.Fetch(v => v.DeepFetchObjects).ThenFetch(v => v.FetchObject))
+ .GenericToDictionaryAsync(v => v.Id, v => v, cancellationToken: ct);
- //Assert
- result0.Should().Contain(testObj);
- }
+ //Assert
+ result0.Should().Contain(testObj);
+ }
}
\ No newline at end of file
diff --git a/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs b/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs
index d045c62..6b5bf48 100644
--- a/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs
+++ b/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs
@@ -7,32 +7,61 @@ namespace GenericQueryable.DependencyInjection;
public class GenericQueryableSetup : IGenericQueryableSetup
{
- private Type targetMethodExtractorType = typeof(SyncTargetMethodExtractor);
+ private Type targetMethodExtractorType = typeof(SyncTargetMethodExtractor);
- private Type fetchServiceType = typeof(IgnoreFetchService);
+ private Type fetchServiceType = typeof(IgnoreFetchService);
- public void Initialize(IServiceCollection services)
- {
- services.AddSingleton();
- services.AddSingleton();
+ private readonly List fetchRuleExpanderTypeList = [typeof(FetchRuleHeaderExpander)];
- services.AddSingleton(typeof(IFetchService), this.fetchServiceType);
- services.AddSingleton(typeof(ITargetMethodExtractor), this.targetMethodExtractorType);
- }
+ private readonly List fetchRuleHeaderInfoList = new();
- public IGenericQueryableSetup SetFetchService()
+ public void Initialize(IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton(typeof(IFetchService), this.fetchServiceType);
+ services.AddSingleton(typeof(ITargetMethodExtractor), this.targetMethodExtractorType);
+
+ foreach (var fetchRuleExpanderType in this.fetchRuleExpanderTypeList)
+ {
+ services.AddSingleton(typeof(IFetchRuleExpander), fetchRuleExpanderType);
+ }
+
+ foreach (var fetchRuleHeaderInfo in this.fetchRuleHeaderInfoList)
+ {
+ services.AddSingleton(fetchRuleHeaderInfo);
+ }
+ }
+
+ public IGenericQueryableSetup SetFetchService()
where TFetchService : IFetchService
- {
- this.fetchServiceType = typeof(TFetchService);
+ {
+ this.fetchServiceType = typeof(TFetchService);
- return this;
- }
+ return this;
+ }
- public IGenericQueryableSetup SetTargetMethodExtractor()
+ public IGenericQueryableSetup AddFetchRule(FetchRule header, FetchRule implementation)
+ {
+ this.fetchRuleHeaderInfoList.Add(new FetchRuleHeaderInfo(header, implementation));
+
+ return this;
+ }
+
+ public IGenericQueryableSetup SetTargetMethodExtractor()
where TTargetMethodExtractor : ITargetMethodExtractor
- {
- this.targetMethodExtractorType = typeof(TTargetMethodExtractor);
+ {
+ this.targetMethodExtractorType = typeof(TTargetMethodExtractor);
+
+ return this;
+ }
+
+ public IGenericQueryableSetup AddFetchRuleExpander()
+ where TFetchRuleExpander : IFetchRuleExpander
+ {
+ this.fetchRuleExpanderTypeList.Add(typeof(TFetchRuleExpander));
- return this;
- }
+ return this;
+ }
}
\ No newline at end of file
diff --git a/src/GenericQueryable/DependencyInjection/IGenericQueryableSetup.cs b/src/GenericQueryable/DependencyInjection/IGenericQueryableSetup.cs
index a8a4e36..cb834d4 100644
--- a/src/GenericQueryable/DependencyInjection/IGenericQueryableSetup.cs
+++ b/src/GenericQueryable/DependencyInjection/IGenericQueryableSetup.cs
@@ -8,6 +8,11 @@ public interface IGenericQueryableSetup
IGenericQueryableSetup SetFetchService()
where TFetchService : IFetchService;
- IGenericQueryableSetup SetTargetMethodExtractor()
+ IGenericQueryableSetup AddFetchRuleExpander()
+ where TFetchRuleExpander : IFetchRuleExpander;
+
+ IGenericQueryableSetup AddFetchRule(FetchRule header, FetchRule implementation);
+
+ IGenericQueryableSetup SetTargetMethodExtractor()
where TTargetMethodExtractor : ITargetMethodExtractor;
}
\ No newline at end of file
diff --git a/src/GenericQueryable/Fetching/FetchRuleHeader.cs b/src/GenericQueryable/Fetching/FetchRuleHeader.cs
new file mode 100644
index 0000000..f890f6b
--- /dev/null
+++ b/src/GenericQueryable/Fetching/FetchRuleHeader.cs
@@ -0,0 +1,3 @@
+namespace GenericQueryable.Fetching;
+
+public record FetchRuleHeader(string Name) : FetchRule;
\ No newline at end of file
diff --git a/src/GenericQueryable/Fetching/FetchRuleHeaderExpander.cs b/src/GenericQueryable/Fetching/FetchRuleHeaderExpander.cs
new file mode 100644
index 0000000..07efc28
--- /dev/null
+++ b/src/GenericQueryable/Fetching/FetchRuleHeaderExpander.cs
@@ -0,0 +1,22 @@
+using System.Collections.Concurrent;
+
+using CommonFramework;
+
+namespace GenericQueryable.Fetching;
+
+public class FetchRuleHeaderExpander(IEnumerable fetchRuleHeaderInfoList) : IFetchRuleExpander
+{
+ private readonly IReadOnlyDictionary> headersDict =
+ fetchRuleHeaderInfoList.GroupBy(v => v.SourceType).ToDictionary(g => g.Key, IReadOnlyList (g) => g.ToList());
+
+ private readonly ConcurrentDictionary cache = new();
+
+ public FetchRule? TryExpand(FetchRule fetchRule)
+ {
+ return cache.GetOrAdd(typeof(TSource),
+ _ => headersDict[typeof(TSource)].Cast>()
+ .ToDictionary(info => info.Header, info => info.Implementation))
+ .Pipe(innerCache => (IReadOnlyDictionary, FetchRule>)innerCache)
+ .Pipe(innerCache => innerCache.GetValueOrDefault(fetchRule));
+ }
+}
\ No newline at end of file
diff --git a/src/GenericQueryable/Fetching/FetchRuleHeaderInfo.cs b/src/GenericQueryable/Fetching/FetchRuleHeaderInfo.cs
new file mode 100644
index 0000000..e63b805
--- /dev/null
+++ b/src/GenericQueryable/Fetching/FetchRuleHeaderInfo.cs
@@ -0,0 +1,11 @@
+namespace GenericQueryable.Fetching;
+
+public abstract record FetchRuleHeaderInfo
+{
+ public abstract Type SourceType { get; }
+}
+
+public record FetchRuleHeaderInfo(FetchRule Header, FetchRule Implementation) : FetchRuleHeaderInfo
+{
+ public override Type SourceType { get; } = typeof(TSource);
+}
\ No newline at end of file
diff --git a/src/GenericQueryable/Fetching/IFetchRuleExpander.cs b/src/GenericQueryable/Fetching/IFetchRuleExpander.cs
new file mode 100644
index 0000000..db85e27
--- /dev/null
+++ b/src/GenericQueryable/Fetching/IFetchRuleExpander.cs
@@ -0,0 +1,6 @@
+namespace GenericQueryable.Fetching;
+
+public interface IFetchRuleExpander
+{
+ FetchRule? TryExpand(FetchRule fetchRule);
+}
\ No newline at end of file
diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs
index d4bbf69..baee29e 100644
--- a/src/__SolutionItems/CommonAssemblyInfo.cs
+++ b/src/__SolutionItems/CommonAssemblyInfo.cs
@@ -3,7 +3,7 @@
[assembly: AssemblyProduct("GenericQueryable")]
[assembly: AssemblyCompany("IvAt")]
-[assembly: AssemblyVersion("2.0.3.0")]
+[assembly: AssemblyVersion("2.0.4.0")]
[assembly: AssemblyInformationalVersion("changes at build")]
#if DEBUG