From 74e0bf5f390d54939a9511c7dc727cded09ea5fe Mon Sep 17 00:00:00 2001 From: JaleChaki Date: Mon, 31 Aug 2026 13:25:38 +0400 Subject: [PATCH 1/3] fix DataSourceExpressionBuilder.BuildGroupCountExpr --- .../DataSourceExpressionBuilderTests.cs | 27 +++++++++++++++++++ .../RemoteGroupingTests.cs | 21 +++++++++++++++ .../DataSourceExpressionBuilder.cs | 17 ++++++++---- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs b/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs index 73121243..26f494ec 100644 --- a/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs +++ b/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs @@ -339,6 +339,33 @@ string BuildExpr(DataSourceLoadOptionsBase options) => Compat.CreateDataSourceEx }) ); } + + [Fact] + public void BuildGroupCountExpr_IntervalGrouping() { + string BuildExpr(DataSourceLoadOptionsBase options) => Compat.CreateDataSourceExpressionBuilder>(options) + .BuildGroupCountExpr() + .ToString(); + + Assert.Equal( + "data.Where(obj => (obj.Item1 == 1))" + + ".GroupBy(obj => new AnonType`1(I0 = Convert(obj.Item2.Year, Nullable`1)))" + + ".OrderBy(g => g.Key.I0)" + + ".Select(g => new AnonType`2(I0 = g.Count(), I1 = g.Key.I0))" + + ".Count()", + + BuildExpr(new SampleLoadOptions { + GuardNulls = false, + Filter = new[] { "Item1", "1" }, + RequireGroupCount = true, + Group = new[] { + new GroupingInfo { Selector = "Item2", GroupInterval = "year" } + }, + GroupSummary = new[] { + new SummaryInfo { Selector = "Item2", SummaryType = "max" } + } + }) + ); + } } } diff --git a/net/DevExtreme.AspNet.Data.Tests/RemoteGroupingTests.cs b/net/DevExtreme.AspNet.Data.Tests/RemoteGroupingTests.cs index 86cbd108..9aac88f5 100644 --- a/net/DevExtreme.AspNet.Data.Tests/RemoteGroupingTests.cs +++ b/net/DevExtreme.AspNet.Data.Tests/RemoteGroupingTests.cs @@ -268,6 +268,27 @@ public void RequireGroupCount_MultiLevels() { Assert.Equal(2, loadResult.groupCount); } + [Fact] + public void RequireGroupCount_Intervals() { + var source = new[] { + new { G1 = new DateTime(2025, 1, 1), PK = 0 }, + new { G1 = new DateTime(2025, 2, 2), PK = 1 }, + new { G1 = new DateTime(2026, 3, 3), PK = 2 } + }; + + var loadOptions = new SampleLoadOptions { + RemoteGrouping = true, + RequireGroupCount = true, + Group = new[] { + new GroupingInfo { Selector = "G1", IsExpanded = false, GroupInterval = "year" }, + }, + Skip = 1 + }; + + var loadResult = DataSourceLoader.Load(source, loadOptions); + Assert.Equal(2, loadResult.groupCount); + } + [Fact] public void Summary_MissingOverload() { // Neither of Min, Max, Sum, Average provides an overload for byte sequences diff --git a/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs b/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs index a21a003d..9b7deac0 100644 --- a/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs +++ b/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs @@ -33,7 +33,7 @@ public Expression BuildCountExpr() { public Expression BuildLoadGroupsExpr(bool paginate, bool suppressGroups = false, bool suppressTotals = false) { AddFilter(); - AddRemoteGrouping(suppressGroups, suppressTotals); + AddRemoteGrouping(suppressGroups, suppressTotals, suppressGroups); if(paginate) AddPaging(); return Expr; @@ -41,8 +41,15 @@ public Expression BuildLoadGroupsExpr(bool paginate, bool suppressGroups = false public Expression BuildGroupCountExpr() { AddFilter(); - Expr = CreateSelectCompiler().CompileSingle(Expr, Context.Group.Single().Selector); - Expr = QueryableCall(nameof(Queryable.Distinct)); + + var group = Context.Group.Single(); + if(String.IsNullOrEmpty(group.GroupInterval)) { + Expr = CreateSelectCompiler().CompileSingle(Expr, Context.Group.Single().Selector); + Expr = QueryableCall(nameof(Queryable.Distinct)); + } else { + AddRemoteGrouping(false, true, true); + } + AddCount(); return Expr; } @@ -75,12 +82,12 @@ void AddPaging() { Expr = QueryableCall(nameof(Queryable.Take), Expression.Constant(Context.Take)); } - void AddRemoteGrouping(bool suppressGroups, bool suppressTotals) { + void AddRemoteGrouping(bool suppressGroups, bool suppressTotals, bool suppressGroupSummary) { var compiler = new RemoteGroupExpressionCompiler( GetItemType(), Context.GuardNulls, Context.ExpandLinqSumType, Context.CreateAnonTypeNewTweaks(), suppressGroups ? null : Context.Group, suppressTotals ? null : Context.TotalSummary, - suppressGroups ? null : Context.GroupSummary + suppressGroupSummary ? null : Context.GroupSummary ); Expr = compiler.Compile(Expr); } From 4a5cd4f49d9568bf4d5f092879b12a4a274f77dc Mon Sep 17 00:00:00 2001 From: JaleChaki Date: Tue, 1 Sep 2026 12:08:47 +0400 Subject: [PATCH 2/3] fix test for net framework --- .../DataSourceExpressionBuilderTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs b/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs index 26f494ec..7ae9c87d 100644 --- a/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs +++ b/net/DevExtreme.AspNet.Data.Tests/DataSourceExpressionBuilderTests.cs @@ -348,7 +348,13 @@ string BuildExpr(DataSourceLoadOptionsBase options) => Compat.CreateDataSourceEx Assert.Equal( "data.Where(obj => (obj.Item1 == 1))" + + // "Convert" expression is displayed differently in the .NET Framework and .NET Core + // https://github.com/microsoft/referencesource/blob/ec9fa9ae770d522a5b5f0607898044b7478574a3/System.Core/Microsoft/Scripting/Ast/ExpressionStringBuilder.cs#L657 +#if NET4 + ".GroupBy(obj => new AnonType`1(I0 = Convert(obj.Item2.Year)))" + +#else ".GroupBy(obj => new AnonType`1(I0 = Convert(obj.Item2.Year, Nullable`1)))" + +#endif ".OrderBy(g => g.Key.I0)" + ".Select(g => new AnonType`2(I0 = g.Count(), I1 = g.Key.I0))" + ".Count()", From a1731923d8ce635ca194a8b583fd4da7c150a168 Mon Sep 17 00:00:00 2001 From: JaleChaki Date: Tue, 1 Sep 2026 13:38:39 +0400 Subject: [PATCH 3/3] review 1 --- net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs b/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs index 9b7deac0..60957371 100644 --- a/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs +++ b/net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs @@ -44,7 +44,7 @@ public Expression BuildGroupCountExpr() { var group = Context.Group.Single(); if(String.IsNullOrEmpty(group.GroupInterval)) { - Expr = CreateSelectCompiler().CompileSingle(Expr, Context.Group.Single().Selector); + Expr = CreateSelectCompiler().CompileSingle(Expr, group.Selector); Expr = QueryableCall(nameof(Queryable.Distinct)); } else { AddRemoteGrouping(false, true, true);