瀏覽代碼

- [Vitorm] new feature to query TotalCount(Vit.Linq.Queryable_Extensions.TotalCount)
and query List and TotalCount(Vit.Linq.Queryable_Extensions.ToListAndTotalCount) at one request

Lith 10 月之前
父節點
當前提交
dd1913d695

+ 3 - 0
README.md

@@ -46,6 +46,9 @@ supported features:
 | --- | --- | --- | --- |
 |  change table   |  ChangeTable    |  change mapping table from database   |   |
 |  change database  |  ChangeDatabase   | change database to be connected  |   |
+| --- | --- | --- | --- |
+|  collection total count   |  TotalCount    |  Collection Total Count without Take and Skip   |   |
+|  collection total count and list  |  ToListAndTotalCount   | query List and TotalCount at on request  |   |
 |     |     |   |   |
 
 

+ 2 - 0
doc/ReleaseLog.md

@@ -13,6 +13,8 @@
 - [Vitorm.ClickHouse] fix String.Add null value and cast issue : ifNull(  cast( (userFatherId) as Nullable(String) ) , ''  )
 - [Vitorm.Sqlite] fix String.Add null value and cast issue
 - [Vitorm] new feature to change mapped table and change database for sharding
+- [Vitorm] new feature to query TotalCount(Vit.Linq.Queryable_Extensions.TotalCount) 
+           and query List and TotalCount(Vit.Linq.Queryable_Extensions.ToListAndTotalCount) at one request
 
 -----------------------
 # 1.1.0

+ 2 - 2
src/Vitorm.SqlServer/SqlTranslate/BaseQueryTranslateService.cs

@@ -176,7 +176,7 @@ SELECT *
 FROM (
     {sql}
 ) AS [t]
-WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take > 0 ? "AND [t].[__RowNumber__] <= " + (stream.take + stream.skip) : "")} ;
+WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take.HasValue ? "AND [t].[__RowNumber__] <= " + (stream.take + stream.skip) : "")} ;
 ";
             #endregion
         }
@@ -190,7 +190,7 @@ WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take > 0 ? "AND [t].[__RowNum
             string sql = "select ";
 
             // #0  select
-            if (stream.take != null) sql += "top " + stream.take + " ";
+            if (stream.take.HasValue) sql += "top " + stream.take + " ";
             sql += ReadSelect(arg, stream, prefix: null);
 
 

+ 15 - 9
src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs

@@ -1,4 +1,7 @@
 using System;
+using System.Linq;
+
+using Vit.Linq;
 
 using Vitorm.Sql.DataReader;
 using Vitorm.Sql.SqlTranslate;
@@ -27,7 +30,7 @@ ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
         {
         }
 
-  
+
         public override string BuildCountQuery(QueryTranslateArgument arg, CombinedStream stream)
         {
             // select count(*) from (select distinct fatherid,motherId from "User" u) u;
@@ -38,14 +41,7 @@ ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
         {
             switch (stream.method)
             {
-                case "Count":
-                    //return prefix + " " + "count(*)";
-                case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
-                    {
-                        var reader = new DataReader();
-                        return prefix + " " + BuildDataReader(arg, stream, reader);
-                    }
-                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last):
                     {
                         stream.take = 1;
                         stream.skip = null;
@@ -57,6 +53,16 @@ ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
                         var reader = new DataReader_FirstRow { nullable = nullable };
                         return prefix + " " + BuildDataReader(arg, stream, reader);
                     }
+
+                case nameof(Queryable.Count) or nameof(Queryable_Extensions.TotalCount):
+                //return prefix + " " + "count(*)";
+
+                case "" or null or nameof(Enumerable.ToList):
+                case nameof(Orm_Extensions.ToExecuteString):
+                    {
+                        var reader = new DataReader();
+                        return prefix + " " + BuildDataReader(arg, stream, reader);
+                    }
             }
             throw new NotSupportedException("not supported method: " + stream.method);
         }

+ 128 - 51
src/Vitorm/Sql/SqlDbContext.cs

@@ -87,17 +87,24 @@ namespace Vitorm.Sql
         protected virtual string dbGroupName => "SqlDbSet_" + dbConnectionProvider.dbHashCode;
         public virtual string databaseName => dbConnectionProvider.databaseName;
 
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="sqlTranslateService"></param>
-        /// <param name="dbConnectionProvider"></param>
-        /// <param name="sqlExecutor"></param>
-        public virtual void Init(ISqlTranslateService sqlTranslateService, DbConnectionProvider dbConnectionProvider, SqlExecutor sqlExecutor = null)
+
+        public virtual void Init(ISqlTranslateService sqlTranslateService, DbConnectionProvider dbConnectionProvider, SqlExecutor sqlExecutor = null, Dictionary<string, object> extraConfig = null)
         {
             this.sqlTranslateService = sqlTranslateService;
             this.dbConnectionProvider = dbConnectionProvider;
             this.sqlExecutor = sqlExecutor ?? SqlExecutor.Instance;
+
+            extraConfig?.ForEach(kv =>
+            {
+                switch (kv.Key)
+                {
+                    case nameof(query_ToListAndTotalCount_InvokeInOneExecute):
+                        {
+                            if (kv.Value is bool invokeInOneExecute) query_ToListAndTotalCount_InvokeInOneExecute = invokeInOneExecute;
+                            break;
+                        }
+                }
+            });
         }
 
 
@@ -276,21 +283,26 @@ namespace Vitorm.Sql
 
         }
 
+        public override IQueryable<Entity> Query<Entity>()
+        {
+            return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
+        }
+
         protected bool QueryIsFromSameDb(object query, Type elementType)
         {
             return dbGroupName == QueryableBuilder.GetQueryConfig(query as IQueryable) as string;
         }
         public Action<SqlDbContext, Expression, Type, object> AfterQuery;
-        protected object QueryExecutor(Expression expression, Type type)
+        protected object QueryExecutor(Expression expression, Type expressionResultType)
         {
             object result = null;
             try
             {
-                return result = ExecuteQuery(expression, type);
+                return result = ExecuteQuery(expression, expressionResultType);
             }
             finally
             {
-                AfterQuery?.Invoke(this, expression, type, result);
+                AfterQuery?.Invoke(this, expression, expressionResultType, result);
             }
         }
         public virtual SqlDbContext AutoDisposeAfterQuery()
@@ -299,7 +311,7 @@ namespace Vitorm.Sql
             return this;
         }
 
-        protected virtual object ExecuteQuery(Expression expression, Type type)
+        protected virtual object ExecuteQuery(Expression expression, Type expressionResultType)
         {
             // #1 convert to ExpressionNode 
             ExpressionNode node = convertService.ConvertToLambdaData(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
@@ -341,46 +353,20 @@ namespace Vitorm.Sql
                         (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
                         return sql;
                     }
-                case "Count":
-                    {
-                        // Count
-
-                        // deal with skip and take , no need to pass to PrepareCountQuery
-                        combinedStream.orders = null;
-                        (int skip, int? take) range = (combinedStream.skip ?? 0, combinedStream.take);
-                        combinedStream.take = null;
-                        combinedStream.skip = null;
-
-                        // get arg
-                        var arg = new QueryTranslateArgument(this, null);
-
-                        (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
-
-                        var count = ExecuteScalar(sql: sql, param: sqlParam, useReadOnly: true);
-                        var rowCount = Convert.ToInt32(count);
-                        if (rowCount > 0)
-                        {
-                            if (range.skip > 0) rowCount = Math.Max(rowCount - range.skip, 0);
-
-                            if (combinedStream.take.HasValue)
-                                rowCount = Math.Min(rowCount, range.take.Value);
-                        }
-                        return rowCount;
-                    }
                 case nameof(Orm_Extensions.ExecuteDelete):
                     {
                         // ExecuteDelete
 
                         // get arg
-                        var resultEntityType = (combinedStream.source as SourceStream)?.GetEntityType();
-                        var arg = new QueryTranslateArgument(this, resultEntityType);
+                        var entityType = (combinedStream.source as SourceStream)?.GetEntityType();
+                        var arg = new QueryTranslateArgument(this, entityType);
 
                         (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
 
                         var count = Execute(sql: sql, param: sqlParam);
                         return count;
                     }
-                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last):
                     {
                         // get arg
                         var resultEntityType = expression.Type;
@@ -391,28 +377,119 @@ namespace Vitorm.Sql
                         using var reader = ExecuteReader(sql: sql, param: sqlParam, useReadOnly: true);
                         return dataReader.ReadData(reader);
                     }
-                case "ToList":
+                case nameof(Queryable.Count) or nameof(Queryable_Extensions.TotalCount):
+                    {
+                        return ExecuteQuery_Count(combinedStream);
+                    }
+                case nameof(Queryable_Extensions.ToListAndTotalCount): 
+                    {
+                        var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault()?.GetGenericArguments()?.FirstOrDefault();
+                        return ExecuteQuery_ToListAndTotalCount(combinedStream, resultEntityType);
+                    }
+                case nameof(Enumerable.ToList):
                 case "":
                 case null:
                     {
                         // ToList
-
-                        // get arg
                         var resultEntityType = expression.Type.GetGenericArguments()?.FirstOrDefault();
-                        var arg = new QueryTranslateArgument(this, resultEntityType);
-
-                        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
-
-                        using var reader = ExecuteReader(sql: sql, param: sqlParam, useReadOnly: true);
-                        return dataReader.ReadData(reader);
+                        return ExecuteQuery_ToList(combinedStream, resultEntityType);
                     }
             }
             throw new NotSupportedException("not supported query type: " + combinedStream.method);
         }
 
-        public override IQueryable<Entity> Query<Entity>()
+        protected bool query_ToListAndTotalCount_InvokeInOneExecute = true;
+        protected virtual object ExecuteQuery_ToListAndTotalCount(CombinedStream combinedStream, Type resultEntityType)
         {
-            return QueryableBuilder.Build<Entity>(QueryExecutor, dbGroupName);
+            object list; int totalCount;
+
+            if (query_ToListAndTotalCount_InvokeInOneExecute)
+            {
+                // get arg
+                var arg = new QueryTranslateArgument(this, resultEntityType);
+
+                string sqlToList, sqlCount;
+                IDbDataReader dataReader; Dictionary<string, object> sqlParam;
+                // #1 ToList
+                {
+                    combinedStream.method = nameof(Enumerable.ToList);
+                    (sqlToList, sqlParam, dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+                }
+
+                // #2 TotalCount
+                {
+                    combinedStream.method = nameof(Enumerable.Count);
+                    (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null, null, null);
+
+                    (sqlCount, sqlParam) = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
+                }
+
+                // #3 read data
+                {
+                    var sql = sqlCount + " ;\r\n" + sqlToList;
+                    using var reader = ExecuteReader(sql: sql, param: sqlParam, useReadOnly: true);
+                    reader.Read();
+                    totalCount = Convert.ToInt32(reader[0]);
+                    reader.NextResult();
+                    list = dataReader.ReadData(reader);
+                }
+            }
+            else
+            {
+                combinedStream.method = nameof(Enumerable.ToList);
+                list = ExecuteQuery_ToList(combinedStream, resultEntityType);
+
+                combinedStream.method = nameof(Queryable_Extensions.TotalCount);
+                totalCount = ExecuteQuery_Count(combinedStream);
+            }
+
+            //combinedStream.method = nameof(Queryable_Extensions.ToListAndTotalCount);
+
+            return new Func<object, int, (object, int)>(ValueTuple.Create<object, int>)
+                .Method.GetGenericMethodDefinition()
+                .MakeGenericMethod(list.GetType(), typeof(int))
+                .Invoke(null, new[] { list, totalCount });
+        }
+
+
+        /// <summary>
+        /// Queryable.Count or Queryable_Extensions.TotalCount
+        /// </summary>
+        /// <param name="combinedStream"></param>
+        /// <returns></returns>
+        protected virtual int ExecuteQuery_Count(CombinedStream combinedStream) 
+        {
+            // deal with skip and take , no need to pass to PrepareCountQuery
+            var queryArg = (combinedStream.orders,combinedStream.skip  , combinedStream.take);
+            (combinedStream.orders, combinedStream.skip, combinedStream.take) = (null,null,null);
+
+            // get arg
+            var arg = new QueryTranslateArgument(this, null);
+
+            (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
+
+            var countValue = ExecuteScalar(sql: sql, param: sqlParam, useReadOnly: true);
+            var count = Convert.ToInt32(countValue);
+            if (count > 0 && combinedStream.method == nameof(Queryable.Count))
+            {
+                if (queryArg.skip > 0) count = Math.Max(count - queryArg.skip.Value, 0);
+
+                if (queryArg.take.HasValue)
+                    count = Math.Min(count, queryArg.take.Value);
+            }
+
+            (combinedStream.orders, combinedStream.skip, combinedStream.take) = queryArg;
+            return count;
+        }
+
+        protected virtual object ExecuteQuery_ToList(CombinedStream combinedStream, Type resultEntityType)
+        {
+            var arg = new QueryTranslateArgument(this, resultEntityType);
+
+            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+
+            using var reader = ExecuteReader(sql: sql, param: sqlParam, useReadOnly: true);
+            return dataReader.ReadData(reader);
         }
 
         #endregion

+ 8 - 4
src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs

@@ -1,4 +1,7 @@
 using System;
+using System.Linq;
+
+using Vit.Linq;
 
 using Vitorm.Sql.DataReader;
 using Vitorm.StreamQuery;
@@ -27,20 +30,21 @@ namespace Vitorm.Sql.SqlTranslate
         public override string BuildCountQuery(QueryTranslateArgument arg, CombinedStream stream)
         {
             // select count(*) from (select distinct fatherid,motherId from "User" u) u;
-            return $"select count(*) from ({BuildQuery(arg, stream)}) u;";
+            return $"select count(*) from ({BuildQuery(arg, stream)}) u";
         }
 
         protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
         {
             switch (stream.method)
             {
-                case "Count":
-                case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
+                case nameof(Queryable.Count):
+                case "" or null or nameof(Enumerable.ToList) or nameof(Orm_Extensions.ToExecuteString):
+                case nameof(Queryable_Extensions.ToListAndTotalCount) or nameof(Queryable_Extensions.TotalCount):
                     {
                         var reader = new DataReader.DataReader();
                         return prefix + " " + BuildDataReader(arg, stream, reader);
                     }
-                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last):
                     {
                         stream.take = 1;
                         stream.skip = null;

+ 12 - 11
src/Vitorm/StreamQuery/StreamReader.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 
+using Vit.Linq;
 using Vit.Linq.ExpressionTree.ComponentModel;
 
 namespace Vitorm.StreamQuery
@@ -235,14 +236,14 @@ namespace Vitorm.StreamQuery
 
                         switch (call.methodName)
                         {
-                            case "Where":
+                            case nameof(Queryable.Where):
                                 {
                                     var predicateLambda = call.arguments[1] as ExpressionNode_Lambda;
                                     var stream = ReadStreamWithWhere(arg, source, predicateLambda);
                                     if (stream == default) break;
                                     return stream;
                                 }
-                            case "FirstOrDefault" or "First" or "LastOrDefault" or "Last" when call.arguments.Length == 2:
+                            case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last) when call.arguments.Length == 2:
                                 {
                                     var predicateLambda = call.arguments[1] as ExpressionNode_Lambda;
                                     var stream = ReadStreamWithWhere(arg, source, predicateLambda);
@@ -250,14 +251,14 @@ namespace Vitorm.StreamQuery
                                     stream.method = call.methodName;
                                     return stream;
                                 }
-                            case "Distinct":
+                            case nameof(Queryable.Distinct):
                                 {
                                     var combinedStream = AsCombinedStream(source);
 
                                     combinedStream.distinct = true;
                                     return combinedStream;
                                 }
-                            case "Select":
+                            case nameof(Queryable.Select):
                                 {
                                     ExpressionNode_Lambda resultSelector = call.arguments[1];
 
@@ -322,21 +323,20 @@ namespace Vitorm.StreamQuery
                                     }
                                     break;
                                 }
-                            case "Take":
-                            case "Skip":
+                            case nameof(Queryable.Take) or nameof(Queryable.Skip):
                                 {
                                     CombinedStream combinedStream = AsCombinedStream(source);
 
                                     var value = (call.arguments[1] as ExpressionNode_Constant)?.value as int?;
 
-                                    if (call.methodName == "Skip")
+                                    if (call.methodName == nameof(Queryable.Skip))
                                         combinedStream.skip = value;
                                     else
                                         combinedStream.take = value;
                                     return combinedStream;
                                 }
 
-                            case "OrderBy" or "OrderByDescending" or "ThenBy" or "ThenByDescending":
+                            case nameof(Queryable.OrderBy) or nameof(Queryable.OrderByDescending) or nameof(Queryable.ThenBy) or nameof(Queryable.ThenByDescending):
                                 {
                                     CombinedStream combinedStream = AsCombinedStream(source);
 
@@ -357,11 +357,12 @@ namespace Vitorm.StreamQuery
 
                                     return combinedStream;
                                 }
-                            case "FirstOrDefault" or "First" or "LastOrDefault" or "Last" when call.arguments.Length == 1:
-                            case "Count":
+                            case nameof(Queryable.FirstOrDefault) or nameof(Queryable.First) or nameof(Queryable.LastOrDefault) or nameof(Queryable.Last) when call.arguments.Length == 1:
+                            case nameof(Queryable.Count):
+                            case nameof(Queryable_Extensions.ToListAndTotalCount) or nameof(Queryable_Extensions.TotalCount):
                             case nameof(Orm_Extensions.ExecuteDelete):
                             case nameof(Orm_Extensions.ToExecuteString):
-                            case "ToList":
+                            case nameof(Enumerable.ToList):
                                 {
                                     if (call.arguments?.Length != 1) break;
 

+ 20 - 1
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Count_Test.cs

@@ -26,7 +26,26 @@ namespace Vitorm.MsTest.CommonTest
             using var dbContext = DataSource.CreateDbContext();
             var userQuery = dbContext.Query<User>();
 
-            TestAllCase(userQuery, new Config { skip = 1, take = 100 });
+            TestAllCase(userQuery, new Config { skip = 0 });
+            TestAllCase(userQuery, new Config { skip = 1 });
+            TestAllCase(userQuery, new Config { skip = 10 });
+
+            TestAllCase(userQuery, new Config { take = 0 });
+            TestAllCase(userQuery, new Config { take = 2 });
+            TestAllCase(userQuery, new Config { take = 20 });
+
+
+            TestAllCase(userQuery, new Config { skip = 0, take = 0 });
+            TestAllCase(userQuery, new Config { skip = 0, take = 2 });
+            TestAllCase(userQuery, new Config { skip = 0, take = 20 });
+
+            TestAllCase(userQuery, new Config { skip = 1, take = 0 });
+            TestAllCase(userQuery, new Config { skip = 1, take = 2 });
+            TestAllCase(userQuery, new Config { skip = 1, take = 20 });
+
+            TestAllCase(userQuery, new Config { skip = 10, take = 0 });
+            TestAllCase(userQuery, new Config { skip = 10, take = 2 });
+            TestAllCase(userQuery, new Config { skip = 10, take = 20 });
         }
 
 

+ 79 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_ToListAndTotalCount_Test.cs

@@ -0,0 +1,79 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_LinqMethods_ToListAndTotalCount_Test
+    {
+
+        [TestMethod]
+        public void Test_ToListAndTotalCount()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            var query = userQuery
+                     .Where(user => user.id > 2)
+                     .OrderBy(m => m.id)
+                     .Select(user => new { id = user.id, name = user.name })
+                     ;
+
+
+            Test(query, expectedCount: 4, expectedTotalCount: 4);
+
+
+            Test(query.Skip(0), expectedCount: 4, expectedTotalCount: 4);
+            Test(query.Skip(1), expectedCount: 3, expectedTotalCount: 4);
+            Test(query.Skip(10), expectedCount: 0, expectedTotalCount: 4);
+
+            Test(query.Take(0), expectedCount: 0, expectedTotalCount: 4);
+            Test(query.Take(2), expectedCount: 2, expectedTotalCount: 4);
+            Test(query.Take(20), expectedCount: 4, expectedTotalCount: 4);
+
+
+
+            Test(query.Skip(0).Take(0), expectedCount: 0, expectedTotalCount: 4);
+            Test(query.Skip(0).Take(2), expectedCount: 2, expectedTotalCount: 4);
+            Test(query.Skip(0).Take(10), expectedCount: 4, expectedTotalCount: 4);
+
+            Test(query.Skip(1).Take(0), expectedCount: 0, expectedTotalCount: 4);
+            Test(query.Skip(1).Take(2), expectedCount: 2, expectedTotalCount: 4);
+            Test(query.Skip(1).Take(10), expectedCount: 3, expectedTotalCount: 4);
+
+            Test(query.Skip(10).Take(0), expectedCount: 0, expectedTotalCount: 4);
+            Test(query.Skip(10).Take(2), expectedCount: 0, expectedTotalCount: 4);
+            Test(query.Skip(10).Take(10), expectedCount: 0, expectedTotalCount: 4);
+
+        }
+
+
+
+        void Test<T>(IQueryable<T> query, int expectedCount, int expectedTotalCount)
+        {
+            // Count
+            {
+                var count = query.Count();
+                Assert.AreEqual(expectedCount, count);
+            }
+            // TotalCount
+            {
+                var totalCount = query.TotalCount();
+                Assert.AreEqual(expectedTotalCount, totalCount);
+            }
+            // ToListAndTotalCount
+            {
+                var (list, totalCount) = query.ToListAndTotalCount();
+                Assert.AreEqual(expectedCount, list.Count);
+                Assert.AreEqual(expectedTotalCount, totalCount);
+            }
+        }
+
+
+
+    }
+}