diff --git a/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj b/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj index 02ff2bfc..0e2bf425 100644 --- a/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj +++ b/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj @@ -31,9 +31,9 @@ - True + true diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs index afd46754..51ee6fee 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq/EFDynamicQueryableExtensions.cs @@ -693,6 +693,93 @@ public static Task LastOrDefaultAsync([NotNull] this IQueryable source, } #endregion LastOrDefault + #region LongCount + private static readonly MethodInfo _longCount = GetMethod(nameof(Queryable.LongCount)); + + /// + /// Asynchronously returns the number of elements in a sequence. + /// + /// + /// 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 that contains the elements to be counted. + /// + /// + /// A to observe while waiting for the task to complete. + /// + /// + /// A task that represents the asynchronous operation. + /// The task result contains the number of elements in the input sequence. + /// + [PublicAPI] + public static Task LongCountAsync([NotNull] this IQueryable source, CancellationToken cancellationToken = default(CancellationToken)) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + return ExecuteAsync(_longCount, source, cancellationToken); + } + + private static readonly MethodInfo _longCountPredicate = GetMethod(nameof(Queryable.LongCount), 1); + + /// + /// Asynchronously returns the number of elements in a sequence that satisfy a condition. + /// + /// + /// 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 that contains the elements to be counted. + /// + /// 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 the number of elements in the sequence that satisfy the condition in the predicate + /// function. + /// + [PublicAPI] + public static Task LongCountAsync([NotNull] this IQueryable source, [NotNull] string predicate, [CanBeNull] params object[] args) + { + return LongCountAsync(source, default(CancellationToken), predicate, args); + } + + /// + /// Asynchronously returns the number of elements in a sequence that satisfy a condition. + /// + /// + /// 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 that contains the elements to be counted. + /// + /// 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 the number of elements in the sequence that satisfy the condition in the predicate + /// function. + /// + [PublicAPI] + public static Task LongCountAsync([NotNull] this IQueryable source, CancellationToken cancellationToken, [NotNull] string predicate, [CanBeNull] params object[] args) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(predicate, nameof(predicate)); + Check.NotNull(cancellationToken, nameof(cancellationToken)); + + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(false, source.ElementType, null, predicate, args); + + return ExecuteAsync(_longCountPredicate, source, Expression.Quote(lambda), cancellationToken); + } + #endregion LongCount + #region SingleOrDefaultAsync private static readonly MethodInfo _singleOrDefault = GetMethod(nameof(Queryable.SingleOrDefault)); diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj b/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj index eb35df4e..533074e0 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj @@ -35,9 +35,9 @@ - True + true diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index 2da1c69f..85be5ae9 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -1120,6 +1120,79 @@ public static dynamic LastOrDefault([NotNull] this IQueryable source, [NotNull] } #endregion LastOrDefault + #region LongCount + private static readonly MethodInfo _longCount = GetMethod(nameof(Queryable.LongCount)); + + /// + /// Returns the number of elements in a sequence. + /// + /// The that contains the elements to be counted. + /// + /// + /// IQueryable queryable = employees.AsQueryable(); + /// var result = queryable.LongCount(); + /// + /// + /// The number of elements in the input sequence. + public static long LongCount([NotNull] this IQueryable source) + { + Check.NotNull(source, nameof(source)); + + return Execute(_longCount, source); + } + + private static readonly MethodInfo _longCountPredicate = GetMethod(nameof(Queryable.LongCount), 1); + + /// + /// Returns the number of elements in a sequence. + /// + /// The that contains the elements to be counted. + /// 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 result1 = queryable.LongCount("Income > 50"); + /// var result2 = queryable.LongCount("Income > @0", 50); + /// var result3 = queryable.Select("Roles.LongCount()"); + /// + /// + /// The number of elements in the specified sequence that satisfies a condition. + [PublicAPI] + public static long LongCount([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 Execute(_longCountPredicate, source, lambda); + } + + /// + public static long LongCount([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + { + return LongCount(source, ParsingConfig.Default, predicate, args); + } + + /// + /// Returns the number of elements in a sequence. + /// + /// The that contains the elements to be counted. + /// A cached Lambda Expression. + /// The number of elements in the specified sequence that satisfies a condition. + public static long LongCount([NotNull] this IQueryable source, [NotNull] LambdaExpression lambda) + { + Check.NotNull(source, nameof(source)); + Check.NotNull(lambda, nameof(lambda)); + + return Execute(_longCountPredicate, source, lambda); + } + #endregion LongCount + #region OfType private static readonly MethodInfo _ofType = GetGenericMethod(nameof(Queryable.OfType)); diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs index c561694a..3fd1ebb2 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs @@ -32,6 +32,8 @@ internal interface IEnumerableSignatures void Last(bool predicate); void LastOrDefault(); void LastOrDefault(bool predicate); + void LongCount(); + void LongCount(bool predicate); void Max(object selector); void Min(object selector); void OfType(string type); diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs index 2e188ae4..3574bed0 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs @@ -31,6 +31,8 @@ internal interface IQueryableSignatures void Last(bool predicate); void LastOrDefault(); void LastOrDefault(bool predicate); + void LongCount(); + void LongCount(bool predicate); void Max(object selector); void Min(object selector); void OfType(string type); diff --git a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj index 6399ff70..024e56c1 100644 --- a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj +++ b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj @@ -28,9 +28,9 @@ - True + true diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCount.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCount.cs new file mode 100644 index 00000000..834db30a --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCount.cs @@ -0,0 +1,28 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Entities; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public void Entities_LongCount_Predicate() + { + const string search = "a"; + + //Arrange + var blog1 = new Blog { Name = "blog a", BlogId = 1000, Created = DateTime.Now }; + var blog2 = new Blog { Name = "blog b", BlogId = 3000, Created = DateTime.Now }; + _context.Blogs.Add(blog1); + _context.Blogs.Add(blog2); + _context.SaveChanges(); + + //Act + long expected = _context.Blogs.LongCount(b => b.Name.Contains(search)); + long result = _context.Blogs.LongCount("Name.Contains(@0)", search); + + //Assert + Assert.Equal(expected, result); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCountAsync.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCountAsync.cs new file mode 100644 index 00000000..6b825bad --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.LongCountAsync.cs @@ -0,0 +1,44 @@ +#if EFCORE +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.DynamicLinq; +#else +using System.Data.Entity; +using EntityFramework.DynamicLinq; +#endif +using System.Linq.Expressions; +using System.Threading.Tasks; +using System.Linq.Dynamic.Core.Tests.Helpers.Entities; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class EntitiesTests + { + [Fact] + public async Task Entities_LongCountAsync_Predicate_Args() + { + const string search = "a"; + + //Arrange + var blog1 = new Blog { Name = "blog a", BlogId = 1000, Created = DateTime.Now }; + var blog2 = new Blog { Name = "blog b", BlogId = 3000, Created = DateTime.Now }; + _context.Blogs.Add(blog1); + _context.Blogs.Add(blog2); + _context.SaveChanges(); + + Expression> predicate = b => b.Name.Contains(search); + +#if EFCORE + var expected = await EntityFrameworkQueryableExtensions.LongCountAsync(_context.Blogs, predicate); +#else + var expected = await QueryableExtensions.LongCountAsync(_context.Blogs, predicate); +#endif + + //Act + long result = await (_context.Blogs as IQueryable).LongCountAsync("Name.Contains(@0)", search); + + //Assert + Assert.Equal(expected, result); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Count.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Count.cs index 3a8c99af..f9e4c320 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Count.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Count.cs @@ -53,5 +53,35 @@ public void Count_Predicate_WithArgs() //Assert Assert.Equal(expected, result); } + + [Fact] + public void Count_Dynamic_Select() + { + // Arrange + IQueryable queryable = User.GenerateSampleModels(1).AsQueryable(); + + // Act + var expected = queryable.Select(x => x.Roles.Count()).ToArray(); + var result = queryable.Select("Roles.Count()").ToDynamicArray(); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void Count_Dynamic_Where() + { + const string search = "e"; + + // Arrange + var testList = User.GenerateSampleModels(10); + var queryable = testList.AsQueryable(); + + // Act + var expected = queryable.Where(u => u.Roles.Count(r => r.Name.Contains(search)) > 0).ToArray(); + var result = queryable.Where("Roles.Count(Name.Contains(@0)) > 0", search).ToArray(); + + Assert.Equal(expected, result); + } } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.LongCount.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.LongCount.cs new file mode 100644 index 00000000..22697c13 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.LongCount.cs @@ -0,0 +1,87 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public partial class QueryableTests + { + [Fact] + public void LongCount() + { + //Arrange + IQueryable testListFull = User.GenerateSampleModels(100).AsQueryable(); + IQueryable testListOne = User.GenerateSampleModels(1).AsQueryable(); + IQueryable testListNone = User.GenerateSampleModels(0).AsQueryable(); + + //Act + var resultFull = testListFull.LongCount(); + var resultOne = testListOne.LongCount(); + var resultNone = testListNone.LongCount(); + + //Assert + Assert.Equal(100, resultFull); + Assert.Equal(1, resultOne); + Assert.Equal(0, resultNone); + } + + [Fact] + public void LongCount_Predicate() + { + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); + + //Act + long expected = queryable.LongCount(u => u.Income > 50); + long result = queryable.LongCount("Income > 50"); + + //Assert + Assert.Equal(expected, result); + } + + [Fact] + public void LongCount_Predicate_WithArgs() + { + const int value = 50; + + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); + + //Act + long expected = queryable.LongCount(u => u.Income > value); + long result = queryable.LongCount("Income > @0", value); + + //Assert + Assert.Equal(expected, result); + } + + [Fact] + public void LongCount_Dynamic_Select() + { + // Arrange + IQueryable queryable = User.GenerateSampleModels(1).AsQueryable(); + + // Act + var expected = queryable.Select(x => x.Roles.LongCount()).ToArray(); + var result = queryable.Select("Roles.LongCount()").ToDynamicArray(); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void LongCount_Dynamic_Where() + { + const string search = "e"; + + // Arrange + var testList = User.GenerateSampleModels(10); + var queryable = testList.AsQueryable(); + + // Act + var expected = queryable.Where(u => u.Roles.LongCount(r => r.Name.Contains(search)) > 0).ToArray(); + var result = queryable.Where("Roles.LongCount(Name.Contains(@0)) > 0", search).ToArray(); + + Assert.Equal(expected, result); + } + } +}