Skip to content

Commit de0750d

Browse files
authored
Add 'All', 'Average', 'AverageAsync' and update 'Sum' (#298)
* wip * fix code and tests * fix * rename * fix build * double * add/fax sum * lambda.GetReturnType() * SUM * Average not yet.. * fix * All
1 parent 99e531a commit de0750d

16 files changed

+805
-82
lines changed

src-console/ConsoleAppEF2.1.1_InMemory/Program.cs

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ConsoleAppEF2.Database;
2+
using Microsoft.EntityFrameworkCore.DynamicLinq;
23
using Newtonsoft.Json;
34
using System;
45
using System.Collections.Generic;
@@ -198,6 +199,14 @@ static void Main(string[] args)
198199
{
199200
Console.WriteLine(e);
200201
}
202+
203+
var a1 = context.Cars.Select(c => c.Key).AverageAsync().Result;
204+
var a2 = context.Cars.Select("Key").AverageAsync().Result;
205+
206+
// var a3 = context.Cars.AverageAsync(c => c.Key).Result;
207+
var a4 = context.Cars.AverageAsync("Key").Result;
208+
209+
int end = 0;
201210
}
202211

203212
private static void LikeTests(TestContext context, ParsingConfig config)

src-console/ConsoleApp_net452_EF6_Effort/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ static void Main(string[] args)
1515

1616
using (var context = new KendoGridDbContext(connection))
1717
{
18-
context.KendoGridCountry.Add(new Country { Code = "NL", Name = "Nederland" });
18+
context.KendoGridCountry.Add(new Country { Id = 1000, Code = "NL", Name = "Nederland" });
1919

2020
var main1 = new MainCompany { Name = "Main1" };
2121
context.KendoGridMainCompany.Add(main1);

src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs

+164-48
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// ReSharper disable once CheckNamespace
2+
namespace System.Linq.Expressions
3+
{
4+
internal static class LambdaExpressionExtensions
5+
{
6+
public static Type GetReturnType(this LambdaExpression lambdaExpression)
7+
{
8+
#if !NET35
9+
return lambdaExpression.ReturnType;
10+
#else
11+
return lambdaExpression.Body.Type;
12+
#endif
13+
}
14+
}
15+
}

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

+203-12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,60 @@ public static object Aggregate([NotNull] this IQueryable source, [NotNull] strin
100100
}
101101
#endregion Aggregate
102102

103+
#region All
104+
private static readonly MethodInfo _AllPredicate = GetMethod(nameof(Queryable.All), 1);
105+
106+
/// <summary>
107+
/// Determines whether all the elements of a sequence satisfy a condition.
108+
/// </summary>
109+
/// <remarks>
110+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
111+
/// that All asynchronous operations have completed before calling another method on this context.
112+
/// </remarks>
113+
/// <param name="source">
114+
/// An <see cref="IQueryable" /> to calculate the All of.
115+
/// </param>
116+
/// <param name="predicate">A projection function to apply to each element.</param>
117+
/// <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>
118+
/// <returns>
119+
/// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.
120+
/// </returns>
121+
[PublicAPI]
122+
public static bool All([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
123+
{
124+
return All(source, ParsingConfig.Default, predicate, args);
125+
}
126+
127+
/// <summary>
128+
/// Determines whether all the elements of a sequence satisfy a condition.
129+
/// </summary>
130+
/// <remarks>
131+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
132+
/// that All asynchronous operations have completed before calling another method on this context.
133+
/// </remarks>
134+
/// <param name="source">
135+
/// An <see cref="IQueryable" /> to calculate the All of.
136+
/// </param>
137+
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
138+
/// <param name="predicate">A projection function to apply to each element.</param>
139+
/// <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>
140+
/// <returns>
141+
/// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.
142+
/// </returns>
143+
[PublicAPI]
144+
public static bool All([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, [CanBeNull] params object[] args)
145+
{
146+
Check.NotNull(source, nameof(source));
147+
Check.NotNull(config, nameof(config));
148+
Check.NotEmpty(predicate, nameof(predicate));
149+
150+
bool createParameterCtor = SupportsLinqToObjects(config, source);
151+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args);
152+
153+
return Execute<bool>(_AllPredicate, source, Expression.Quote(lambda));
154+
}
155+
#endregion AllAsync
156+
103157
#region Any
104158
private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any));
105159

@@ -173,6 +227,79 @@ public static bool Any([NotNull] this IQueryable source, [NotNull] LambdaExpress
173227
}
174228
#endregion Any
175229

230+
#region Average
231+
/// <summary>
232+
/// Computes the average of a sequence of numeric values.
233+
/// </summary>
234+
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
235+
/// <example>
236+
/// <code language="cs">
237+
/// IQueryable queryable = employees.AsQueryable();
238+
/// var result1 = queryable.Average();
239+
/// var result2 = queryable.Select("Roles.Average()");
240+
/// </code>
241+
/// </example>
242+
/// <returns>The average of the values in the sequence.</returns>
243+
[PublicAPI]
244+
public static double Average([NotNull] this IQueryable source)
245+
{
246+
Check.NotNull(source, nameof(source));
247+
248+
var average = GetMethod(nameof(Queryable.Average), source.ElementType, typeof(double));
249+
return Execute<double>(average, source);
250+
}
251+
252+
/// <summary>
253+
/// Computes the average of a sequence of numeric values.
254+
/// </summary>
255+
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
256+
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
257+
/// <param name="predicate">A function to test each element for a condition.</param>
258+
/// <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>
259+
/// <example>
260+
/// <code language="cs">
261+
/// IQueryable queryable = employees.AsQueryable();
262+
/// var result = queryable.Average("Income");
263+
/// </code>
264+
/// </example>
265+
/// <returns>The average of the values in the sequence.</returns>
266+
[PublicAPI]
267+
public static double Average([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, params object[] args)
268+
{
269+
Check.NotNull(source, nameof(source));
270+
Check.NotNull(config, nameof(config));
271+
Check.NotEmpty(predicate, nameof(predicate));
272+
273+
bool createParameterCtor = SupportsLinqToObjects(config, source);
274+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args);
275+
276+
return Average(source, lambda);
277+
}
278+
279+
/// <inheritdoc cref="Average(IQueryable, ParsingConfig, string, object[])"/>
280+
[PublicAPI]
281+
public static double Average([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
282+
{
283+
return Average(source, ParsingConfig.Default, predicate, args);
284+
}
285+
286+
/// <summary>
287+
/// Computes the average of a sequence of numeric values.
288+
/// </summary>
289+
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
290+
/// <param name="lambda">A Lambda Expression.</param>
291+
/// <returns>The average of the values in the sequence.</returns>
292+
[PublicAPI]
293+
public static double Average([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
294+
{
295+
Check.NotNull(source, nameof(source));
296+
Check.NotNull(lambda, nameof(lambda));
297+
298+
var averageSelector = GetMethod(nameof(Queryable.Average), lambda.GetReturnType(), typeof(double), 1);
299+
return Execute<double>(averageSelector, source, lambda);
300+
}
301+
#endregion Average
302+
176303
#region AsEnumerable
177304
#if NET35
178305
/// <summary>
@@ -1808,13 +1935,74 @@ public static IQueryable SkipWhile([NotNull] this IQueryable source, [NotNull] s
18081935
/// Computes the sum of a sequence of numeric values.
18091936
/// </summary>
18101937
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
1938+
/// <example>
1939+
/// <code language="cs">
1940+
/// IQueryable queryable = employees.AsQueryable();
1941+
/// var result1 = queryable.Sum();
1942+
/// var result2 = queryable.Select("Roles.Sum()");
1943+
/// </code>
1944+
/// </example>
18111945
/// <returns>The sum of the values in the sequence.</returns>
1946+
[PublicAPI]
18121947
public static object Sum([NotNull] this IQueryable source)
18131948
{
18141949
Check.NotNull(source, nameof(source));
18151950

1816-
var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Sum), null, source.Expression));
1817-
return source.Provider.Execute(optimized);
1951+
var sum = GetMethod(nameof(Queryable.Sum), source.ElementType);
1952+
return Execute<object>(sum, source);
1953+
}
1954+
1955+
/// <summary>
1956+
/// Computes the sum of a sequence of numeric values.
1957+
/// </summary>
1958+
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
1959+
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
1960+
/// <param name="predicate">A function to test each element for a condition.</param>
1961+
/// <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>
1962+
/// <example>
1963+
/// <code language="cs">
1964+
/// IQueryable queryable = employees.AsQueryable();
1965+
/// var result = queryable.Sum("Income");
1966+
/// </code>
1967+
/// </example>
1968+
/// <returns>The sum of the values in the sequence.</returns>
1969+
[PublicAPI]
1970+
public static object Sum([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, params object[] args)
1971+
{
1972+
Check.NotNull(source, nameof(source));
1973+
Check.NotNull(config, nameof(config));
1974+
Check.NotEmpty(predicate, nameof(predicate));
1975+
1976+
bool createParameterCtor = SupportsLinqToObjects(config, source);
1977+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args);
1978+
1979+
var sumSelector = GetMethod(nameof(Queryable.Sum), lambda.GetReturnType(), 1);
1980+
1981+
return Execute<object>(sumSelector, source, lambda);
1982+
}
1983+
1984+
/// <inheritdoc cref="Sum(IQueryable, ParsingConfig, string, object[])"/>
1985+
[PublicAPI]
1986+
public static object Sum([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
1987+
{
1988+
return Sum(source, ParsingConfig.Default, predicate, args);
1989+
}
1990+
1991+
/// <summary>
1992+
/// Computes the sum of a sequence of numeric values.
1993+
/// </summary>
1994+
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
1995+
/// <param name="lambda">A Lambda Expression.</param>
1996+
/// <returns>The sum of the values in the sequence.</returns>
1997+
[PublicAPI]
1998+
public static object Sum([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda)
1999+
{
2000+
Check.NotNull(source, nameof(source));
2001+
Check.NotNull(lambda, nameof(lambda));
2002+
2003+
var sumSelector = GetMethod(nameof(Queryable.Sum), lambda.GetReturnType(), 1);
2004+
2005+
return Execute<object>(sumSelector, source, lambda);
18182006
}
18192007
#endregion Sum
18202008

@@ -2056,13 +2244,6 @@ private static void CheckOuterAndInnerTypes(ParsingConfig config, bool createPar
20562244
// If types are not the same, try to convert to Nullable and generate new LambdaExpression
20572245
if (outerSelectorReturnType != innerSelectorReturnType)
20582246
{
2059-
//var outerSelectorReturnTypeInfo = outerSelectorReturnType.GetTypeInfo();
2060-
//var innerSelectorReturnTypeInfo = innerSelectorReturnType.GetTypeInfo();
2061-
//if (outerSelectorReturnTypeInfo.BaseType == typeof(DynamicClass) && innerSelectorReturnTypeInfo.BaseType == typeof(DynamicClass))
2062-
//{
2063-
2064-
//}
2065-
20662247
if (TypeHelper.IsNullableType(outerSelectorReturnType) && !TypeHelper.IsNullableType(innerSelectorReturnType))
20672248
{
20682249
innerSelectorReturnType = ExpressionParser.ToNullableType(innerSelectorReturnType);
@@ -2125,7 +2306,9 @@ private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryabl
21252306
}
21262307

21272308
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression));
2128-
return source.Provider.Execute<TResult>(optimized);
2309+
var result = source.Provider.Execute(optimized);
2310+
2311+
return (TResult)Convert.ChangeType(result, typeof(TResult));
21292312
}
21302313

21312314
private static object Execute(MethodInfo operatorMethodInfo, IQueryable source, LambdaExpression expression)
@@ -2151,14 +2334,22 @@ private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryabl
21512334
: operatorMethodInfo.MakeGenericMethod(source.ElementType);
21522335

21532336
var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression, expression));
2154-
return source.Provider.Execute<TResult>(optimized);
2337+
var result = source.Provider.Execute(optimized);
2338+
2339+
return (TResult)Convert.ChangeType(result, typeof(TResult));
21552340
}
21562341

21572342
private static MethodInfo GetGenericMethod(string name)
21582343
{
21592344
return typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.IsGenericMethod);
21602345
}
21612346

2347+
private static MethodInfo GetMethod(string name, Type argumentType, Type returnType, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
2348+
GetMethod(name, returnType, parameterCount, mi => mi.ToString().Contains(argumentType.ToString()) && ((predicate == null) || predicate(mi)));
2349+
2350+
private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
2351+
GetMethod(name, parameterCount, mi => (mi.ReturnType == returnType) && ((predicate == null) || predicate(mi)));
2352+
21622353
private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null)
21632354
{
21642355
try
@@ -2168,7 +2359,7 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<Me
21682359
}
21692360
catch (Exception ex)
21702361
{
2171-
throw new Exception("Method not found: " + name, ex);
2362+
throw new Exception("Specific method not found: " + name, ex);
21722363
}
21732364
}
21742365
#endregion Private Helpers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#if EFCORE
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.DynamicLinq;
4+
#else
5+
using System.Data.Entity;
6+
using EntityFramework.DynamicLinq;
7+
#endif
8+
using System.Threading.Tasks;
9+
using Xunit;
10+
11+
namespace System.Linq.Dynamic.Core.Tests
12+
{
13+
public partial class EntitiesTests
14+
{
15+
[Fact]
16+
public void Entities_All()
17+
{
18+
//Arrange
19+
PopulateTestData(1, 0);
20+
21+
var expected = _context.Blogs.All(b => b.BlogId > 2000);
22+
23+
//Act
24+
var actual = _context.Blogs.All("BlogId > 2000");
25+
26+
//Assert
27+
Assert.Equal(expected, actual);
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#if EFCORE
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.DynamicLinq;
4+
#else
5+
using System.Data.Entity;
6+
using EntityFramework.DynamicLinq;
7+
#endif
8+
using System.Threading.Tasks;
9+
using Xunit;
10+
11+
namespace System.Linq.Dynamic.Core.Tests
12+
{
13+
public partial class EntitiesTests
14+
{
15+
[Fact]
16+
public async Task Entities_AllAsync()
17+
{
18+
//Arrange
19+
PopulateTestData(1, 0);
20+
21+
var expected = await _context.Blogs.AllAsync(b => b.BlogId > 2000);
22+
23+
//Act
24+
var actual = await _context.Blogs.AllAsync("BlogId > 2000");
25+
26+
//Assert
27+
Assert.Equal(expected, actual);
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)