Skip to content

Commit 095d3e6

Browse files
committed
internal code refactoring (#24)
1 parent 61b4396 commit 095d3e6

File tree

4 files changed

+175
-42
lines changed

4 files changed

+175
-42
lines changed

src/System.Linq.Dynamic.Core/Compatibility/IntrospectionExtensions.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ public static Type GetTypeInfo(this Type type)
1818
return type;
1919
}
2020

21-
public static Type[] GetGenericTypeArguments(this Type typeInfo)
21+
public static Type[] GetGenericTypeArguments(this Type type)
2222
{
23-
return typeInfo.GetGenericArguments();
23+
return type.GetGenericArguments();
24+
}
25+
26+
public static MethodInfo[] GetDeclaredMethods(this Type type, string name)
27+
{
28+
return type.GetMethods(BindingFlags.DeclaredOnly);
2429
}
2530
#else
2631
public static Type[] GetGenericTypeArguments(this TypeInfo typeInfo)

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

+119-30
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ public static bool Any([NotNull] this IQueryable source)
3535
{
3636
Check.NotNull(source, nameof(source));
3737

38-
return Queryable.Any((IQueryable<object>)source);
38+
return Execute<bool>(_any, source);
3939
}
40+
private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any));
4041

4142
/// <summary>
4243
/// Determines whether a sequence contains any elements.
@@ -61,15 +62,9 @@ public static bool Any([NotNull] this IQueryable source, [NotNull] string predic
6162
bool createParameterCtor = source.IsLinqToObjects();
6263
LambdaExpression lambda = DynamicExpression.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
6364

64-
return source.Provider.Execute<bool>(
65-
Expression.Call(
66-
typeof(Queryable), "Any",
67-
new[] { source.ElementType },
68-
source.Expression,
69-
Expression.Quote(lambda)
70-
)
71-
);
65+
return Execute<bool>(_anyPredicate, source, Expression.Quote(lambda));
7266
}
67+
private static readonly MethodInfo _anyPredicate = GetMethod(nameof(Queryable.Any), 1);
7368
#endregion Any
7469

7570
#region AsEnumerable
@@ -112,8 +107,9 @@ public static int Count([NotNull] this IQueryable source)
112107
{
113108
Check.NotNull(source, nameof(source));
114109

115-
return Queryable.Count((IQueryable<object>)source);
110+
return Execute<int>(_count, source);
116111
}
112+
private static readonly MethodInfo _count = GetMethod(nameof(Queryable.Count));
117113

118114
/// <summary>
119115
/// Returns the number of elements in a sequence.
@@ -136,17 +132,11 @@ public static int Count([NotNull] this IQueryable source, [NotNull] string predi
136132
Check.NotEmpty(predicate, nameof(predicate));
137133

138134
bool createParameterCtor = source.IsLinqToObjects();
139-
LambdaExpression lambda = DynamicExpression.ParseLambda(createParameterCtor, source.ElementType, typeof(bool), predicate, args);
135+
LambdaExpression lambda = DynamicExpression.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
140136

141-
return source.Provider.Execute<int>(
142-
Expression.Call(
143-
typeof(Queryable), "Count",
144-
new[] { source.ElementType },
145-
source.Expression,
146-
Expression.Quote(lambda)
147-
)
148-
);
137+
return Execute<int>(_countPredicate, source, Expression.Quote(lambda));
149138
}
139+
private static readonly MethodInfo _countPredicate = GetMethod(nameof(Queryable.Count), 1);
150140
#endregion Count
151141

152142
#region Distinct
@@ -174,7 +164,7 @@ public static IQueryable Distinct([NotNull] this IQueryable source)
174164
}
175165
#endregion Distinct
176166

177-
#region First/FirstOrDefault
167+
#region First
178168
/// <summary>
179169
/// Returns the first element of a sequence.
180170
/// </summary>
@@ -188,16 +178,40 @@ public static dynamic First([NotNull] this IQueryable source)
188178
{
189179
Check.NotNull(source, nameof(source));
190180

191-
return source.Provider.Execute(Expression.Call(
192-
typeof(Queryable), "First",
193-
new[] { source.ElementType }, source.Expression));
181+
return Execute(_first, source);
182+
}
183+
private static readonly MethodInfo _first = GetMethod(nameof(Queryable.First));
184+
185+
/// <summary>
186+
/// Returns the first element of a sequence that satisfies a specified condition.
187+
/// </summary>
188+
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
189+
/// <param name="predicate">A function to test each element for a condition.</param>
190+
/// <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>
191+
/// <returns>The first element in source that passes the test in predicate.</returns>
192+
#if NET35
193+
public static object First([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
194+
#else
195+
public static dynamic First([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
196+
#endif
197+
{
198+
Check.NotNull(source, nameof(source));
199+
Check.NotEmpty(predicate, nameof(predicate));
200+
201+
bool createParameterCtor = source.IsLinqToObjects();
202+
LambdaExpression lambda = DynamicExpression.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
203+
204+
return Execute(_firstPredicate, source, Expression.Quote(lambda));
194205
}
206+
private static readonly MethodInfo _firstPredicate = GetMethod(nameof(Queryable.First), 1);
207+
#endregion First
195208

209+
#region FirstOrDefault
196210
/// <summary>
197211
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
198212
/// </summary>
199213
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
200-
/// <returns>default(TSource) if source is empty; otherwise, the first element in source.</returns>
214+
/// <returns>default if source is empty; otherwise, the first element in source.</returns>
201215
#if NET35
202216
public static object FirstOrDefault([NotNull] this IQueryable source)
203217
#else
@@ -206,11 +220,33 @@ public static dynamic FirstOrDefault([NotNull] this IQueryable source)
206220
{
207221
Check.NotNull(source, nameof(source));
208222

209-
return source.Provider.Execute(Expression.Call(
210-
typeof(Queryable), "FirstOrDefault",
211-
new[] { source.ElementType }, source.Expression));
223+
return Execute(_firstOrDefault, source);
224+
}
225+
private static readonly MethodInfo _firstOrDefault = GetMethod(nameof(Queryable.FirstOrDefault));
226+
227+
/// <summary>
228+
/// Returns the first element of a sequence that satisfies a specified condition or a default value if no such element is found.
229+
/// </summary>
230+
/// <param name="source">The <see cref="IQueryable"/> to return the first element of.</param>
231+
/// <param name="predicate">A function to test each element for a condition.</param>
232+
/// <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>
233+
/// <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>
234+
#if NET35
235+
public static object FirstOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
236+
#else
237+
public static dynamic FirstOrDefault([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args)
238+
#endif
239+
{
240+
Check.NotNull(source, nameof(source));
241+
Check.NotEmpty(predicate, nameof(predicate));
242+
243+
bool createParameterCtor = source.IsLinqToObjects();
244+
LambdaExpression lambda = DynamicExpression.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
245+
246+
return Execute(_firstOrDefaultPredicate, source, Expression.Quote(lambda));
212247
}
213-
#endregion First/FirstOrDefault
248+
private static readonly MethodInfo _firstOrDefaultPredicate = GetMethod(nameof(Queryable.FirstOrDefault), 1);
249+
#endregion FirstOrDefault
214250

215251
#region GroupBy
216252
/// <summary>
@@ -460,7 +496,7 @@ public static dynamic Last([NotNull] this IQueryable source)
460496
/// Returns the last element of a sequence, or a default value if the sequence contains no elements.
461497
/// </summary>
462498
/// <param name="source">The <see cref="IQueryable"/> to return the last element of.</param>
463-
/// <returns>default(TSource) if source is empty; otherwise, the last element in source.</returns>
499+
/// <returns>default if source is empty; otherwise, the last element in source.</returns>
464500
#if NET35
465501
public static object LastOrDefault([NotNull] this IQueryable source)
466502
#else
@@ -949,7 +985,7 @@ public static dynamic Single([NotNull] this IQueryable source)
949985
/// in the sequence.
950986
/// </summary>
951987
/// <param name="source">A <see cref="IQueryable"/> to return the single element of.</param>
952-
/// <returns>The single element of the input sequence, or default(TSource) if the sequence contains no elements.</returns>
988+
/// <returns>The single element of the input sequence, or default if the sequence contains no elements.</returns>
953989
#if NET35
954990
public static object SingleOrDefault([NotNull] this IQueryable source)
955991
#else
@@ -1074,5 +1110,58 @@ public static IQueryable Where([NotNull] this IQueryable source, [NotNull] strin
10741110
source.Expression, Expression.Quote(lambda)));
10751111
}
10761112
#endregion
1113+
1114+
#region Private Helpers
1115+
// Copied from https://github.com/aspnet/EntityFramework/blob/9186d0b78a3176587eeb0f557c331f635760fe92/src/Microsoft.EntityFrameworkCore/EntityFrameworkQueryableExtensions.cs
1116+
private static object Execute(MethodInfo operatorMethodInfo, IQueryable source)
1117+
{
1118+
if (operatorMethodInfo.IsGenericMethod)
1119+
{
1120+
operatorMethodInfo = operatorMethodInfo.MakeGenericMethod(source.ElementType);
1121+
}
1122+
1123+
return source.Provider.Execute(Expression.Call(null, operatorMethodInfo, source.Expression));
1124+
}
1125+
1126+
private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryable source)
1127+
{
1128+
if (operatorMethodInfo.IsGenericMethod)
1129+
{
1130+
operatorMethodInfo = operatorMethodInfo.MakeGenericMethod(source.ElementType);
1131+
}
1132+
1133+
return source.Provider.Execute<TResult>(Expression.Call(null, operatorMethodInfo, source.Expression));
1134+
}
1135+
1136+
private static object Execute(MethodInfo operatorMethodInfo, IQueryable source, LambdaExpression expression)
1137+
=> Execute(operatorMethodInfo, source, Expression.Quote(expression));
1138+
1139+
private static object Execute(MethodInfo operatorMethodInfo, IQueryable source, Expression expression)
1140+
{
1141+
operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
1142+
? operatorMethodInfo.MakeGenericMethod(source.ElementType, typeof(object))
1143+
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
1144+
1145+
return source.Provider.Execute(Expression.Call(null, operatorMethodInfo, new[] { source.Expression, expression }));
1146+
}
1147+
1148+
private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryable source, LambdaExpression expression)
1149+
=> Execute<TResult>(operatorMethodInfo, source, Expression.Quote(expression));
1150+
1151+
private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryable source, Expression expression)
1152+
{
1153+
operatorMethodInfo = operatorMethodInfo.GetGenericArguments().Length == 2
1154+
? operatorMethodInfo.MakeGenericMethod(source.ElementType, typeof(TResult))
1155+
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
1156+
1157+
return source.Provider.Execute<TResult>(Expression.Call(null, operatorMethodInfo, new[] { source.Expression, expression }));
1158+
}
1159+
1160+
private static MethodInfo GetMethod<TResult>(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
1161+
GetMethod(name, parameterCount, mi => (mi.ReturnType == typeof(TResult)) && ((predicate == null) || predicate(mi)));
1162+
1163+
private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
1164+
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
1165+
#endregion Private Helpers
10771166
}
10781167
}

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

+22-10
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,35 @@ public void First()
2424
}
2525

2626
[Fact]
27-
public void FirstOrDefault()
27+
public void First_Predicate()
2828
{
2929
//Arrange
3030
var testList = User.GenerateSampleModels(100);
31-
IQueryable testListQry = testList.AsQueryable();
31+
var queryable = testList.AsQueryable();
3232

3333
//Act
34-
var singleResult = testListQry.FirstOrDefault();
35-
var defaultResult = ((IQueryable)Enumerable.Empty<User>().AsQueryable()).FirstOrDefault();
34+
var expected = queryable.First(u => u.Income > 1000);
35+
var result = queryable.First("Income > 1000");
3636

3737
//Assert
38-
#if NET35
39-
Assert.Equal(testList[0].Id, singleResult.GetDynamicProperty<Guid>("Id"));
40-
#else
41-
Assert.Equal(testList[0].Id, singleResult.Id);
42-
#endif
43-
Assert.Null(defaultResult);
38+
Assert.Equal(expected as object, result);
39+
}
40+
41+
[Fact]
42+
public void First_Predicate_WithArgs()
43+
{
44+
const int value = 1000;
45+
46+
//Arrange
47+
var testList = User.GenerateSampleModels(100);
48+
var queryable = testList.AsQueryable();
49+
50+
//Act
51+
var expected = queryable.First(u => u.Income > value);
52+
var result = queryable.First("Income > @0", value);
53+
54+
//Assert
55+
Assert.Equal(expected as object, result);
4456
}
4557

4658
[Fact]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
2+
using Xunit;
3+
4+
namespace System.Linq.Dynamic.Core.Tests
5+
{
6+
public partial class QueryableTests
7+
{
8+
[Fact]
9+
public void FirstOrDefault()
10+
{
11+
//Arrange
12+
var testList = User.GenerateSampleModels(100);
13+
var queryable = testList.AsQueryable();
14+
15+
//Act
16+
var expected = Queryable.FirstOrDefault(queryable);
17+
var expectedDefault = Queryable.FirstOrDefault(Enumerable.Empty<User>().AsQueryable());
18+
19+
var result = (queryable as IQueryable).FirstOrDefault();
20+
var resultDefault = (Enumerable.Empty<User>().AsQueryable() as IQueryable).FirstOrDefault();
21+
22+
Assert.Equal(expected, result);
23+
Assert.Null(expectedDefault);
24+
Assert.Null(resultDefault);
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)