Skip to content

Commit 8708ef8

Browse files
wertzuiStefH
authored andcommitted
Added SumAsync (#290)
* Added SumAsync * Made SumAsync generic to work with all return types * Made reflected methods static for better performance * Added tests * Added Reflection that works on netstandard1.3
1 parent d1fe2a7 commit 8708ef8

File tree

2 files changed

+175
-3
lines changed

2 files changed

+175
-3
lines changed

src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs

+130-3
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,93 @@ public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable sourc
661661
}
662662
#endregion SingleOrDefault
663663

664+
#region SumAsync
665+
666+
/// <summary>
667+
/// Asynchronously computes the sum of a sequence of values.
668+
/// </summary>
669+
/// <remarks>
670+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
671+
/// that any asynchronous operations have completed before calling another method on this context.
672+
/// </remarks>
673+
/// <param name="source">
674+
/// An <see cref="IQueryable" /> that contains the elements to be summed.
675+
/// </param>
676+
/// <param name="cancellationToken">
677+
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
678+
/// </param>
679+
/// <returns>
680+
/// A task that represents the asynchronous operation.
681+
/// The task result contains sum of the values in the sequence.
682+
/// </returns>
683+
[PublicAPI]
684+
public static Task<dynamic> SumAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
685+
{
686+
Check.NotNull(source, nameof(source));
687+
Check.NotNull(cancellationToken, nameof(cancellationToken));
688+
689+
var sum = GetMethod(nameof(Queryable.Sum), source.ElementType);
690+
691+
return ExecuteDynamicAsync(sum, source, cancellationToken);
692+
}
693+
694+
/// <summary>
695+
/// Asynchronously computes the sum of a sequence of values.
696+
/// </summary>
697+
/// <remarks>
698+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
699+
/// that any asynchronous operations have completed before calling another method on this context.
700+
/// </remarks>
701+
/// <param name="source">
702+
/// An <see cref="IQueryable" /> that contains the elements to be summed.
703+
/// </param>
704+
/// <param name="selector"> A projection function to apply to each element. </param>
705+
/// <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>
706+
/// <returns>
707+
/// A task that represents the asynchronous operation.
708+
/// The task result contains the number of elements in the sequence that satisfy the condition in the predicate
709+
/// function.
710+
/// </returns>
711+
[PublicAPI]
712+
public static Task<dynamic> SumAsync([NotNull] this IQueryable source, [NotNull] string selector, [CanBeNull] params object[] args)
713+
{
714+
return SumAsync(source, default(CancellationToken), selector, args);
715+
}
716+
717+
/// <summary>
718+
/// Asynchronously computes the sum of a sequence of values.
719+
/// </summary>
720+
/// <remarks>
721+
/// Multiple active operations on the same context instance are not supported. Use 'await' to ensure
722+
/// that any asynchronous operations have completed before calling another method on this context.
723+
/// </remarks>
724+
/// <param name="source">
725+
/// An <see cref="IQueryable" /> that contains the elements to be summed.
726+
/// </param>
727+
/// <param name="selector"> A projection function to apply to each element. </param>
728+
/// <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>
729+
/// <param name="cancellationToken">
730+
/// A <see cref="CancellationToken" /> to observe while waiting for the task to complete.
731+
/// </param>
732+
/// <returns>
733+
/// A task that represents the asynchronous operation.
734+
/// The task result contains the sum of the projected values.
735+
/// </returns>
736+
[PublicAPI]
737+
public static Task<dynamic> SumAsync([NotNull] this IQueryable source, CancellationToken cancellationToken, [NotNull] string selector, [CanBeNull] params object[] args)
738+
{
739+
Check.NotNull(source, nameof(source));
740+
Check.NotNull(selector, nameof(selector));
741+
Check.NotNull(cancellationToken, nameof(cancellationToken));
742+
743+
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, selector, args);
744+
745+
var sumSelector = GetMethod(nameof(Queryable.Sum), lambda.ReturnType, 1);
746+
747+
return ExecuteDynamicAsync(sumSelector, source, Expression.Quote(lambda), cancellationToken);
748+
}
749+
#endregion SumAsync
750+
664751
#region Private Helpers
665752
// Copied from https://github.com/aspnet/EntityFramework/blob/9186d0b78a3176587eeb0f557c331f635760fe92/src/Microsoft.EntityFrameworkCore/EntityFrameworkQueryableExtensions.cs
666753
//private static Task<dynamic> ExecuteAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
@@ -681,6 +768,24 @@ public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable sourc
681768

682769
// throw new InvalidOperationException(Res.IQueryableProviderNotAsync);
683770
//}
771+
private static readonly MethodInfo _executeAsyncMethod =
772+
typeof(EntityFrameworkDynamicQueryableExtensions)
773+
#if NETSTANDARD
774+
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
775+
.Single(m => m.Name == nameof(ExecuteAsync) && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(MethodInfo), typeof(IQueryable), typeof(CancellationToken) }));
776+
#else
777+
.GetMethod(nameof(ExecuteAsync), BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(MethodInfo), typeof(IQueryable), typeof(CancellationToken) }, null);
778+
#endif
779+
780+
private static Task<dynamic> ExecuteDynamicAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
781+
{
782+
var executeAsyncMethod = _executeAsyncMethod.MakeGenericMethod(operatorMethodInfo.ReturnType);
783+
784+
var task = (Task)executeAsyncMethod.Invoke(null, new object[] { operatorMethodInfo, source, cancellationToken });
785+
var castedTask = task.ContinueWith(t => (dynamic)t.GetType().GetProperty(nameof(Task<object>.Result)).GetValue(t));
786+
787+
return castedTask;
788+
}
684789

685790
private static Task<TResult> ExecuteAsync<TResult>(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken))
686791
{
@@ -707,6 +812,25 @@ public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable sourc
707812
private static Task<TResult> ExecuteAsync<TResult>(MethodInfo operatorMethodInfo, IQueryable source, LambdaExpression expression, CancellationToken cancellationToken = default(CancellationToken))
708813
=> ExecuteAsync<TResult>(operatorMethodInfo, source, Expression.Quote(expression), cancellationToken);
709814

815+
private static readonly MethodInfo _executeAsyncMethodWithExpression =
816+
typeof(EntityFrameworkDynamicQueryableExtensions)
817+
#if NETSTANDARD
818+
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
819+
.Single(m => m.Name == nameof(ExecuteAsync) && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(MethodInfo), typeof(IQueryable), typeof(Expression), typeof(CancellationToken) }));
820+
#else
821+
.GetMethod(nameof(ExecuteAsync), BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(MethodInfo), typeof(IQueryable), typeof(Expression), typeof(CancellationToken)}, null);
822+
#endif
823+
824+
private static Task<dynamic> ExecuteDynamicAsync(MethodInfo operatorMethodInfo, IQueryable source, Expression expression, CancellationToken cancellationToken = default(CancellationToken))
825+
{
826+
var executeAsyncMethod = _executeAsyncMethodWithExpression.MakeGenericMethod(operatorMethodInfo.ReturnType);
827+
828+
var task = (Task)executeAsyncMethod.Invoke(null, new object[] { operatorMethodInfo, source, expression, cancellationToken });
829+
var castedTask = task.ContinueWith(t => (dynamic)t.GetType().GetProperty(nameof(Task<object>.Result)).GetValue(t));
830+
831+
return castedTask;
832+
}
833+
710834
private static Task<TResult> ExecuteAsync<TResult>(MethodInfo operatorMethodInfo, IQueryable source, Expression expression, CancellationToken cancellationToken = default(CancellationToken))
711835
{
712836
#if EFCORE
@@ -730,10 +854,13 @@ public static Task<dynamic> SingleOrDefaultAsync([NotNull] this IQueryable sourc
730854
}
731855

732856
private static MethodInfo GetMethod<TResult>(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
733-
GetMethod(name, parameterCount, mi => (mi.ReturnType == typeof(TResult)) && ((predicate == null) || predicate(mi)));
857+
GetMethod(name, typeof(TResult), parameterCount, predicate);
858+
859+
private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
860+
GetMethod(name, parameterCount, mi => (mi.ReturnType == returnType) && ((predicate == null) || predicate(mi)));
734861

735862
private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null) =>
736-
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
737-
#endregion Private Helpers
863+
typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).First(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi)));
864+
#endregion Private Helpers
738865
}
739866
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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_SumAsync()
17+
{
18+
//Arrange
19+
PopulateTestData(1, 0);
20+
21+
var expectedSum = await _context.Blogs.Select(b => b.BlogId).SumAsync();
22+
23+
//Act
24+
var actualSum = await _context.Blogs.Select(b => b.BlogId).SumAsync();
25+
26+
//Assert
27+
Assert.Equal(expectedSum, actualSum);
28+
}
29+
30+
[Fact]
31+
public async Task Entities_SumAsync_Selector()
32+
{
33+
//Arrange
34+
PopulateTestData(1, 0);
35+
36+
var expectedSum = await _context.Blogs.SumAsync(b => b.BlogId);
37+
38+
//Act
39+
var actualSum = await _context.Blogs.SumAsync("BlogId");
40+
41+
//Assert
42+
Assert.Equal(expectedSum, actualSum);
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)