Skip to content

Commit c21b1be

Browse files
authored
Merge pull request #80 from jogibear9988/patch-1
[Feature] Usage of cached Lambda Expressions
2 parents 1fb5134 + b1c54d5 commit c21b1be

File tree

4 files changed

+339
-0
lines changed

4 files changed

+339
-0
lines changed

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

+239
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ public static bool Any([NotNull] this IQueryable source, [NotNull] string predic
9696

9797
return Execute<bool>(_anyPredicate, source, lambda);
9898
}
99+
100+
/// <summary>
101+
/// Determines whether a sequence contains any elements.
102+
/// </summary>
103+
/// <param name="source">A sequence to check for being empty.</param>
104+
/// <param name="lambda">A cached Lambda Expression.</param>
105+
/// <returns>true if the source sequence contains any elements; otherwise, false.</returns>
106+
public static bool Any([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
107+
{
108+
Check.NotNull(source, nameof(source));
109+
Check.NotNull(lambda, nameof(lambda));
110+
111+
return Execute<bool>(_anyPredicate, source, lambda);
112+
}
99113
#endregion Any
100114

101115
#region AsEnumerable
@@ -170,6 +184,20 @@ public static int Count([NotNull] this IQueryable source, [NotNull] string predi
170184

171185
return Execute<int>(_countPredicate, source, lambda);
172186
}
187+
188+
/// <summary>
189+
/// Returns the number of elements in a sequence.
190+
/// </summary>
191+
/// <param name="source">The <see cref="IQueryable"/> that contains the elements to be counted.</param>
192+
/// <param name="lambda">A cached Lambda Expression.</param>
193+
/// <returns>The number of elements in the specified sequence that satisfies a condition.</returns>
194+
public static int Count([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
195+
{
196+
Check.NotNull(source, nameof(source));
197+
Check.NotNull(lambda, nameof(lambda));
198+
199+
return Execute<int>(_countPredicate, source, lambda);
200+
}
173201
#endregion Count
174202

175203
#region Distinct
@@ -238,6 +266,22 @@ public static dynamic First([NotNull] this IQueryable source, [NotNull] string p
238266

239267
return Execute(_firstPredicate, source, lambda);
240268
}
269+
270+
/// <summary>
271+
/// Returns the first element of a sequence that satisfies a specified condition.
272+
/// </summary>
273+
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
274+
/// <param name="lambda">A cached Lambda Expression.</param>
275+
/// <returns>The first element in source that passes the test in predicate.</returns>
276+
#if NET35
277+
public static object First([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
278+
#else
279+
public static dynamic First([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
280+
#endif
281+
{
282+
Check.NotNull(source, nameof(source));
283+
return Execute(_firstPredicate, source, lambda);
284+
}
241285
#endregion First
242286

243287
#region FirstOrDefault
@@ -279,6 +323,23 @@ public static dynamic FirstOrDefault([NotNull] this IQueryable source, [NotNull]
279323

280324
return Execute(_firstOrDefaultPredicate, source, lambda);
281325
}
326+
327+
/// <summary>
328+
/// Returns the first element of a sequence that satisfies a specified condition or a default value if no such element is found.
329+
/// </summary>
330+
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
331+
/// <param name="lambda">A cached Lambda Expression.</param>
332+
/// <returns>default if source is empty or if no element passes the test specified by predicate; otherwise, the first element in source that passes the test specified by predicate.</returns>
333+
#if NET35
334+
public static object FirstOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
335+
#else
336+
public static dynamic FirstOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
337+
#endif
338+
{
339+
Check.NotNull(source, nameof(source));
340+
341+
return Execute(_firstOrDefaultPredicate, source, lambda);
342+
}
282343
private static readonly MethodInfo _firstOrDefaultPredicate = GetMethod(nameof(Queryable.FirstOrDefault), 1);
283344
#endregion FirstOrDefault
284345

@@ -563,6 +624,46 @@ public static dynamic Last([NotNull] this IQueryable source)
563624

564625
return Execute(_last, source);
565626
}
627+
628+
private static readonly MethodInfo _lastPredicate = GetMethod(nameof(Queryable.Last), 1);
629+
630+
/// <summary>
631+
/// Returns the last element of a sequence that satisfies a specified condition.
632+
/// </summary>
633+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
634+
/// <param name="predicate">A function to test each element for a condition.</param>
635+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
636+
/// <returns>The first element in source that passes the test in predicate.</returns>
637+
#if NET35
638+
public static object Last([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
639+
#else
640+
public static dynamic Last([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
641+
#endif
642+
{
643+
Check.NotNull(source, nameof(source));
644+
Check.NotEmpty(predicate, nameof(predicate));
645+
646+
bool createParameterCtor = source.IsLinqToObjects();
647+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
648+
649+
return Execute(_lastPredicate, source, lambda);
650+
}
651+
652+
/// <summary>
653+
/// Returns the last element of a sequence that satisfies a specified condition.
654+
/// </summary>
655+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
656+
/// <param name="lambda">A cached Lambda Expression.</param>
657+
/// <returns>The first element in source that passes the test in predicate.</returns>
658+
#if NET35
659+
public static object Last([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
660+
#else
661+
public static dynamic Last([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
662+
#endif
663+
{
664+
Check.NotNull(source, nameof(source));
665+
return Execute(_lastPredicate, source, lambda);
666+
}
566667
#endregion Last
567668

568669
#region LastOrDefault
@@ -582,6 +683,46 @@ public static dynamic LastOrDefault([NotNull] this IQueryable source)
582683

583684
return Execute(_lastDefault, source);
584685
}
686+
687+
private static readonly MethodInfo _lastDefaultPredicate = GetMethod(nameof(Queryable.LastOrDefault), 1);
688+
689+
/// <summary>
690+
/// Returns the last element of a sequence that satisfies a specified condition, or a default value if the sequence contains no elements.
691+
/// </summary>
692+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
693+
/// <param name="predicate">A function to test each element for a condition.</param>
694+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
695+
/// <returns>The first element in source that passes the test in predicate.</returns>
696+
#if NET35
697+
public static object LastOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
698+
#else
699+
public static dynamic LastOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
700+
#endif
701+
{
702+
Check.NotNull(source, nameof(source));
703+
Check.NotEmpty(predicate, nameof(predicate));
704+
705+
bool createParameterCtor = source.IsLinqToObjects();
706+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
707+
708+
return Execute(_lastDefaultPredicate, source, lambda);
709+
}
710+
711+
/// <summary>
712+
/// Returns the last element of a sequence that satisfies a specified condition, or a default value if the sequence contains no elements.
713+
/// </summary>
714+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
715+
/// <param name="lambda">A cached Lambda Expression.</param>
716+
/// <returns>The first element in source that passes the test in predicate.</returns>
717+
#if NET35
718+
public static object LastOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
719+
#else
720+
public static dynamic LastOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
721+
#endif
722+
{
723+
Check.NotNull(source, nameof(source));
724+
return Execute(_lastDefaultPredicate, source, lambda);
725+
}
585726
#endregion LastOrDefault
586727

587728
#region OrderBy
@@ -1066,6 +1207,48 @@ public static dynamic Single([NotNull] this IQueryable source)
10661207
return source.Provider.Execute(optimized);
10671208
}
10681209

1210+
private static readonly MethodInfo _singlePredicate = GetMethod(nameof(Queryable.Single), 1);
1211+
1212+
/// <summary>
1213+
/// Returns the only element of a sequence that satisfies a specified condition, and throws an exception if there
1214+
/// is not exactly one element in the sequence.
1215+
/// </summary>
1216+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
1217+
/// <param name="predicate">A function to test each element for a condition.</param>
1218+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
1219+
/// <returns>The first element in source that passes the test in predicate.</returns>
1220+
#if NET35
1221+
public static object Single([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
1222+
#else
1223+
public static dynamic Single([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
1224+
#endif
1225+
{
1226+
Check.NotNull(source, nameof(source));
1227+
Check.NotEmpty(predicate, nameof(predicate));
1228+
1229+
bool createParameterCtor = source.IsLinqToObjects();
1230+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
1231+
1232+
return Execute(_singlePredicate, source, lambda);
1233+
}
1234+
1235+
/// <summary>
1236+
/// Returns the only element of a sequence that satisfies a specified condition, and throws an exception if there
1237+
/// is not exactly one element in the sequence.
1238+
/// </summary>
1239+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
1240+
/// <param name="lambda">A cached Lambda Expression.</param>
1241+
/// <returns>The first element in source that passes the test in predicate.</returns>
1242+
#if NET35
1243+
public static object Single([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1244+
#else
1245+
public static dynamic Single([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1246+
#endif
1247+
{
1248+
Check.NotNull(source, nameof(source));
1249+
return Execute(_singlePredicate, source, lambda);
1250+
}
1251+
10691252
/// <summary>
10701253
/// Returns the only element of a sequence, or a default value if the sequence
10711254
/// is empty; this method throws an exception if there is more than one element
@@ -1084,6 +1267,48 @@ public static dynamic SingleOrDefault([NotNull] this IQueryable source)
10841267
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "SingleOrDefault", new[] { source.ElementType }, source.Expression));
10851268
return source.Provider.Execute(optimized);
10861269
}
1270+
1271+
private static readonly MethodInfo _singleDefaultPredicate = GetMethod(nameof(Queryable.SingleOrDefault), 1);
1272+
1273+
/// <summary>
1274+
/// Returns the only element of a sequence that satisfies a specified condition or a default value if the sequence
1275+
/// is empty; and throws an exception if there is not exactly one element in the sequence.
1276+
/// </summary>
1277+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
1278+
/// <param name="predicate">A function to test each element for a condition.</param>
1279+
/// <param name="args">An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings.</param>
1280+
/// <returns>The first element in source that passes the test in predicate.</returns>
1281+
#if NET35
1282+
public static object SingleOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
1283+
#else
1284+
public static dynamic SingleOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
1285+
#endif
1286+
{
1287+
Check.NotNull(source, nameof(source));
1288+
Check.NotEmpty(predicate, nameof(predicate));
1289+
1290+
bool createParameterCtor = source.IsLinqToObjects();
1291+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
1292+
1293+
return Execute(_singleDefaultPredicate, source, lambda);
1294+
}
1295+
1296+
/// <summary>
1297+
/// Returns the only element of a sequence that satisfies a specified condition or a default value if the sequence
1298+
/// is empty; and throws an exception if there is not exactly one element in the sequence.
1299+
/// </summary>
1300+
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
1301+
/// <param name="lambda">A cached Lambda Expression.</param>
1302+
/// <returns>The first element in source that passes the test in predicate.</returns>
1303+
#if NET35
1304+
public static object SingleOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1305+
#else
1306+
public static dynamic SingleOrDefault([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1307+
#endif
1308+
{
1309+
Check.NotNull(source, nameof(source));
1310+
return Execute(_singleDefaultPredicate, source, lambda);
1311+
}
10871312
#endregion Single/SingleOrDefault
10881313

10891314
#region Skip
@@ -1314,6 +1539,20 @@ public static IQueryable Where([NotNull] this IQueryable source, [NotNull] strin
13141539
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
13151540
return source.Provider.CreateQuery(optimized);
13161541
}
1542+
1543+
/// <summary>
1544+
/// Filters a sequence of values based on a predicate.
1545+
/// </summary>
1546+
/// <param name="source">A <see cref="IQueryable"/> to filter.</param>
1547+
/// <param name="lambda">A cached Lambda Expression.</param>
1548+
public static IQueryable Where([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1549+
{
1550+
Check.NotNull(source, nameof(source));
1551+
Check.NotNull(lambda, nameof(lambda));
1552+
1553+
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
1554+
return source.Provider.CreateQuery(optimized);
1555+
}
13171556
#endregion
13181557

13191558
#region Private Helpers

test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs

+40
Original file line numberDiff line numberDiff line change
@@ -1205,5 +1205,45 @@ public void ExpressionTests_Where_DoubleDecimalCompare()
12051205

12061206
Assert.Equal(expected, result);
12071207
}
1208+
1209+
[Fact]
1210+
public void ExpressionTests_Where_WithCachedLambda()
1211+
{
1212+
var list = new List<SimpleValuesModel>
1213+
{
1214+
new SimpleValuesModel { IntValue = 1 },
1215+
new SimpleValuesModel { IntValue = 3 },
1216+
new SimpleValuesModel { IntValue = 2 },
1217+
new SimpleValuesModel { IntValue = 3 }
1218+
};
1219+
1220+
var lambda = DynamicExpressionParser.ParseLambda(typeof(SimpleValuesModel), typeof(bool), "IntValue == 3");
1221+
var res = DynamicQueryableExtensions.Where(list.AsQueryable(), lambda);
1222+
Assert.Equal(res.Count(), 2);
1223+
1224+
var res2 = DynamicQueryableExtensions.Any(list.AsQueryable(), lambda);
1225+
Assert.True(res2);
1226+
1227+
var res3 = DynamicQueryableExtensions.Count(list.AsQueryable(), lambda);
1228+
Assert.Equal(res3, 2);
1229+
1230+
var res4 = DynamicQueryableExtensions.First(list.AsQueryable(), lambda);
1231+
Assert.Equal(res4, list[1]);
1232+
1233+
var res5 = DynamicQueryableExtensions.FirstOrDefault(list.AsQueryable(), lambda);
1234+
Assert.Equal(res5, list[1]);
1235+
1236+
var res6 = DynamicQueryableExtensions.Last(list.AsQueryable(), lambda);
1237+
Assert.Equal(res6, list[3]);
1238+
1239+
var res7 = DynamicQueryableExtensions.LastOrDefault(list.AsQueryable(), lambda);
1240+
Assert.Equal(res7, list[3]);
1241+
1242+
var res8 = DynamicQueryableExtensions.Single(list.AsQueryable().Take(2), lambda);
1243+
Assert.Equal(res8, list[1]);
1244+
1245+
var res9 = DynamicQueryableExtensions.SingleOrDefault(list.AsQueryable().Take(2), lambda);
1246+
Assert.Equal(res9, list[1]);
1247+
}
12081248
}
12091249
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Last.cs

+30
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ public void Last()
2424
#endif
2525
}
2626

27+
[Fact]
28+
public void Last_Predicate()
29+
{
30+
//Arrange
31+
var testList = User.GenerateSampleModels(100);
32+
var queryable = testList.AsQueryable();
33+
34+
//Act
35+
var expected = queryable.Last(u => u.Income > 1000);
36+
var result = queryable.Last("Income > 1000");
37+
38+
//Assert
39+
Assert.Equal(expected as object, result);
40+
}
41+
2742
[Fact]
2843
public void LastOrDefault()
2944
{
@@ -45,6 +60,21 @@ public void LastOrDefault()
4560
Assert.Null(defaultResult);
4661
}
4762

63+
[Fact]
64+
public void LastOrDefault_Predicate()
65+
{
66+
//Arrange
67+
var testList = User.GenerateSampleModels(100);
68+
var queryable = testList.AsQueryable();
69+
70+
//Act
71+
var expected = queryable.LastOrDefault(u => u.Income > 1000);
72+
var result = queryable.LastOrDefault("Income > 1000");
73+
74+
//Assert
75+
Assert.Equal(expected as object, result);
76+
}
77+
4878
[Fact]
4979
public void Last_Dynamic()
5080
{

0 commit comments

Comments
 (0)