diff --git a/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs b/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs index 38f4a6cb..1564452f 100644 --- a/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs +++ b/src-console/ConsoleAppEF2.1.1_InMemory/Program.cs @@ -1,4 +1,5 @@ using ConsoleAppEF2.Database; +using Microsoft.EntityFrameworkCore.DynamicLinq; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -183,6 +184,14 @@ static void Main(string[] args) { Console.WriteLine(e); } + + var a1 = context.Cars.Select(c => c.Key).AverageAsync().Result; + var a2 = context.Cars.Select("Key").AverageAsync().Result; + + // var a3 = context.Cars.AverageAsync(c => c.Key).Result; + var a4 = context.Cars.AverageAsync("Key").Result; + + int end = 0; } private static void LikeTests(TestContext context, ParsingConfig config) diff --git a/src-console/ConsoleApp_net452_EF6_Effort/Program.cs b/src-console/ConsoleApp_net452_EF6_Effort/Program.cs index efbf1281..324329ca 100644 --- a/src-console/ConsoleApp_net452_EF6_Effort/Program.cs +++ b/src-console/ConsoleApp_net452_EF6_Effort/Program.cs @@ -15,7 +15,7 @@ static void Main(string[] args) using (var context = new KendoGridDbContext(connection)) { - context.KendoGridCountry.Add(new Country { Code = "NL", Name = "Nederland" }); + context.KendoGridCountry.Add(new Country { Id = 1000, Code = "NL", Name = "Nederland" }); var main1 = new MainCompany { Name = "Main1" }; context.KendoGridMainCompany.Add(main1); diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs index d11ef3e9..afd46754 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs @@ -29,24 +29,60 @@ public static class EntityFrameworkDynamicQueryableExtensions { private static readonly TraceSource TraceSource = new TraceSource(typeof(EntityFrameworkDynamicQueryableExtensions).Name); - private static Expression OptimizeExpression(Expression expression) + #region AllAsync + private static readonly MethodInfo _AllPredicate = GetMethod(nameof(Queryable.All), 1); + + /// + /// Asynchronously determines whether all the elements of a sequence satisfy a condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that All asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the All of. + /// + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A task that represents the asynchronous operation. The task result contains true if every element of the source sequence passes the test in the specified predicate; otherwise, false. + /// + [PublicAPI] + public static Task AllAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) { - if (ExtensibilityPoint.QueryOptimizer != null) - { - var optimized = ExtensibilityPoint.QueryOptimizer(expression); + return AllAsync(source, predicate, default(CancellationToken), args); + } -#if !(WINDOWS_APP45x || SILVERLIGHT) - if (optimized != expression) - { - TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression before : {0}", expression); - TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression after : {0}", optimized); - } -#endif - return optimized; - } + /// + /// Asynchronously determines whether all the elements of a sequence satisfy a condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that All asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the All of. + /// + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. The task result contains true if every element of the source sequence passes the test in the specified predicate; otherwise, false. + /// + [PublicAPI] + public static Task AllAsync([NotNull] this IQueryable source, [NotNull] string predicate, CancellationToken cancellationToken = default(CancellationToken), [CanBeNull] params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotEmpty(predicate, nameof(predicate)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); - return expression; + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, predicate, args); + + return ExecuteAsync(_AllPredicate, source, Expression.Quote(lambda), cancellationToken); } + #endregion AllAsync #region AnyAsync private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any)); @@ -65,8 +101,7 @@ private static Expression OptimizeExpression(Expression expression) /// A to observe while waiting for the task to complete. /// /// - /// A task that represents the asynchronous operation. - /// The task result contains true if the source sequence contains any elements; otherwise, false. + /// A task that represents the asynchronous operation. The task result contains true if the source sequence contains any elements; otherwise, false. /// [PublicAPI] public static Task AnyAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) @@ -89,12 +124,10 @@ private static Expression OptimizeExpression(Expression expression) /// /// An whose elements to test for a condition. /// - /// A function to test each element for a condition. + /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// - /// A task that represents the asynchronous operation. - /// The task result contains true if any elements in the source sequence pass the test in the specified - /// predicate; otherwise, false. + /// A task that represents the asynchronous operation. The task result contains true if the source sequence contains any elements; otherwise, false. /// [PublicAPI] public static Task AnyAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) @@ -112,15 +145,13 @@ public static Task AnyAsync([NotNull] this IQueryable source, [NotNull] st /// /// An whose elements to test for a condition. /// - /// A function to test each element for a condition. + /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// /// A to observe while waiting for the task to complete. /// /// - /// A task that represents the asynchronous operation. - /// The task result contains true if any elements in the source sequence pass the test in the specified - /// predicate; otherwise, false. + /// A task that represents the asynchronous operation. The task result contains true if the source sequence contains any elements; otherwise, false. /// [PublicAPI] public static Task AnyAsync([NotNull] this IQueryable source, [NotNull] string predicate, CancellationToken cancellationToken = default(CancellationToken), [CanBeNull] params object[] args) @@ -135,6 +166,92 @@ public static Task AnyAsync([NotNull] this IQueryable source, [NotNull] st } #endregion AnyAsync + #region AverageAsync + private static readonly MethodInfo _average = GetMethod(nameof(Queryable.Average)); + + /// + /// Asynchronously computes the average of a sequence of values. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that any asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the average of. + /// + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. The task result contains the average of the sequence of values. + /// + [PublicAPI] + public static Task AverageAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + return ExecuteAsync(_average, source, cancellationToken); + } + + private static readonly MethodInfo _averagePredicate = GetMethod(nameof(Queryable.Average), 1); + + /// + /// Asynchronously computes the average of a sequence of values that is obtained by invoking a projection function on each element of the input sequence. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that Average asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the average of. + /// + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A task that represents the asynchronous operation. + /// The task result contains true if Average elements in the source sequence pass the test in the specified + /// predicate; otherwise, false. + /// + [PublicAPI] + public static Task AverageAsync([NotNull] this IQueryable source, [NotNull] string selector, [CanBeNull] params object[] args) + { + return AverageAsync(source, selector, default(CancellationToken), args); + } + + /// + /// Asynchronously computes the average of a sequence of values that is obtained by invoking a projection function on each element of the input sequence. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that Average asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the average of. + /// + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. + /// The task result contains true if Average elements in the source sequence pass the test in the specified + /// predicate; otherwise, false. + /// + [PublicAPI] + public static Task AverageAsync([NotNull] this IQueryable source, [NotNull] string selector, CancellationToken cancellationToken = default(CancellationToken), [CanBeNull] params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotEmpty(selector, nameof(selector)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, selector, args); + + return ExecuteAsync(_averagePredicate, source, Expression.Quote(lambda), cancellationToken); + } + #endregion AverageAsync + #region Count private static readonly MethodInfo _count = GetMethod(nameof(Queryable.Count)); @@ -662,7 +779,6 @@ public static Task SingleOrDefaultAsync([NotNull] this IQueryable sourc #endregion SingleOrDefault #region SumAsync - /// /// Asynchronously computes the sum of a sequence of values. /// @@ -749,28 +865,9 @@ public static Task SumAsync([NotNull] this IQueryable source, Cancellat #endregion SumAsync #region Private Helpers - // Copied from https://github.com/aspnet/EntityFramework/blob/9186d0b78a3176587eeb0f557c331f635760fe92/src/Microsoft.EntityFrameworkCore/EntityFrameworkQueryableExtensions.cs - //private static Task ExecuteAsync(MethodInfo operatorMethodInfo, IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) - //{ - // var provider = source.Provider as IAsyncQueryProvider; - - // if (provider != null) - // { - // if (operatorMethodInfo.IsGenericMethod) - // { - // operatorMethodInfo = operatorMethodInfo.MakeGenericMethod(source.ElementType); - // } - - // return provider.ExecuteAsync( - // Expression.Call(null, operatorMethodInfo, source.Expression), - // cancellationToken); - // } - - // throw new InvalidOperationException(Res.IQueryableProviderNotAsync); - //} private static readonly MethodInfo _executeAsyncMethod = typeof(EntityFrameworkDynamicQueryableExtensions) -#if NETSTANDARD +#if NETSTANDARD || UAP10_0 .GetMethods(BindingFlags.Static | BindingFlags.NonPublic) .Single(m => m.Name == nameof(ExecuteAsync) && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(MethodInfo), typeof(IQueryable), typeof(CancellationToken) })); #else @@ -814,11 +911,11 @@ public static Task SumAsync([NotNull] this IQueryable source, Cancellat private static readonly MethodInfo _executeAsyncMethodWithExpression = typeof(EntityFrameworkDynamicQueryableExtensions) -#if NETSTANDARD +#if NETSTANDARD || UAP10_0 .GetMethods(BindingFlags.Static | BindingFlags.NonPublic) .Single(m => m.Name == nameof(ExecuteAsync) && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { typeof(MethodInfo), typeof(IQueryable), typeof(Expression), typeof(CancellationToken) })); #else - .GetMethod(nameof(ExecuteAsync), BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(MethodInfo), typeof(IQueryable), typeof(Expression), typeof(CancellationToken)}, null); + .GetMethod(nameof(ExecuteAsync), BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(MethodInfo), typeof(IQueryable), typeof(Expression), typeof(CancellationToken) }, null); #endif private static Task ExecuteDynamicAsync(MethodInfo operatorMethodInfo, IQueryable source, Expression expression, CancellationToken cancellationToken = default(CancellationToken)) @@ -861,6 +958,25 @@ private static MethodInfo GetMethod(string name, Type returnType, int parameterC private static MethodInfo GetMethod(string name, int parameterCount = 0, Func predicate = null) => typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).First(mi => (mi.GetParameters().Length == parameterCount + 1) && ((predicate == null) || predicate(mi))); -#endregion Private Helpers + + private static Expression OptimizeExpression(Expression expression) + { + if (ExtensibilityPoint.QueryOptimizer != null) + { + var optimized = ExtensibilityPoint.QueryOptimizer(expression); + +#if !(WINDOWS_APP45x || SILVERLIGHT) + if (optimized != expression) + { + TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression before : {0}", expression); + TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Expression after : {0}", optimized); + } +#endif + return optimized; + } + + return expression; + } + #endregion Private Helpers } } diff --git a/src/System.Linq.Dynamic.Core/Compatibility/LambdaExpressionExtensions.cs b/src/System.Linq.Dynamic.Core/Compatibility/LambdaExpressionExtensions.cs new file mode 100644 index 00000000..d018c44b --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Compatibility/LambdaExpressionExtensions.cs @@ -0,0 +1,15 @@ +// ReSharper disable once CheckNamespace +namespace System.Linq.Expressions +{ + internal static class LambdaExpressionExtensions + { + public static Type GetReturnType(this LambdaExpression lambdaExpression) + { +#if !NET35 + return lambdaExpression.ReturnType; +#else + return lambdaExpression.Body.Type; +#endif + } + } +} diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index bd58c7c1..2da1c69f 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -100,6 +100,60 @@ public static object Aggregate([NotNull] this IQueryable source, [NotNull] strin } #endregion Aggregate + #region All + private static readonly MethodInfo _AllPredicate = GetMethod(nameof(Queryable.All), 1); + + /// + /// Determines whether all the elements of a sequence satisfy a condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that All asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the All of. + /// + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false. + /// + [PublicAPI] + public static bool All([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) + { + return All(source, ParsingConfig.Default, predicate, args); + } + + /// + /// Determines whether all the elements of a sequence satisfy a condition. + /// + /// + /// Multiple active operations on the same context instance are not supported. Use 'await' to ensure + /// that All asynchronous operations have completed before calling another method on this context. + /// + /// + /// An to calculate the All of. + /// + /// The . + /// A projection function to apply to each element. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false. + /// + [PublicAPI] + public static bool All([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, [CanBeNull] params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(config, nameof(config)); + Check.NotEmpty(predicate, nameof(predicate)); + + bool createParameterCtor = SupportsLinqToObjects(config, source); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args); + + return Execute(_AllPredicate, source, Expression.Quote(lambda)); + } + #endregion AllAsync + #region Any private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any)); @@ -173,6 +227,79 @@ public static bool Any([NotNull] this IQueryable source, [NotNull] LambdaExpress } #endregion Any + #region Average + /// + /// Computes the average of a sequence of numeric values. + /// + /// A sequence of numeric values to calculate the average of. + /// + /// + /// IQueryable queryable = employees.AsQueryable(); + /// var result1 = queryable.Average(); + /// var result2 = queryable.Select("Roles.Average()"); + /// + /// + /// The average of the values in the sequence. + [PublicAPI] + public static double Average([NotNull] this IQueryable source) + { + Check.NotNull(source, nameof(source)); + + var average = GetMethod(nameof(Queryable.Average), source.ElementType, typeof(double)); + return Execute(average, source); + } + + /// + /// Computes the average of a sequence of numeric values. + /// + /// A sequence of numeric values to calculate the average of. + /// The . + /// A function to test each element for a condition. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// + /// IQueryable queryable = employees.AsQueryable(); + /// var result = queryable.Average("Income"); + /// + /// + /// The average of the values in the sequence. + [PublicAPI] + public static double Average([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(config, nameof(config)); + Check.NotEmpty(predicate, nameof(predicate)); + + bool createParameterCtor = SupportsLinqToObjects(config, source); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); + + return Average(source, lambda); + } + + /// + [PublicAPI] + public static double Average([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) + { + return Average(source, ParsingConfig.Default, predicate, args); + } + + /// + /// Computes the average of a sequence of numeric values. + /// + /// A sequence of numeric values to calculate the average of. + /// A Lambda Expression. + /// The average of the values in the sequence. + [PublicAPI] + public static double Average([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(lambda, nameof(lambda)); + + var averageSelector = GetMethod(nameof(Queryable.Average), lambda.GetReturnType(), typeof(double), 1); + return Execute(averageSelector, source, lambda); + } + #endregion Average + #region AsEnumerable #if NET35 /// @@ -1808,13 +1935,74 @@ public static IQueryable SkipWhile([NotNull] this IQueryable source, [NotNull] s /// Computes the sum of a sequence of numeric values. /// /// A sequence of numeric values to calculate the sum of. + /// + /// + /// IQueryable queryable = employees.AsQueryable(); + /// var result1 = queryable.Sum(); + /// var result2 = queryable.Select("Roles.Sum()"); + /// + /// /// The sum of the values in the sequence. + [PublicAPI] public static object Sum([NotNull] this IQueryable source) { Check.NotNull(source, nameof(source)); - var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), nameof(Queryable.Sum), null, source.Expression)); - return source.Provider.Execute(optimized); + var sum = GetMethod(nameof(Queryable.Sum), source.ElementType); + return Execute(sum, source); + } + + /// + /// Computes the sum of a sequence of numeric values. + /// + /// A sequence of numeric values to calculate the sum of. + /// The . + /// A function to test each element for a condition. + /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. + /// + /// + /// IQueryable queryable = employees.AsQueryable(); + /// var result = queryable.Sum("Income"); + /// + /// + /// The sum of the values in the sequence. + [PublicAPI] + public static object Sum([NotNull] this IQueryable source, [NotNull] ParsingConfig config, [NotNull] string predicate, params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(config, nameof(config)); + Check.NotEmpty(predicate, nameof(predicate)); + + bool createParameterCtor = SupportsLinqToObjects(config, source); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); + + var sumSelector = GetMethod(nameof(Queryable.Sum), lambda.GetReturnType(), 1); + + return Execute(sumSelector, source, lambda); + } + + /// + [PublicAPI] + public static object Sum([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) + { + return Sum(source, ParsingConfig.Default, predicate, args); + } + + /// + /// Computes the sum of a sequence of numeric values. + /// + /// A sequence of numeric values to calculate the sum of. + /// A Lambda Expression. + /// The sum of the values in the sequence. + [PublicAPI] + public static object Sum([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(lambda, nameof(lambda)); + + var sumSelector = GetMethod(nameof(Queryable.Sum), lambda.GetReturnType(), 1); + + return Execute(sumSelector, source, lambda); } #endregion Sum @@ -2056,13 +2244,6 @@ private static void CheckOuterAndInnerTypes(ParsingConfig config, bool createPar // If types are not the same, try to convert to Nullable and generate new LambdaExpression if (outerSelectorReturnType != innerSelectorReturnType) { - //var outerSelectorReturnTypeInfo = outerSelectorReturnType.GetTypeInfo(); - //var innerSelectorReturnTypeInfo = innerSelectorReturnType.GetTypeInfo(); - //if (outerSelectorReturnTypeInfo.BaseType == typeof(DynamicClass) && innerSelectorReturnTypeInfo.BaseType == typeof(DynamicClass)) - //{ - - //} - if (TypeHelper.IsNullableType(outerSelectorReturnType) && !TypeHelper.IsNullableType(innerSelectorReturnType)) { innerSelectorReturnType = ExpressionParser.ToNullableType(innerSelectorReturnType); @@ -2125,7 +2306,9 @@ private static TResult Execute(MethodInfo operatorMethodInfo, IQueryabl } var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression)); - return source.Provider.Execute(optimized); + var result = source.Provider.Execute(optimized); + + return (TResult)Convert.ChangeType(result, typeof(TResult)); } private static object Execute(MethodInfo operatorMethodInfo, IQueryable source, LambdaExpression expression) @@ -2151,7 +2334,9 @@ private static TResult Execute(MethodInfo operatorMethodInfo, IQueryabl : operatorMethodInfo.MakeGenericMethod(source.ElementType); var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression, expression)); - return source.Provider.Execute(optimized); + var result = source.Provider.Execute(optimized); + + return (TResult)Convert.ChangeType(result, typeof(TResult)); } private static MethodInfo GetGenericMethod(string name) @@ -2159,6 +2344,12 @@ private static MethodInfo GetGenericMethod(string name) return typeof(Queryable).GetTypeInfo().GetDeclaredMethods(name).Single(mi => mi.IsGenericMethod); } + private static MethodInfo GetMethod(string name, Type argumentType, Type returnType, int parameterCount = 0, Func predicate = null) => + GetMethod(name, returnType, parameterCount, mi => mi.ToString().Contains(argumentType.ToString()) && ((predicate == null) || predicate(mi))); + + private static MethodInfo GetMethod(string name, Type returnType, int parameterCount = 0, Func predicate = null) => + GetMethod(name, parameterCount, mi => (mi.ReturnType == returnType) && ((predicate == null) || predicate(mi))); + private static MethodInfo GetMethod(string name, int parameterCount = 0, Func predicate = null) { try @@ -2168,7 +2359,7 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func b.BlogId > 2000); + + //Act + var actual = _context.Blogs.All("BlogId > 2000"); + + //Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AllAsync.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AllAsync.cs new file mode 100644 index 00000000..7ec5d647 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AllAsync.cs @@ -0,0 +1,30 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using System.Data.Entity; +using EntityFramework.DynamicLinq; +#endif +using System.Threading.Tasks; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public async Task Entities_AllAsync() + { + //Arrange + PopulateTestData(1, 0); + + var expected = await _context.Blogs.AllAsync(b => b.BlogId > 2000); + + //Act + var actual = await _context.Blogs.AllAsync("BlogId > 2000"); + + //Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Average.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Average.cs new file mode 100644 index 00000000..815b7b26 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Average.cs @@ -0,0 +1,43 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using System.Data.Entity; +using EntityFramework.DynamicLinq; +#endif +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public void Entities_Average() + { + // Arrange + PopulateTestData(2, 0); + + // Act + double expected = _context.Blogs.Select(b => b.BlogId).Average(); + double actual = _context.Blogs.Select("BlogId").Average(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Entities_Average_Selector() + { + // Arrange + PopulateTestData(2, 0); + + double expected = _context.Blogs.Average(b => b.BlogId); + + // Act + double actual = _context.Blogs.Average("BlogId"); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AverageAsync.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AverageAsync.cs new file mode 100644 index 00000000..41236fbb --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AverageAsync.cs @@ -0,0 +1,45 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using System.Data.Entity; +using EntityFramework.DynamicLinq; +#endif +using System.Threading.Tasks; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public async Task Entities_AverageAsync() + { + // Arrange + PopulateTestData(2, 0); + + double expected = await _context.Blogs.Select(b => b.BlogId).AverageAsync(); + + // Act + double actual = await _context.Blogs.Select("BlogId").AverageAsync(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public async Task Entities_AverageAsync_Selector() + { + // Arrange + PopulateTestData(2, 0); + + double expected = await _context.Blogs.AverageAsync(b => b.BlogId); + + // Act + double actual = await _context.Blogs.AverageAsync("BlogId"); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Sum.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Sum.cs new file mode 100644 index 00000000..3cbb3714 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.Sum.cs @@ -0,0 +1,74 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using System.Data.Entity; +using EntityFramework.DynamicLinq; +#endif +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public void Entities_Sum_Integer() + { + // Arrange + PopulateTestData(2, 0); + + var expected = _context.Blogs.Select(b => b.BlogId).Sum(); + + // Act + var actual = _context.Blogs.Select("BlogId").Sum(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Entities_Sum_Double() + { + // Arrange + PopulateTestData(2, 0); + + var expected = _context.Blogs.Select(b => b.BlogId * 1.0d).Sum(); + + // Act + var actual = _context.Blogs.Select("BlogId * 1.0").Sum(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Entities_Sum_Integer_Selector() + { + // Arrange + PopulateTestData(2, 0); + + var expected = _context.Blogs.Sum(b => b.BlogId); + + // Act + var actual = _context.Blogs.Sum("BlogId"); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Entities_Sum_Double_Selector() + { + // Arrange + PopulateTestData(2, 0); + + var expected = _context.Blogs.Sum(b => b.BlogId * 1.0d); + + // Act + var actual = _context.Blogs.Sum("BlogId * 1.0d"); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SumAsync.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SumAsync.cs index 16013225..8c4daaf4 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SumAsync.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.SumAsync.cs @@ -13,33 +13,63 @@ namespace System.Linq.Dynamic.Core.Tests public partial class EntitiesTests { [Fact] - public async Task Entities_SumAsync() + public async Task Entities_SumAsync_Integer() { - //Arrange - PopulateTestData(1, 0); + // Arrange + PopulateTestData(2, 0); - var expectedSum = await _context.Blogs.Select(b => b.BlogId).SumAsync(); + var expected = await _context.Blogs.Select(b => b.BlogId).SumAsync(); - //Act - var actualSum = await _context.Blogs.Select(b => b.BlogId).SumAsync(); + // Act + var actual = await _context.Blogs.Select("BlogId").SumAsync(); - //Assert - Assert.Equal(expectedSum, actualSum); + // Assert + Assert.Equal(expected, actual); } [Fact] - public async Task Entities_SumAsync_Selector() + public async Task Entities_SumAsync_Double() { - //Arrange - PopulateTestData(1, 0); + // Arrange + PopulateTestData(2, 0); - var expectedSum = await _context.Blogs.SumAsync(b => b.BlogId); + var expected = await _context.Blogs.Select(b => b.BlogId * 1.0d).SumAsync(); - //Act - var actualSum = await _context.Blogs.SumAsync("BlogId"); + // Act + var actual = await _context.Blogs.Select("BlogId").SumAsync(); - //Assert - Assert.Equal(expectedSum, actualSum); + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public async Task Entities_SumAsync_Integer_Selector() + { + // Arrange + PopulateTestData(2, 0); + + var expected = await _context.Blogs.SumAsync(b => b.BlogId); + + // Act + var actual = await _context.Blogs.SumAsync("BlogId"); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public async Task Entities_SumAsync_Double_Selector() + { + // Arrange + PopulateTestData(2, 0); + + var expected = await _context.Blogs.SumAsync(b => b.BlogId * 1.0d); + + // Act + var actual = await _context.Blogs.SumAsync("BlogId * 1.0d"); + + // Assert + Assert.Equal(expected, actual); } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.All.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.All.cs new file mode 100644 index 00000000..a1436946 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.All.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class QueryableTests + { + [Fact] + public void All_Predicate() + { + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); + + //Act + bool expected = queryable.All(u => u.Income > 50); + bool result = queryable.All("Income > 50"); + + //Assert + Assert.Equal(expected, result); + } + + [Fact] + public void All_Predicate_WithArgs() + { + const int value = 50; + + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); + + //Act + bool expected = queryable.All(u => u.Income > value); + bool result = queryable.All("Income > @0", value); + + //Assert + Assert.Equal(expected, result); + } + + [Fact] + public void All_Dynamic_Select() + { + // Arrange + IQueryable queryable = User.GenerateSampleModels(1).AsQueryable(); + + // Act + var expected = queryable.Select(x => x.Roles.All(r => r.Name != null)).ToArray(); + var result = queryable.Select("Roles.All(Name != null)").ToDynamicArray(); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void All_Dynamic_Where() + { + const string search = "e"; + + // Arrange + var testList = User.GenerateSampleModels(10); + var queryable = testList.AsQueryable(); + + // Act + var expected = queryable.Where(u => u.Roles.All(r => r.Name.Contains(search))).ToArray(); + var result = queryable.Where("Roles.All(Name.Contains(@0))", search).ToArray(); + + Assert.Equal(expected, result); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs index 55b0fa9f..32feb2ee 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs @@ -33,7 +33,7 @@ public void Any_Predicate() //Act bool expected = queryable.Any(u => u.Income > 50); - bool result = (queryable as IQueryable).Any("Income > 50"); + bool result = queryable.Any("Income > 50"); //Assert Assert.Equal(expected, result); @@ -49,7 +49,7 @@ public void Any_Predicate_WithArgs() //Act bool expected = queryable.Any(u => u.Income > value); - bool result = (queryable as IQueryable).Any("Income > @0", value); + bool result = queryable.Any("Income > @0", value); //Assert Assert.Equal(expected, result); @@ -109,7 +109,7 @@ public void Any_Dynamic_Where_Nested2() // arrange var list = new List { - new A {Bs = new List() {new B {A = new A(), Cs = new List {new C {B = new B()}}}}} + new A {Bs = new List {new B {A = new A(), Cs = new List {new C {B = new B()}}}}} }; var queryable = list.AsQueryable(); @@ -151,4 +151,4 @@ class C public B B { get; set; } } } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Average.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Average.cs new file mode 100644 index 00000000..f424d9fc --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Average.cs @@ -0,0 +1,36 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class QueryableTests + { + [Fact] + public void Average() + { + // Arrange + var incomes = User.GenerateSampleModels(100).Select(u => u.Income); + + // Act + var expected = incomes.Average(); + var actual = incomes.AsQueryable().Average(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Average_Selector() + { + // Arrange + var users = User.GenerateSampleModels(100); + + // Act + var expected = users.Average(u => u.Income); + var result = users.AsQueryable().Average("Income"); + + // Assert + Assert.Equal(expected, result); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Sum.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Sum.cs new file mode 100644 index 00000000..7bab86be --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Sum.cs @@ -0,0 +1,36 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class QueryableTests + { + [Fact] + public void Sum() + { + // Arrange + var incomes = User.GenerateSampleModels(100).Select(u => u.Income); + + // Act + var expected = incomes.Sum(); + var actual = incomes.AsQueryable().Sum(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Sum_Selector() + { + // Arrange + var users = User.GenerateSampleModels(100); + + // Act + var expected = users.Sum(u => u.Income); + var result = users.AsQueryable().Sum("Income"); + + // Assert + Assert.Equal(expected, result); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj index 65412342..aa725e7b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj +++ b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj @@ -49,7 +49,6 @@ -