diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index abc9b23..5003ca7 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -3,8 +3,8 @@
true
-
-
+
+
diff --git a/src/GenericQueryable.Abstractions/Fetching/FetchPath.cs b/src/GenericQueryable.Abstractions/Fetching/FetchPath.cs
deleted file mode 100644
index 195e738..0000000
--- a/src/GenericQueryable.Abstractions/Fetching/FetchPath.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-using System.Linq.Expressions;
-
-namespace GenericQueryable.Fetching;
-
-public record FetchPath(IReadOnlyList Properties);
\ No newline at end of file
diff --git a/src/GenericQueryable.Abstractions/Fetching/FetchRule.cs b/src/GenericQueryable.Abstractions/Fetching/FetchRule.cs
index 96f8a31..20de869 100644
--- a/src/GenericQueryable.Abstractions/Fetching/FetchRule.cs
+++ b/src/GenericQueryable.Abstractions/Fetching/FetchRule.cs
@@ -1,5 +1,7 @@
using System.Linq.Expressions;
+using CommonFramework;
+
namespace GenericQueryable.Fetching;
public abstract record FetchRule
@@ -11,8 +13,8 @@ public static FetchRule Create(string path)
public static PropertyFetchRule Create(Expression> prop)
{
- return new PropertyFetchRule([new FetchPath([prop])]);
+ return new PropertyFetchRule([new LambdaExpressionPath([prop])]);
}
- public static PropertyFetchRule Empty { get; } = new ([]);
+ public static PropertyFetchRule Empty { get; } = new([]);
}
\ No newline at end of file
diff --git a/src/GenericQueryable.Abstractions/Fetching/IPropertyFetchRule.cs b/src/GenericQueryable.Abstractions/Fetching/IPropertyFetchRule.cs
index 14e5984..9771e8e 100644
--- a/src/GenericQueryable.Abstractions/Fetching/IPropertyFetchRule.cs
+++ b/src/GenericQueryable.Abstractions/Fetching/IPropertyFetchRule.cs
@@ -1,8 +1,10 @@
-namespace GenericQueryable.Fetching;
+using CommonFramework;
+
+namespace GenericQueryable.Fetching;
public interface IPropertyFetchRule : IPropertyFetchRule;
public interface IPropertyFetchRule
{
- IReadOnlyList Paths { get; }
+ DeepEqualsCollection Paths { get; }
}
\ No newline at end of file
diff --git a/src/GenericQueryable.Abstractions/Fetching/PropertyFetchRule.cs b/src/GenericQueryable.Abstractions/Fetching/PropertyFetchRule.cs
index 2395d5c..9d342b2 100644
--- a/src/GenericQueryable.Abstractions/Fetching/PropertyFetchRule.cs
+++ b/src/GenericQueryable.Abstractions/Fetching/PropertyFetchRule.cs
@@ -1,13 +1,27 @@
using System.Linq.Expressions;
+using CommonFramework;
+
namespace GenericQueryable.Fetching;
-public record PropertyFetchRule(IReadOnlyList Paths) : FetchRule, IPropertyFetchRule
+public record PropertyFetchRule(DeepEqualsCollection Paths) : FetchRule, IPropertyFetchRule
{
+ public PropertyFetchRule(IEnumerable paths) :
+ this(DeepEqualsCollection.Create(paths))
+ {
+ }
+
public PropertyFetchRule Fetch(Expression> path)
{
- return new PropertyFetchRule(this.Paths.Concat([new FetchPath([path])]).ToList());
+ return new PropertyFetchRule(this.Paths.Concat([new LambdaExpressionPath([path])]));
}
}
-public record PropertyFetchRule(IReadOnlyList Paths) : PropertyFetchRule(Paths), IPropertyFetchRule;
\ No newline at end of file
+public record PropertyFetchRule(DeepEqualsCollection Paths)
+ : PropertyFetchRule(Paths), IPropertyFetchRule
+{
+ public PropertyFetchRule(IEnumerable paths) :
+ this(DeepEqualsCollection.Create(paths))
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/GenericQueryable.Abstractions/PropertyFetchRuleExtensions.cs b/src/GenericQueryable.Abstractions/PropertyFetchRuleExtensions.cs
index ec3646a..caa1cf4 100644
--- a/src/GenericQueryable.Abstractions/PropertyFetchRuleExtensions.cs
+++ b/src/GenericQueryable.Abstractions/PropertyFetchRuleExtensions.cs
@@ -2,6 +2,8 @@
using System.Linq.Expressions;
+using CommonFramework;
+
namespace GenericQueryable;
public static class PropertyFetchRuleExtensions
@@ -29,7 +31,7 @@ private static PropertyFetchRule ThenFetchInternal(prevPaths.Concat([newLastPath]).ToList());
}
diff --git a/src/GenericQueryable.EntityFramework/EfFetchService.cs b/src/GenericQueryable.EntityFramework/EfFetchService.cs
index fcef9b9..3787578 100644
--- a/src/GenericQueryable.EntityFramework/EfFetchService.cs
+++ b/src/GenericQueryable.EntityFramework/EfFetchService.cs
@@ -1,8 +1,9 @@
-using System.Linq.Expressions;
+using System.Collections.Concurrent;
+using System.Linq.Expressions;
using System.Reflection;
using CommonFramework;
-
+using CommonFramework.ExpressionEvaluate;
using GenericQueryable.Fetching;
using Microsoft.EntityFrameworkCore;
@@ -13,48 +14,73 @@ namespace GenericQueryable.EntityFramework;
public class EfFetchService([FromKeyedServices(RootFetchRuleExpander.Key)] IFetchRuleExpander fetchRuleExpander) : IFetchService
{
+ private readonly ConcurrentDictionary> rootCache = [];
+
public virtual IQueryable ApplyFetch(IQueryable source, FetchRule fetchRule)
where TSource : class
{
- var expandedFetchRule = fetchRuleExpander.TryExpand(fetchRule) ?? fetchRule;
-
- return expandedFetchRule switch
+ return fetchRule switch
{
UntypedFetchRule untypedFetchRule => source.Include(untypedFetchRule.Path),
- PropertyFetchRule propertyFetchRule => this.ApplyFetch(source, propertyFetchRule),
-
- _ => throw new ArgumentOutOfRangeException(nameof(fetchRule))
+ _ => this.GetApplyFetchFunc(fetchRule).Invoke(source)
};
}
- protected IQueryable ApplyFetch(IQueryable source, PropertyFetchRule fetchRule)
- where TSource : class
- {
- return fetchRule.Paths.Aggregate(source, this.ApplyFetch);
- }
+ 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 IQueryable ApplyFetch(IQueryable source, FetchPath fetchPath)
- where TSource : class
- {
- return fetchPath
- .Properties
- .ZipStrong(new LambdaExpression?[] { null }.Concat(fetchPath.Properties.SkipLast(1)), (prop, prevProp) => new { prop, prevProp })
- .Aggregate(source, (q, pair) => this.ApplyFetch(q, pair.prop, pair.prevProp));
- }
+ private Expression, IQueryable>> GetApplyFetchExpression(PropertyFetchRule fetchRule)
+ where TSource : class
+ {
+ var startState = ExpressionHelper.GetIdentity>();
- private IQueryable ApplyFetch(IQueryable source, LambdaExpression prop, LambdaExpression? prevProp)
- where TSource : class
- {
- return this.GetFetchMethod(prop, prevProp).Invoke>(this, source, prop);
- }
+ return fetchRule.Paths.Aggregate(startState, (state, path) =>
+ {
+ var nextApplyFunc = GetApplyFetchExpression(path);
- private MethodInfo GetFetchMethod(LambdaExpression prop, LambdaExpression? prevProp)
+ return ExpressionEvaluateHelper.InlineEvaluate, IQueryable>>(ee =>
+
+ q => ee.Evaluate(nextApplyFunc, ee.Evaluate(state, q)));
+ });
+ }
+
+ private static Expression, IQueryable>> GetApplyFetchExpression(LambdaExpressionPath fetchPath)
+ where TSource : class
+ {
+ LambdaExpression startState = ExpressionHelper.GetIdentity>();
+
+ var resultBody = 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);
+ }
+
+ private static MethodInfo GetFetchMethod(LambdaExpression prop, LambdaExpression? prevProp)
where TSource : class
{
if (prevProp == null)
{
- return new Func, Expression>, IIncludableQueryable>(this.ApplyFetch)
+ return new Func, Expression>, IIncludableQueryable>(EntityFrameworkQueryableExtensions.Include)
.CreateGenericMethod(typeof(TSource), prop.Body.Type);
}
else
@@ -68,35 +94,15 @@ private MethodInfo GetFetchMethod(LambdaExpression prop, LambdaExpressi
if (prevPropRealType.IsGenericType && typeof(IEnumerable<>).MakeGenericType(prevElementType).IsAssignableFrom(prevPropRealType))
{
return new Func>, Expression>, IIncludableQueryable>(
- this.ApplyThenFetch)
+ EntityFrameworkQueryableExtensions.ThenInclude)
.CreateGenericMethod(typeof(TSource), prevElementType, nextPropertyType);
}
else
{
return new Func, Expression>, IIncludableQueryable>(
- this.ApplyThenFetch)
+ EntityFrameworkQueryableExtensions.ThenInclude)
.CreateGenericMethod(typeof(TSource), prevElementType, nextPropertyType);
}
}
}
-
- private IIncludableQueryable ApplyFetch(IQueryable source, Expression> prop)
- where TSource : class
- {
- return source.Include(prop);
- }
-
- private IIncludableQueryable ApplyThenFetch(
- IIncludableQueryable> source, Expression> prop)
- where TSource : class
- {
- return source.ThenInclude(prop);
- }
-
- private IIncludableQueryable ApplyThenFetch(
- IIncludableQueryable source, Expression> prop)
- where TSource : class
- {
- return source.ThenInclude(prop);
- }
}
\ No newline at end of file
diff --git a/src/GenericQueryable.Runtime/Fetching/FetchRuleExpanderExtensions.cs b/src/GenericQueryable.Runtime/Fetching/FetchRuleExpanderExtensions.cs
new file mode 100644
index 0000000..38b9e78
--- /dev/null
+++ b/src/GenericQueryable.Runtime/Fetching/FetchRuleExpanderExtensions.cs
@@ -0,0 +1,7 @@
+namespace GenericQueryable.Fetching;
+
+public static class FetchRuleExpanderExtensions
+{
+ public static PropertyFetchRule Expand(this IFetchRuleExpander fetchRuleExpander, FetchRule fetchRule) =>
+ fetchRuleExpander.TryExpand(fetchRule) ?? throw new ArgumentOutOfRangeException(nameof(fetchRule));
+}
\ No newline at end of file
diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs
index a3d7303..1df93de 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.9.0")]
+[assembly: AssemblyVersion("2.1.11.0")]
[assembly: AssemblyInformationalVersion("changes at build")]
#if DEBUG