Skip to content

Commit f914af6

Browse files
committed
DefaultIfEmpty (#82)
1 parent 663b19c commit f914af6

File tree

5 files changed

+236
-68
lines changed

5 files changed

+236
-68
lines changed

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static class DynamicExpressionParser
1717
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
1818
/// <returns>The generated <see cref="LambdaExpression"/></returns>
1919
[PublicAPI]
20-
public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
20+
public static LambdaExpression ParseLambda([CanBeNull] Type resultType, string expression, params object[] values)
2121
{
2222
return ParseLambda(true, resultType, expression, values);
2323
}
@@ -31,7 +31,7 @@ public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull
3131
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
3232
/// <returns>The generated <see cref="LambdaExpression"/></returns>
3333
[PublicAPI]
34-
public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
34+
public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, string expression, params object[] values)
3535
{
3636
Check.NotEmpty(expression, nameof(expression));
3737

@@ -49,7 +49,7 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull]
4949
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
5050
/// <returns>The generated <see cref="LambdaExpression"/></returns>
5151
[PublicAPI]
52-
public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
52+
public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values)
5353
{
5454
return ParseLambda(true, itType, resultType, expression, values);
5555
}
@@ -64,7 +64,7 @@ public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Ty
6464
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
6565
/// <returns>The generated <see cref="LambdaExpression"/></returns>
6666
[PublicAPI]
67-
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
67+
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values)
6868
{
6969
Check.NotNull(itType, nameof(itType));
7070
Check.NotEmpty(expression, nameof(expression));
@@ -81,7 +81,7 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T
8181
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
8282
/// <returns>The generated <see cref="LambdaExpression"/></returns>
8383
[PublicAPI]
84-
public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
84+
public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, params object[] values)
8585
{
8686
return ParseLambda(true, parameters, resultType, expression, values);
8787
}
@@ -96,7 +96,7 @@ public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] param
9696
/// <param name="values">An object array that contains zero or more objects which are used as replacement values.</param>
9797
/// <returns>The generated <see cref="LambdaExpression"/></returns>
9898
[PublicAPI]
99-
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values)
99+
public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, params object[] values)
100100
{
101101
Check.NotNull(parameters, nameof(parameters));
102102
Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters));

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

+51-12
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,46 @@ public static int Count([NotNull] this IQueryable source, [NotNull] LambdaExpres
200200
}
201201
#endregion Count
202202

203+
#region DefaultIfEmpty
204+
private static readonly MethodInfo _defaultIfEmpty = GetMethod(nameof(Queryable.DefaultIfEmpty));
205+
private static readonly MethodInfo _defaultIfEmptyWithParam = GetMethod(nameof(Queryable.DefaultIfEmpty), 1);
206+
207+
/// <summary>
208+
/// Returns the elements of the specified sequence or the type parameter's default value in a singleton collection if the sequence is empty.
209+
/// </summary>
210+
/// <param name="source">The <see cref="IQueryable"/> to return a default value for if empty.</param>
211+
/// <example>
212+
/// <code language="cs">
213+
/// IQueryable queryable = employees.DefaultIfEmpty();
214+
/// </code>
215+
/// </example>
216+
/// <returns>An <see cref="IQueryable"/> that contains default if source is empty; otherwise, source.</returns>
217+
public static IQueryable DefaultIfEmpty([NotNull] this IQueryable source)
218+
{
219+
Check.NotNull(source, nameof(source));
220+
221+
return CreateQuery(_defaultIfEmpty, source);
222+
}
223+
224+
/// <summary>
225+
/// Returns the elements of the specified sequence or the type parameter's default value in a singleton collection if the sequence is empty.
226+
/// </summary>
227+
/// <param name="source">The <see cref="IQueryable"/> to return a default value for if empty.</param>
228+
/// <param name="defaultValue">The value to return if the sequence is empty.</param>
229+
/// <example>
230+
/// <code language="cs">
231+
/// IQueryable queryable = employees.DefaultIfEmpty(new Employee());
232+
/// </code>
233+
/// </example>
234+
/// <returns>An <see cref="IQueryable"/> that contains defaultValue if source is empty; otherwise, source.</returns>
235+
public static IQueryable DefaultIfEmpty([NotNull] this IQueryable source, [CanBeNull] object defaultValue)
236+
{
237+
Check.NotNull(source, nameof(source));
238+
239+
return CreateQuery(_defaultIfEmptyWithParam, source, Expression.Constant(defaultValue));
240+
}
241+
#endregion
242+
203243
#region Distinct
204244
private static readonly MethodInfo _distinct = GetMethod(nameof(Queryable.Distinct));
205245

@@ -219,8 +259,7 @@ public static IQueryable Distinct([NotNull] this IQueryable source)
219259
{
220260
Check.NotNull(source, nameof(source));
221261

222-
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Distinct", new Type[] { source.ElementType }, source.Expression));
223-
return source.Provider.CreateQuery(optimized);
262+
return CreateQuery(_distinct, source);
224263
}
225264
#endregion Distinct
226265

@@ -285,6 +324,8 @@ public static dynamic First([NotNull] this IQueryable source, [NotNull] LambdaEx
285324
#endregion First
286325

287326
#region FirstOrDefault
327+
private static readonly MethodInfo _firstOrDefault = GetMethod(nameof(Queryable.FirstOrDefault));
328+
288329
/// <summary>
289330
/// Returns the first element of a sequence, or a default value if the sequence contains no elements.
290331
/// </summary>
@@ -300,7 +341,6 @@ public static dynamic FirstOrDefault([NotNull] this IQueryable source)
300341

301342
return Execute(_firstOrDefault, source);
302343
}
303-
private static readonly MethodInfo _firstOrDefault = GetMethod(nameof(Queryable.FirstOrDefault));
304344

305345
/// <summary>
306346
/// Returns the first element of a sequence that satisfies a specified condition or a default value if no such element is found.
@@ -424,8 +464,7 @@ public static IQueryable GroupBy([NotNull] this IQueryable source, [NotNull] str
424464

425465
var optimized = OptimizeExpression(Expression.Call(
426466
typeof(Queryable), nameof(Queryable.GroupBy),
427-
new[] { source.ElementType, keyLambda.Body.Type },
428-
new[] { source.Expression, Expression.Quote(keyLambda) }));
467+
new[] { source.ElementType, keyLambda.Body.Type }, source.Expression, Expression.Quote(keyLambda)));
429468

430469
return source.Provider.CreateQuery(optimized);
431470
}
@@ -1539,7 +1578,7 @@ public static IQueryable Where([NotNull] this IQueryable source, [NotNull] strin
15391578
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
15401579
return source.Provider.CreateQuery(optimized);
15411580
}
1542-
1581+
15431582
/// <summary>
15441583
/// Filters a sequence of values based on a predicate.
15451584
/// </summary>
@@ -1549,7 +1588,7 @@ public static IQueryable Where([NotNull] this IQueryable source, [NotNull] Lambd
15491588
{
15501589
Check.NotNull(source, nameof(source));
15511590
Check.NotNull(lambda, nameof(lambda));
1552-
1591+
15531592
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
15541593
return source.Provider.CreateQuery(optimized);
15551594
}
@@ -1604,7 +1643,7 @@ private static IQueryable CreateQuery(MethodInfo operatorMethodInfo, IQueryable
16041643
? operatorMethodInfo.MakeGenericMethod(source.ElementType, typeof(object))
16051644
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
16061645

1607-
return source.Provider.CreateQuery(Expression.Call(null, operatorMethodInfo, new[] { source.Expression, expression }));
1646+
return source.Provider.CreateQuery(Expression.Call(null, operatorMethodInfo, source.Expression, expression));
16081647
}
16091648

16101649
private static object Execute(MethodInfo operatorMethodInfo, IQueryable source)
@@ -1638,7 +1677,7 @@ private static object Execute(MethodInfo operatorMethodInfo, IQueryable source,
16381677
? operatorMethodInfo.MakeGenericMethod(source.ElementType, typeof(object))
16391678
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
16401679

1641-
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, new[] { source.Expression, expression }));
1680+
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression, expression));
16421681
return source.Provider.Execute(optimized);
16431682
}
16441683

@@ -1651,15 +1690,15 @@ private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryabl
16511690
? operatorMethodInfo.MakeGenericMethod(source.ElementType, typeof(TResult))
16521691
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
16531692

1654-
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, new[] { source.Expression, expression }));
1693+
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression, expression));
16551694
return source.Provider.Execute<TResult>(optimized);
16561695
}
16571696

16581697
private static MethodInfo GetMethod<TResult>(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
1659-
GetMethod(name, parameterCount, mi => (mi.ReturnType == typeof(TResult)) && ((predicate == null) || predicate(mi)));
1698+
GetMethod(name, parameterCount, mi => mi.ReturnType == typeof(TResult) && (predicate == null || predicate(mi)));
16601699

16611700
private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
1662-
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
1701+
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.GetParameters().Length == parameterCount + 1 && (predicate == null || predicate(mi)));
16631702
#endregion Private Helpers
16641703
}
16651704
}

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

+48-46
Original file line numberDiff line numberDiff line change
@@ -123,65 +123,66 @@ interface INotSignatures
123123

124124
interface IEnumerableSignatures
125125
{
126-
void Where(bool predicate);
126+
void All(bool predicate);
127127
void Any();
128128
void Any(bool predicate);
129+
void Average(decimal? selector);
130+
void Average(decimal selector);
131+
void Average(double? selector);
132+
void Average(double selector);
133+
void Average(float? selector);
134+
void Average(float selector);
135+
void Average(int? selector);
136+
void Average(int selector);
137+
void Average(long? selector);
138+
void Average(long selector);
139+
void Contains(object selector);
140+
void Count();
141+
void Count(bool predicate);
142+
void DefaultIfEmpty();
143+
void DefaultIfEmpty(object defaultValue);
144+
void Distinct();
129145
void First(bool predicate);
130146
void FirstOrDefault(bool predicate);
131-
void Single(bool predicate);
132-
void SingleOrDefault(bool predicate);
147+
void GroupBy(object selector);
133148
void Last(bool predicate);
134149
void LastOrDefault(bool predicate);
135-
void All(bool predicate);
136-
void Count();
137-
void Count(bool predicate);
138-
void Min(object selector);
139150
void Max(object selector);
140-
void Sum(int selector);
141-
void Sum(int? selector);
142-
void Sum(long selector);
143-
void Sum(long? selector);
144-
void Sum(float selector);
145-
void Sum(float? selector);
146-
void Sum(double selector);
147-
void Sum(double? selector);
148-
void Sum(decimal selector);
149-
void Sum(decimal? selector);
150-
void Average(int selector);
151-
void Average(int? selector);
152-
void Average(long selector);
153-
void Average(long? selector);
154-
void Average(float selector);
155-
void Average(float? selector);
156-
void Average(double selector);
157-
void Average(double? selector);
158-
void Average(decimal selector);
159-
void Average(decimal? selector);
160-
void Select(object selector);
161-
void SelectMany(object selector);
151+
void Min(object selector);
162152
void OrderBy(object selector);
163153
void OrderByDescending(object selector);
164-
void ThenBy(object selector);
165-
void ThenByDescending(object selector);
166-
void Contains(object selector);
154+
void Select(object selector);
155+
void SelectMany(object selector);
156+
void Single(bool predicate);
157+
void SingleOrDefault(bool predicate);
167158
void Skip(int count);
168159
void SkipWhile(bool predicate);
160+
void Sum(decimal? selector);
161+
void Sum(decimal selector);
162+
void Sum(double? selector);
163+
void Sum(double selector);
164+
void Sum(float? selector);
165+
void Sum(float selector);
166+
void Sum(int? selector);
167+
void Sum(int selector);
168+
void Sum(long? selector);
169+
void Sum(long selector);
169170
void Take(int count);
170171
void TakeWhile(bool predicate);
171-
void Distinct();
172-
void GroupBy(object selector);
172+
void ThenBy(object selector);
173+
void ThenByDescending(object selector);
174+
void Where(bool predicate);
173175

174-
//Executors
175-
void Single();
176-
void SingleOrDefault();
176+
// Executors
177177
void First();
178178
void FirstOrDefault();
179179
void Last();
180180
void LastOrDefault();
181+
void Single();
182+
void SingleOrDefault();
181183
}
182184

183185
// These shorthands have different name than actual type and therefore not recognized by default from the _predefinedTypes
184-
//
185186
static readonly Dictionary<string, Type> _predefinedTypesShorthands = new Dictionary<string, Type>
186187
{
187188
{ "int", typeof(int) },
@@ -249,8 +250,6 @@ interface IEnumerableSignatures
249250
ParameterExpression _parent;
250251
ParameterExpression _root;
251252

252-
253-
254253
static ExpressionParser()
255254
{
256255
#if !(NET35 || SILVERLIGHT || NETFX_CORE ||WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
@@ -1603,17 +1602,20 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
16031602
typeArgs = new[] { elementType };
16041603
}
16051604

1606-
if (signature.Name == "Contains" || signature.Name == "Take" || signature.Name == "Skip")
1607-
{
1608-
args = new[] { instance, args[0] };
1609-
}
1610-
else if (args.Length == 0)
1605+
if (args.Length == 0)
16111606
{
16121607
args = new[] { instance };
16131608
}
16141609
else
16151610
{
1616-
args = new[] { instance, Expression.Lambda(args[0], innerIt) };
1611+
if (new[] { "Contains", "Take", "Skip", "DefaultIfEmpty" }.Contains(signature.Name))
1612+
{
1613+
args = new[] { instance, args[0] };
1614+
}
1615+
else
1616+
{
1617+
args = new[] { instance, Expression.Lambda(args[0], innerIt) };
1618+
}
16171619
}
16181620

16191621
return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args);

0 commit comments

Comments
 (0)