Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="IvAt.CommonFramework" Version="2.1.8" />
<PackageVersion Include="IvAt.CommonFramework.DependencyInjection" Version="2.1.8" />
<PackageVersion Include="IvAt.CommonFramework" Version="2.1.11" />
<PackageVersion Include="IvAt.CommonFramework.DependencyInjection" Version="2.1.11" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
Expand Down
5 changes: 0 additions & 5 deletions src/GenericQueryable.Abstractions/Fetching/FetchPath.cs

This file was deleted.

6 changes: 4 additions & 2 deletions src/GenericQueryable.Abstractions/Fetching/FetchRule.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Linq.Expressions;

using CommonFramework;

namespace GenericQueryable.Fetching;

public abstract record FetchRule<TSource>
Expand All @@ -11,8 +13,8 @@ public static FetchRule<TSource> Create(string path)

public static PropertyFetchRule<TSource, TProperty> Create<TProperty>(Expression<Func<TSource, TProperty>> prop)
{
return new PropertyFetchRule<TSource, TProperty>([new FetchPath([prop])]);
return new PropertyFetchRule<TSource, TProperty>([new LambdaExpressionPath([prop])]);
}

public static PropertyFetchRule<TSource> Empty { get; } = new ([]);
public static PropertyFetchRule<TSource> Empty { get; } = new([]);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace GenericQueryable.Fetching;
using CommonFramework;

namespace GenericQueryable.Fetching;

public interface IPropertyFetchRule<TSource, out TLastProperty> : IPropertyFetchRule<TSource>;

public interface IPropertyFetchRule<TSource>
{
IReadOnlyList<FetchPath> Paths { get; }
DeepEqualsCollection<LambdaExpressionPath> Paths { get; }
}
20 changes: 17 additions & 3 deletions src/GenericQueryable.Abstractions/Fetching/PropertyFetchRule.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
using System.Linq.Expressions;

using CommonFramework;

namespace GenericQueryable.Fetching;

public record PropertyFetchRule<TSource>(IReadOnlyList<FetchPath> Paths) : FetchRule<TSource>, IPropertyFetchRule<TSource>
public record PropertyFetchRule<TSource>(DeepEqualsCollection<LambdaExpressionPath> Paths) : FetchRule<TSource>, IPropertyFetchRule<TSource>
{
public PropertyFetchRule(IEnumerable<LambdaExpressionPath> paths) :
this(DeepEqualsCollection.Create(paths))
{
}

public PropertyFetchRule<TSource, TNextProperty> Fetch<TNextProperty>(Expression<Func<TSource, TNextProperty>> path)
{
return new PropertyFetchRule<TSource, TNextProperty>(this.Paths.Concat([new FetchPath([path])]).ToList());
return new PropertyFetchRule<TSource, TNextProperty>(this.Paths.Concat([new LambdaExpressionPath([path])]));
}
}

public record PropertyFetchRule<TSource, TLastProperty>(IReadOnlyList<FetchPath> Paths) : PropertyFetchRule<TSource>(Paths), IPropertyFetchRule<TSource, TLastProperty>;
public record PropertyFetchRule<TSource, TLastProperty>(DeepEqualsCollection<LambdaExpressionPath> Paths)
: PropertyFetchRule<TSource>(Paths), IPropertyFetchRule<TSource, TLastProperty>
{
public PropertyFetchRule(IEnumerable<LambdaExpressionPath> paths) :
this(DeepEqualsCollection.Create(paths))
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using System.Linq.Expressions;

using CommonFramework;

namespace GenericQueryable;

public static class PropertyFetchRuleExtensions
Expand Down Expand Up @@ -29,7 +31,7 @@ private static PropertyFetchRule<TSource, TNextProperty> ThenFetchInternal<TSour

var lastPath = fetchRule.Paths.Last();

var newLastPath = new FetchPath(lastPath.Properties.Concat([prop]).ToList());
var newLastPath = new LambdaExpressionPath(lastPath.Properties.Concat([prop]).ToList());

return new PropertyFetchRule<TSource, TNextProperty>(prevPaths.Concat([newLastPath]).ToList());
}
Expand Down
106 changes: 56 additions & 50 deletions src/GenericQueryable.EntityFramework/EfFetchService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,48 +14,73 @@ namespace GenericQueryable.EntityFramework;

public class EfFetchService([FromKeyedServices(RootFetchRuleExpander.Key)] IFetchRuleExpander fetchRuleExpander) : IFetchService
{
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, object>> rootCache = [];

public virtual IQueryable<TSource> ApplyFetch<TSource>(IQueryable<TSource> source, FetchRule<TSource> fetchRule)
where TSource : class
{
var expandedFetchRule = fetchRuleExpander.TryExpand(fetchRule) ?? fetchRule;

return expandedFetchRule switch
return fetchRule switch
{
UntypedFetchRule<TSource> untypedFetchRule => source.Include(untypedFetchRule.Path),

PropertyFetchRule<TSource> propertyFetchRule => this.ApplyFetch(source, propertyFetchRule),

_ => throw new ArgumentOutOfRangeException(nameof(fetchRule))
_ => this.GetApplyFetchFunc(fetchRule).Invoke(source)
};
}

protected IQueryable<TSource> ApplyFetch<TSource>(IQueryable<TSource> source, PropertyFetchRule<TSource> fetchRule)
where TSource : class
{
return fetchRule.Paths.Aggregate(source, this.ApplyFetch);
}
private Func<IQueryable<TSource>, IQueryable<TSource>> GetApplyFetchFunc<TSource>(FetchRule<TSource> fetchRule)
where TSource : class
{
return this.rootCache
.GetOrAdd(typeof(TSource), _ => new ConcurrentDictionary<Type, object>())
.GetOrAdd(fetchRule.GetType(), _ => new ConcurrentDictionary<FetchRule<TSource>, Func<IQueryable<TSource>, IQueryable<TSource>>>())
.Pipe(v => (ConcurrentDictionary<FetchRule<TSource>, Func<IQueryable<TSource>, IQueryable<TSource>>>)v)
.GetOrAdd(fetchRule, _ =>
{
var fetchExpr = this.GetApplyFetchExpression(fetchRuleExpander.Expand(fetchRule));

return fetchExpr.Compile();
});
}

private IQueryable<TSource> ApplyFetch<TSource>(IQueryable<TSource> 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<Func<IQueryable<TSource>, IQueryable<TSource>>> GetApplyFetchExpression<TSource>(PropertyFetchRule<TSource> fetchRule)
where TSource : class
{
var startState = ExpressionHelper.GetIdentity<IQueryable<TSource>>();

private IQueryable<TSource> ApplyFetch<TSource>(IQueryable<TSource> source, LambdaExpression prop, LambdaExpression? prevProp)
where TSource : class
{
return this.GetFetchMethod<TSource>(prop, prevProp).Invoke<IQueryable<TSource>>(this, source, prop);
}
return fetchRule.Paths.Aggregate(startState, (state, path) =>
{
var nextApplyFunc = GetApplyFetchExpression<TSource>(path);

private MethodInfo GetFetchMethod<TSource>(LambdaExpression prop, LambdaExpression? prevProp)
return ExpressionEvaluateHelper.InlineEvaluate<Func<IQueryable<TSource>, IQueryable<TSource>>>(ee =>

q => ee.Evaluate(nextApplyFunc, ee.Evaluate(state, q)));
});
}

private static Expression<Func<IQueryable<TSource>, IQueryable<TSource>>> GetApplyFetchExpression<TSource>(LambdaExpressionPath fetchPath)
where TSource : class
{
LambdaExpression startState = ExpressionHelper.GetIdentity<IQueryable<TSource>>();

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<TSource>(pair.prop, pair.prevProp);

return Expression.Call(fetchMethod, state, pair.prop);
});

return Expression.Lambda<Func<IQueryable<TSource>, IQueryable<TSource>>>(resultBody, startState.Parameters);
}

private static MethodInfo GetFetchMethod<TSource>(LambdaExpression prop, LambdaExpression? prevProp)
where TSource : class
{
if (prevProp == null)
{
return new Func<IQueryable<TSource>, Expression<Func<TSource, Ignore>>, IIncludableQueryable<TSource, Ignore>>(this.ApplyFetch)
return new Func<IQueryable<TSource>, Expression<Func<TSource, Ignore>>, IIncludableQueryable<TSource, Ignore>>(EntityFrameworkQueryableExtensions.Include)
.CreateGenericMethod(typeof(TSource), prop.Body.Type);
}
else
Expand All @@ -68,35 +94,15 @@ private MethodInfo GetFetchMethod<TSource>(LambdaExpression prop, LambdaExpressi
if (prevPropRealType.IsGenericType && typeof(IEnumerable<>).MakeGenericType(prevElementType).IsAssignableFrom(prevPropRealType))
{
return new Func<IIncludableQueryable<TSource, IEnumerable<Ignore>>, Expression<Func<Ignore, Ignore>>, IIncludableQueryable<TSource, Ignore>>(
this.ApplyThenFetch)
EntityFrameworkQueryableExtensions.ThenInclude)
.CreateGenericMethod(typeof(TSource), prevElementType, nextPropertyType);
}
else
{
return new Func<IIncludableQueryable<TSource, Ignore>, Expression<Func<Ignore, Ignore>>, IIncludableQueryable<TSource, Ignore>>(
this.ApplyThenFetch)
EntityFrameworkQueryableExtensions.ThenInclude)
.CreateGenericMethod(typeof(TSource), prevElementType, nextPropertyType);
}
}
}

private IIncludableQueryable<TSource, TProperty> ApplyFetch<TSource, TProperty>(IQueryable<TSource> source, Expression<Func<TSource, TProperty>> prop)
where TSource : class
{
return source.Include(prop);
}

private IIncludableQueryable<TSource, TNextProperty> ApplyThenFetch<TSource, TPrevProperty, TNextProperty>(
IIncludableQueryable<TSource, IEnumerable<TPrevProperty>> source, Expression<Func<TPrevProperty, TNextProperty>> prop)
where TSource : class
{
return source.ThenInclude(prop);
}

private IIncludableQueryable<TSource, TNextProperty> ApplyThenFetch<TSource, TPrevProperty, TNextProperty>(
IIncludableQueryable<TSource, TPrevProperty> source, Expression<Func<TPrevProperty, TNextProperty>> prop)
where TSource : class
{
return source.ThenInclude(prop);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace GenericQueryable.Fetching;

public static class FetchRuleExpanderExtensions
{
public static PropertyFetchRule<TSource> Expand<TSource>(this IFetchRuleExpander fetchRuleExpander, FetchRule<TSource> fetchRule) =>
fetchRuleExpander.TryExpand(fetchRule) ?? throw new ArgumentOutOfRangeException(nameof(fetchRule));
}
2 changes: 1 addition & 1 deletion src/__SolutionItems/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading