diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5003ca7..dc0bc13 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -3,8 +3,8 @@ true - - + + diff --git a/src/GenericQueryable.EntityFramework/EfFetchService.cs b/src/GenericQueryable.EntityFramework/EfFetchService.cs index 3787578..cbf435e 100644 --- a/src/GenericQueryable.EntityFramework/EfFetchService.cs +++ b/src/GenericQueryable.EntityFramework/EfFetchService.cs @@ -1,9 +1,8 @@ -using System.Collections.Concurrent; -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Reflection; using CommonFramework; -using CommonFramework.ExpressionEvaluate; + using GenericQueryable.Fetching; using Microsoft.EntityFrameworkCore; @@ -12,67 +11,28 @@ namespace GenericQueryable.EntityFramework; -public class EfFetchService([FromKeyedServices(RootFetchRuleExpander.Key)] IFetchRuleExpander fetchRuleExpander) : IFetchService +public class EfFetchService([FromKeyedServices(RootFetchRuleExpander.Key)] IFetchRuleExpander fetchRuleExpander) : FetchService(fetchRuleExpander) { - private readonly ConcurrentDictionary> rootCache = []; - - public virtual IQueryable ApplyFetch(IQueryable source, FetchRule fetchRule) + public override IQueryable ApplyFetch(IQueryable source, FetchRule fetchRule) where TSource : class { - return fetchRule switch + if (fetchRule is UntypedFetchRule untypedFetchRule) { - UntypedFetchRule untypedFetchRule => source.Include(untypedFetchRule.Path), - - _ => this.GetApplyFetchFunc(fetchRule).Invoke(source) - }; - } - - private Func, IQueryable> GetApplyFetchFunc(FetchRule fetchRule) - where TSource : class - { - return this.rootCache - .GetOrAdd(typeof(TSource), _ => new ConcurrentDictionary()) - .GetOrAdd(fetchRule.GetType(), _ => new ConcurrentDictionary, Func, IQueryable>>()) - .Pipe(v => (ConcurrentDictionary, Func, IQueryable>>)v) - .GetOrAdd(fetchRule, _ => - { - var fetchExpr = this.GetApplyFetchExpression(fetchRuleExpander.Expand(fetchRule)); - - return fetchExpr.Compile(); - }); - } - - private Expression, IQueryable>> GetApplyFetchExpression(PropertyFetchRule fetchRule) - where TSource : class - { - var startState = ExpressionHelper.GetIdentity>(); - - return fetchRule.Paths.Aggregate(startState, (state, path) => + return source.Include(untypedFetchRule.Path); + } + else { - var nextApplyFunc = GetApplyFetchExpression(path); - - return ExpressionEvaluateHelper.InlineEvaluate, IQueryable>>(ee => - - q => ee.Evaluate(nextApplyFunc, ee.Evaluate(state, q))); - }); + return base.ApplyFetch(source, fetchRule); + } } - private static Expression, IQueryable>> GetApplyFetchExpression(LambdaExpressionPath fetchPath) + protected override IEnumerable GetFetchMethods(LambdaExpressionPath fetchPath) where TSource : class { - LambdaExpression startState = ExpressionHelper.GetIdentity>(); - - var resultBody = fetchPath + return fetchPath .Properties .ZipStrong(new LambdaExpression?[] { null }.Concat(fetchPath.Properties.SkipLast(1)), (prop, prevProp) => new { prop, prevProp }) - .Aggregate(startState.Body, (state, pair) => - { - var fetchMethod = GetFetchMethod(pair.prop, pair.prevProp); - - return Expression.Call(fetchMethod, state, pair.prop); - }); - - return Expression.Lambda, IQueryable>>(resultBody, startState.Parameters); + .Select(pair => GetFetchMethod(pair.prop, pair.prevProp)); } private static MethodInfo GetFetchMethod(LambdaExpression prop, LambdaExpression? prevProp) diff --git a/src/GenericQueryable.Runtime/Fetching/FetchService.cs b/src/GenericQueryable.Runtime/Fetching/FetchService.cs new file mode 100644 index 0000000..38d1be3 --- /dev/null +++ b/src/GenericQueryable.Runtime/Fetching/FetchService.cs @@ -0,0 +1,63 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +using CommonFramework; +using CommonFramework.ExpressionEvaluate; + +using Microsoft.Extensions.DependencyInjection; + +namespace GenericQueryable.Fetching; + +public abstract class FetchService([FromKeyedServices(RootFetchRuleExpander.Key)] IFetchRuleExpander fetchRuleExpander) : IFetchService +{ + private readonly ConcurrentDictionary> rootCache = []; + + public virtual IQueryable ApplyFetch(IQueryable source, FetchRule fetchRule) + where TSource : class => this.GetApplyFetchFunc(fetchRule).Invoke(source); + + private Func, IQueryable> GetApplyFetchFunc(FetchRule fetchRule) + where TSource : class + { + return this.rootCache + .GetOrAdd(typeof(TSource), _ => new ConcurrentDictionary()) + .GetOrAdd(fetchRule.GetType(), _ => new ConcurrentDictionary, Func, IQueryable>>()) + .Pipe(v => (ConcurrentDictionary, Func, IQueryable>>)v) + .GetOrAdd(fetchRule, _ => + { + var fetchExpr = this.GetApplyFetchExpression(fetchRuleExpander.Expand(fetchRule)); + + return fetchExpr.Compile(); + }); + } + + private Expression, IQueryable>> GetApplyFetchExpression(PropertyFetchRule fetchRule) + where TSource : class + { + var startState = ExpressionHelper.GetIdentity>(); + + return fetchRule.Paths.Aggregate(startState, (state, path) => + { + var nextApplyFunc = this.GetApplyFetchExpression(path); + + return ExpressionEvaluateHelper.InlineEvaluate, IQueryable>>(ee => + + q => ee.Evaluate(nextApplyFunc, ee.Evaluate(state, q))); + }); + } + + private Expression, IQueryable>> GetApplyFetchExpression(LambdaExpressionPath fetchPath) + where TSource : class + { + LambdaExpression startState = ExpressionHelper.GetIdentity>(); + + var resultBody = this + .GetFetchMethods(fetchPath).ZipStrong(fetchPath.Properties, (method, prop) => new { method, prop }) + .Aggregate(startState.Body, (state, pair) => Expression.Call(pair.method, state, pair.prop)); + + return Expression.Lambda, IQueryable>>(resultBody, startState.Parameters); + } + + protected abstract IEnumerable GetFetchMethods(LambdaExpressionPath fetchPath) + where TSource : class; +} \ No newline at end of file diff --git a/src/GenericQueryable.Runtime/Fetching/UntypedFetchExpander.cs b/src/GenericQueryable.Runtime/Fetching/UntypedFetchExpander.cs new file mode 100644 index 0000000..8320e81 --- /dev/null +++ b/src/GenericQueryable.Runtime/Fetching/UntypedFetchExpander.cs @@ -0,0 +1,29 @@ +using System.Collections.Concurrent; + +using CommonFramework; + +namespace GenericQueryable.Fetching; + +public class UntypedFetchExpander : IFetchRuleExpander +{ + private readonly ConcurrentDictionary cache = new(); + + public PropertyFetchRule? TryExpand(FetchRule fetchRule) + { + if (fetchRule is UntypedFetchRule untypedFetchRule) + { + return this.cache.GetOrAdd(typeof(TSource), _ => new ConcurrentDictionary, PropertyFetchRule>()) + .Pipe(innerCache => (ConcurrentDictionary, PropertyFetchRule>)innerCache) + .Pipe(innerCache => innerCache.GetOrAdd( + untypedFetchRule, + _ => + { + var fetchPath = LambdaExpressionPath.Create(typeof(TSource), untypedFetchRule.Path.Split('.')); + + return new PropertyFetchRule([fetchPath]); + })); + } + + return null; + } +} \ No newline at end of file diff --git a/src/GenericQueryable.Runtime/GenericQueryable.Runtime.csproj b/src/GenericQueryable.Runtime/GenericQueryable.Runtime.csproj index 6b15324..3ee59ac 100644 --- a/src/GenericQueryable.Runtime/GenericQueryable.Runtime.csproj +++ b/src/GenericQueryable.Runtime/GenericQueryable.Runtime.csproj @@ -5,6 +5,10 @@ GenericQueryable + + + + diff --git a/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs b/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs index dc00a54..a0373e3 100644 --- a/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs +++ b/src/GenericQueryable/DependencyInjection/GenericQueryableSetup.cs @@ -26,6 +26,8 @@ public void Initialize(IServiceCollection services) services.TryAddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddKeyedSingleton(RootFetchRuleExpander.Key); } diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs index 1df93de..4fdb881 100644 --- a/src/__SolutionItems/CommonAssemblyInfo.cs +++ b/src/__SolutionItems/CommonAssemblyInfo.cs @@ -3,7 +3,7 @@ [assembly: AssemblyProduct("GenericQueryable")] [assembly: AssemblyCompany("IvAt")] -[assembly: AssemblyVersion("2.1.11.0")] +[assembly: AssemblyVersion("2.1.13.0")] [assembly: AssemblyInformationalVersion("changes at build")] #if DEBUG