Skip to content

Commit f4ea021

Browse files
Add support to IncludeFilter for EF Core
Add support to IncludeFilter for EF Core
1 parent 9614e2e commit f4ea021

20 files changed

+1683
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Description: Entity Framework Bulk Operations & Utilities (EF Bulk SaveChanges, Insert, Update, Delete, Merge | LINQ Query Cache, Deferred, Filter, IncludeFilter, IncludeOptimize | Audit)
2+
// Website & Documentation: https://github.com/zzzprojects/Entity-Framework-Plus
3+
// Forum & Issues: https://github.com/zzzprojects/EntityFramework-Plus/issues
4+
// License: https://github.com/zzzprojects/EntityFramework-Plus/blob/master/LICENSE
5+
// More projects: http://www.zzzprojects.com/
6+
// Copyright © ZZZ Projects Inc. 2014 - 2016. All rights reserved.
7+
8+
using System;
9+
using System.Linq;
10+
using System.Linq.Expressions;
11+
12+
namespace Z.EntityFramework.Plus
13+
{
14+
/// <summary>Base class for query include filter child.</summary>
15+
public abstract class BaseQueryIncludeFilterChild
16+
{
17+
/// <summary>Gets or sets a value indicating whether this object is lazy.</summary>
18+
/// <value>true if this object is lazy, false if not.</value>
19+
public bool IsLazy { get; set; }
20+
21+
/// <summary>Creates the query to use to load related entities.</summary>
22+
/// <param name="rootQuery">The root query.</param>
23+
public virtual void CreateIncludeQuery(IQueryable rootQuery)
24+
{
25+
throw new Exception(ExceptionMessage.GeneralException);
26+
}
27+
28+
/// <summary>Gets the filter.</summary>
29+
/// <returns>The filter.</returns>
30+
public abstract Expression GetFilter();
31+
32+
/// <summary>Gets filtered query.</summary>
33+
/// <param name="query">The query.</param>
34+
/// <returns>The filtered query.</returns>
35+
public abstract IQueryable GetFilteredQuery(IQueryable query);
36+
}
37+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Description: Entity Framework Bulk Operations & Utilities (EF Bulk SaveChanges, Insert, Update, Delete, Merge | LINQ Query Cache, Deferred, Filter, IncludeFilter, IncludeOptimize | Audit)
2+
// Website & Documentation: https://github.com/zzzprojects/Entity-Framework-Plus
3+
// Forum & Issues: https://github.com/zzzprojects/EntityFramework-Plus/issues
4+
// License: https://github.com/zzzprojects/EntityFramework-Plus/blob/master/LICENSE
5+
// More projects: http://www.zzzprojects.com/
6+
// Copyright © ZZZ Projects Inc. 2014 - 2016. All rights reserved.
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using System.Linq.Expressions;
12+
13+
namespace Z.EntityFramework.Plus
14+
{
15+
public static partial class QueryIncludeFilterExtensions
16+
{
17+
private static IQueryable<T> IncludeOptimizedSingle<T, TChild>(this IQueryable<T> query, Expression<Func<T, TChild>> queryIncludeFilter) where T : class where TChild : class
18+
{
19+
// INCLUDE sub path
20+
query = QueryIncludeFilterIncludeSubPath.IncludeSubPath(query, queryIncludeFilter);
21+
22+
// GET query root
23+
var includeOrderedQueryable = query as QueryIncludeFilterParentQueryable<T> ?? new QueryIncludeFilterParentQueryable<T>(query);
24+
25+
// ADD sub query
26+
includeOrderedQueryable.Childs.Add(new QueryIncludeFilterChild<T, TChild>(queryIncludeFilter));
27+
28+
// RETURN root
29+
return includeOrderedQueryable;
30+
}
31+
32+
/// <summary>
33+
/// An IQueryable&lt;T&gt; extension method that include and filter related entities.
34+
/// </summary>
35+
/// <typeparam name="T">Generic type parameter.</typeparam>
36+
/// <typeparam name="TChild">Type of the child.</typeparam>
37+
/// <param name="query">The query to filter included related entities.</param>
38+
/// <param name="queryIncludeFilter">The query filter to apply on included related entities.</param>
39+
/// <returns>An IQueryable&lt;T&gt; that include and filter related entities.</returns>
40+
public static IQueryable<T> IncludeFilter<T, TChild>(this IQueryable<T> query, Expression<Func<T, IEnumerable<TChild>>> queryIncludeFilter) where T : class where TChild : class
41+
{
42+
return query.IncludeOptimizedSingle(queryIncludeFilter);
43+
}
44+
45+
/// <summary>
46+
/// An IQueryable&lt;T&gt; extension method that include and filter related entities.
47+
/// </summary>
48+
/// <typeparam name="T">Generic type parameter.</typeparam>
49+
/// <typeparam name="TChild">Type of the child.</typeparam>
50+
/// <param name="query">The query to filter included related entities.</param>
51+
/// <param name="queryIncludeFilter">The query filter to apply on included related entities.</param>
52+
/// <returns>An IQueryable&lt;T&gt; that include and filter related entities.</returns>
53+
public static IQueryable<T> IncludeFilter<T, TChild>(this IQueryable<T> query, Expression<Func<T, TChild>> queryIncludeFilter) where T : class where TChild : class
54+
{
55+
return query.IncludeOptimizedSingle(queryIncludeFilter);
56+
}
57+
}
58+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Description: Entity Framework Bulk Operations & Utilities (EF Bulk SaveChanges, Insert, Update, Delete, Merge | LINQ Query Cache, Deferred, Filter, IncludeFilter, IncludeOptimize | Audit)
2+
// Website & Documentation: https://github.com/zzzprojects/Entity-Framework-Plus
3+
// Forum & Issues: https://github.com/zzzprojects/EntityFramework-Plus/issues
4+
// License: https://github.com/zzzprojects/EntityFramework-Plus/blob/master/LICENSE
5+
// More projects: http://www.zzzprojects.com/
6+
// Copyright © ZZZ Projects Inc. 2014 - 2016. All rights reserved.
7+
8+
using System.Linq;
9+
10+
namespace Z.EntityFramework.Plus
11+
{
12+
public static partial class QueryIncludeFilterExtensions
13+
{
14+
/// <summary>
15+
/// An IQueryable&lt;T&gt; extension method that include and filter related entities.
16+
/// </summary>
17+
/// <typeparam name="T">The type of elements of the query.</typeparam>
18+
/// <param name="query">The query to filter included related entities.</param>
19+
/// <param name="navigationProperties">The navigation properties to include.</param>
20+
/// <returns>An IQueryable&lt;T&gt; that include and filter related entities.</returns>
21+
public static IQueryable<T> IncludeFilterByPath<T>(this IQueryable<T> query, string navigationProperties)
22+
{
23+
// require a new method name to avoid annoying IntelliSense showing a string instead of the expression.
24+
return QueryIncludeFilterByPath.IncludeFilterByPath(query, navigationProperties);
25+
}
26+
}
27+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Reflection;
6+
7+
namespace Z.EntityFramework.Plus
8+
{
9+
/// <summary>A query include filter by path.</summary>
10+
public static class QueryIncludeFilterByPath
11+
{
12+
/// <summary>Include filter by path.</summary>
13+
/// <typeparam name="T">Generic type parameter.</typeparam>
14+
/// <param name="query">The query.</param>
15+
/// <param name="navigationPath">Full pathname of the navigation file.</param>
16+
/// <returns>An IQueryable&lt;T&gt;</returns>
17+
public static IQueryable<T> IncludeFilterByPath<T>(IQueryable<T> query, string navigationPath)
18+
{
19+
var elementType = typeof(T);
20+
var paths = navigationPath.Split('.');
21+
22+
// CREATE expression x => x.Right
23+
var expression = CreateLambdaExpression(elementType, paths, 0);
24+
25+
var method = typeof(QueryIncludeFilterExtensions).GetMethod("IncludeFilter", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)?.MakeGenericMethod(typeof(T), expression.Type.GetGenericArguments()[1]);
26+
27+
query = (IQueryable<T>) method?.Invoke(null, new object[] {query, expression});
28+
29+
return query;
30+
}
31+
32+
/// <summary>Creates lambda expression.</summary>
33+
/// <param name="parameterType">Type of the parameter.</param>
34+
/// <param name="paths">The paths.</param>
35+
/// <param name="currentIndex">The current index.</param>
36+
/// <returns>The new lambda expression.</returns>
37+
public static Expression CreateLambdaExpression(Type parameterType, string[] paths, int currentIndex)
38+
{
39+
// CREATE expression [x => x.Right]
40+
41+
// ADD parameter [x =>]
42+
var parameter = Expression.Parameter(parameterType);
43+
Expression expression = parameter;
44+
45+
// ADD property [x.Right]
46+
expression = AppendPropertyPath(expression, paths, currentIndex);
47+
48+
// GET function generic type
49+
var funcGenericType = typeof(Func<,>).MakeGenericType(parameterType, expression.Type);
50+
51+
// GET lambda method
52+
var lambdaMethod = typeof(Expression).GetMethods()
53+
.Single(x => x.Name == "Lambda"
54+
&& x.IsGenericMethod
55+
&& x.GetParameters().Length == 2
56+
&& !x.GetParameters()[1].ParameterType.IsArray)
57+
.MakeGenericMethod(funcGenericType);
58+
59+
// CREATE lambda expression
60+
expression = (Expression) lambdaMethod.Invoke(null, new object[] {expression, new List<ParameterExpression> {parameter}});
61+
62+
return expression;
63+
}
64+
65+
/// <summary>Appends a path.</summary>
66+
/// <param name="expression">The expression.</param>
67+
/// <param name="paths">The paths.</param>
68+
/// <param name="currentIndex">The current index.</param>
69+
/// <returns>An Expression.</returns>
70+
public static Expression AppendPath(Expression expression, string[] paths, int currentIndex)
71+
{
72+
expression = expression.Type.GetGenericArguments().Length == 0 ? AppendPropertyPath(expression, paths, currentIndex) : AppendSelectPath(expression, paths, currentIndex);
73+
74+
return expression;
75+
}
76+
77+
/// <summary>Appends a property path.</summary>
78+
/// <exception cref="Exception">Thrown when an exception error condition occurs.</exception>
79+
/// <param name="expression">The expression.</param>
80+
/// <param name="paths">The paths.</param>
81+
/// <param name="currentIndex">The current index.</param>
82+
/// <returns>An Expression.</returns>
83+
public static Expression AppendPropertyPath(Expression expression, string[] paths, int currentIndex)
84+
{
85+
// APPEND [x.PropertyName]
86+
var elementType = expression.Type;
87+
var property = elementType.GetProperty(paths[currentIndex], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
88+
89+
// ENSURE property exists
90+
if (property == null)
91+
{
92+
// Try Again with case insensitive
93+
var properties = elementType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
94+
.Where(x => x.Name.ToLowerInvariant() == paths[currentIndex].ToLowerInvariant()).ToList();
95+
96+
if (properties.Count == 1)
97+
{
98+
property = properties[0];
99+
}
100+
101+
if (property == null)
102+
{
103+
throw new Exception(string.Format(ExceptionMessage.QueryIncludeFilter_ByPath_MissingPath, elementType.FullName, paths[currentIndex]));
104+
}
105+
}
106+
107+
expression = Expression.Property(expression, property);
108+
109+
// APPEND path child
110+
currentIndex++;
111+
if (currentIndex < paths.Length)
112+
{
113+
expression = AppendPath(expression, paths, currentIndex);
114+
}
115+
116+
return expression;
117+
}
118+
119+
/// <summary>Appends a select path.</summary>
120+
/// <param name="expression">The expression.</param>
121+
/// <param name="paths">The paths.</param>
122+
/// <param name="currentIndex">The current index.</param>
123+
/// <returns>An Expression.</returns>
124+
public static Expression AppendSelectPath(Expression expression, string[] paths, int currentIndex)
125+
{
126+
// APPEND x => x.Rights[.Select(y => y.Right)]
127+
var elementType = expression.Type.GetGenericArguments()[0];
128+
129+
// CREATE lambda expression [y => y.Right]
130+
var lambdaExpression = CreateLambdaExpression(elementType, paths, currentIndex);
131+
132+
// APPEND Method [.Select(y => y.Right)]
133+
var selectMethod = typeof(Enumerable).GetMethods()
134+
.Single(x => x.Name == "Select"
135+
&& x.GetParameters().Length == 2
136+
&& x.GetParameters()[1].ParameterType.GetGenericArguments().Length == 2)
137+
.MakeGenericMethod(elementType, lambdaExpression.Type.GetGenericArguments()[1]);
138+
expression = Expression.Call(null, selectMethod, expression, lambdaExpression);
139+
140+
return expression;
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)