Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src-console/ConsoleAppEF2.1.1_InMemory/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ConsoleAppEF2.Database;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src-console/ConsoleApp_net452_EF6_Effort/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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
}
}
}
215 changes: 203 additions & 12 deletions src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// Determines whether all the elements of a sequence satisfy a condition.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="source">
/// An <see cref="IQueryable" /> to calculate the All of.
/// </param>
/// <param name="predicate">A projection function to apply to each element.</param>
/// <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>
/// <returns>
/// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.
/// </returns>
[PublicAPI]
public static bool All([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
{
return All(source, ParsingConfig.Default, predicate, args);
}

/// <summary>
/// Determines whether all the elements of a sequence satisfy a condition.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="source">
/// An <see cref="IQueryable" /> to calculate the All of.
/// </param>
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
/// <param name="predicate">A projection function to apply to each element.</param>
/// <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>
/// <returns>
/// true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.
/// </returns>
[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<bool>(_AllPredicate, source, Expression.Quote(lambda));
}
#endregion AllAsync

#region Any
private static readonly MethodInfo _any = GetMethod(nameof(Queryable.Any));

Expand Down Expand Up @@ -173,6 +227,79 @@ public static bool Any([NotNull] this IQueryable source, [NotNull] LambdaExpress
}
#endregion Any

#region Average
/// <summary>
/// Computes the average of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
/// <example>
/// <code language="cs">
/// IQueryable queryable = employees.AsQueryable();
/// var result1 = queryable.Average();
/// var result2 = queryable.Select("Roles.Average()");
/// </code>
/// </example>
/// <returns>The average of the values in the sequence.</returns>
[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<double>(average, source);
}

/// <summary>
/// Computes the average of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <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>
/// <example>
/// <code language="cs">
/// IQueryable queryable = employees.AsQueryable();
/// var result = queryable.Average("Income");
/// </code>
/// </example>
/// <returns>The average of the values in the sequence.</returns>
[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);
}

/// <inheritdoc cref="Average(IQueryable, ParsingConfig, string, object[])"/>
[PublicAPI]
public static double Average([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
{
return Average(source, ParsingConfig.Default, predicate, args);
}

/// <summary>
/// Computes the average of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the average of.</param>
/// <param name="lambda">A Lambda Expression.</param>
/// <returns>The average of the values in the sequence.</returns>
[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<double>(averageSelector, source, lambda);
}
#endregion Average

#region AsEnumerable
#if NET35
/// <summary>
Expand Down Expand Up @@ -1808,13 +1935,74 @@ public static IQueryable SkipWhile([NotNull] this IQueryable source, [NotNull] s
/// Computes the sum of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
/// <example>
/// <code language="cs">
/// IQueryable queryable = employees.AsQueryable();
/// var result1 = queryable.Sum();
/// var result2 = queryable.Select("Roles.Sum()");
/// </code>
/// </example>
/// <returns>The sum of the values in the sequence.</returns>
[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<object>(sum, source);
}

/// <summary>
/// Computes the sum of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
/// <param name="config">The <see cref="ParsingConfig"/>.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <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>
/// <example>
/// <code language="cs">
/// IQueryable queryable = employees.AsQueryable();
/// var result = queryable.Sum("Income");
/// </code>
/// </example>
/// <returns>The sum of the values in the sequence.</returns>
[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<object>(sumSelector, source, lambda);
}

/// <inheritdoc cref="Sum(IQueryable, ParsingConfig, string, object[])"/>
[PublicAPI]
public static object Sum([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args)
{
return Sum(source, ParsingConfig.Default, predicate, args);
}

/// <summary>
/// Computes the sum of a sequence of numeric values.
/// </summary>
/// <param name="source">A sequence of numeric values to calculate the sum of.</param>
/// <param name="lambda">A Lambda Expression.</param>
/// <returns>The sum of the values in the sequence.</returns>
[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<object>(sumSelector, source, lambda);
}
#endregion Sum

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2125,7 +2306,9 @@ private static TResult Execute<TResult>(MethodInfo operatorMethodInfo, IQueryabl
}

var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression));
return source.Provider.Execute<TResult>(optimized);
var result = source.Provider.Execute(optimized);

return (TResult)Convert.ChangeType(result, typeof(TResult));
}

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

var optimized = OptimizeExpression(Expression.Call(null, operatorMethodInfo, source.Expression, expression));
return source.Provider.Execute<TResult>(optimized);
var result = source.Provider.Execute(optimized);

return (TResult)Convert.ChangeType(result, typeof(TResult));
}

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<MethodInfo, bool> 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<MethodInfo, bool> predicate = null) =>
GetMethod(name, parameterCount, mi => (mi.ReturnType == returnType) && ((predicate == null) || predicate(mi)));

private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<MethodInfo, bool> predicate = null)
{
try
Expand All @@ -2168,7 +2359,7 @@ private static MethodInfo GetMethod(string name, int parameterCount = 0, Func<Me
}
catch (Exception ex)
{
throw new Exception("Method not found: " + name, ex);
throw new Exception("Specific method not found: " + name, ex);
}
}
#endregion Private Helpers
Expand Down
30 changes: 30 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/EntitiesTests.All.cs
Original file line number Diff line number Diff line change
@@ -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 void Entities_All()
{
//Arrange
PopulateTestData(1, 0);

var expected = _context.Blogs.All(b => b.BlogId > 2000);

//Act
var actual = _context.Blogs.All("BlogId > 2000");

//Assert
Assert.Equal(expected, actual);
}
}
}
30 changes: 30 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/EntitiesTests.AllAsync.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading