Browse Source

fix Count issue (work for skip take and distinct)

Lith 10 tháng trước cách đây
mục cha
commit
b1b9724ee4
40 tập tin đã thay đổi với 1307 bổ sung406 xóa
  1. 2 4
      README.md
  2. 64 0
      doc/Count.md
  3. 1 2
      src/Vitorm.Data/README.md
  4. 3 25
      src/Vitorm.MySql/SqlTranslateService.cs
  5. 165 94
      src/Vitorm.SqlServer/SqlTranslate/BaseQueryTranslateService.cs
  6. 1 1
      src/Vitorm.SqlServer/SqlTranslate/ExecuteDeleteTranslateService.cs
  7. 1 1
      src/Vitorm.SqlServer/SqlTranslate/ExecuteUpdateTranslateService.cs
  8. 7 6
      src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs
  9. 6 29
      src/Vitorm.SqlServer/SqlTranslateService.cs
  10. 2 4
      src/Vitorm.Sqlite/README.md
  11. 3 26
      src/Vitorm.Sqlite/SqlTranslateService.cs
  12. 143 18
      src/Vitorm/Sql/DataReader/EntityReader.cs
  13. 2 2
      src/Vitorm/Sql/DataReader/EntityReader/ModelReader.EntityPropertyReader.cs
  14. 6 18
      src/Vitorm/Sql/DataReader/EntityReader/ModelReader.cs
  15. 4 10
      src/Vitorm/Sql/DataReader/EntityReader/SqlFieldReader.cs
  16. 2 2
      src/Vitorm/Sql/DataReader/EntityReader/ValueReader.cs
  17. 0 20
      src/Vitorm/Sql/DataReader/NumScalarReader.cs
  18. 16 2
      src/Vitorm/Sql/SqlDbContext.cs
  19. 18 9
      src/Vitorm/Sql/SqlTranslate/BaseQueryTranslateService.cs
  20. 1 0
      src/Vitorm/Sql/SqlTranslate/ISqlTranslateService.cs
  21. 6 6
      src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs
  22. 26 6
      src/Vitorm/Sql/SqlTranslate/SqlTranslateService.cs
  23. 9 0
      test/Vitorm.Data.Benchmark/01.Run benchmark.bat
  24. 4 0
      test/Vitorm.Data.Benchmark/02.Open result.bat
  25. 104 0
      test/Vitorm.Data.Benchmark/OtherTest/VitormBenchmark_ReduceMember.cs
  26. 36 91
      test/Vitorm.Data.Benchmark/Program.cs
  27. 15 0
      test/Vitorm.Data.Benchmark/QueryTest/IBenchmarkQuery.cs
  28. 95 0
      test/Vitorm.Data.Benchmark/QueryTest/QueryTest_EntityFramework.cs
  29. 100 0
      test/Vitorm.Data.Benchmark/QueryTest/QueryTest_Vitorm.cs
  30. 3 0
      test/Vitorm.Data.Benchmark/Vitorm.Data.Benchmark.csproj
  31. 1 2
      test/Vitorm.Data.Console/Program.cs
  32. 0 3
      test/Vitorm.Data.MsTest/CommonTest/SqliteReadOnly_Test.cs
  33. 0 6
      test/Vitorm.Data.MsTest/CommonTest/Sqlite_Test.cs
  34. 1 2
      test/Vitorm.Sqlite.Console/Program.cs
  35. 218 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Count_Test.cs
  36. 29 7
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Distinct_Test.cs
  37. 44 6
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Test.cs
  38. 89 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_ScopeParam_LeftJoin_Test.cs
  39. 80 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_ScopeParam_Test.cs
  40. 0 4
      test/Vitorm.Sqlite.MsTest/DataSource.cs

+ 2 - 4
README.md

@@ -52,8 +52,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 Configure Vitorm
             using var dbContext = new Vitorm.Sql.SqlDbContext();
             dbContext.UseSqlite("data source=sqlite.db");
 
@@ -211,8 +210,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm.Data
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 No need to init Vitorm.Data
 
             // #2 Create Table
             Data.Drop<User>();

+ 64 - 0
doc/Count.md

@@ -0,0 +1,64 @@
+ Because count function will exclude rows with non value, that's not what we want, so will use inner query for all query
+ ```
+  select count(distinct userFatherId) from [User];
+
+  select count(*) from (select distinct fatherid,motherId from "User" u) u;
+ 
+ ```
+
+
+# count
+```
+----------------
+select count(id)
+select count(*)
+select count(*) from (select fatherid,motherId from "User" u) u;
+----------------
+// single table
+[ ] users.Select(user => user.fatherId)
+[ ] users.Select(user => new { user.fatherId })
+[ ] users.Select(user => user)                                  // has key
+[ ] users.Select(user => new { user })                          // has key
+[ ] users.Select(user => new { user, user.fatherId })           // has key
+[ ] users.Select(user => new { user.id, user.fatherId })        // has key
+
+users.Select(user => user)                              // has no key
+users.Select(user => new { user })                      // has no key
+users.Select(user => new { user, user.fatherId })       // has no key
+users.Select(user => new { user.id, user.fatherId })    // has no key
+
+
+// multiple table
+[ ] users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user.fatherId)
+[ ] users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user.fatherId })
+
+[ ] users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user)                     // has key
+[ ] users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user })             // has key
+[ ] users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, user.id })    // has key
+
+users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, father })
+users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user)                     // has no key
+users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user })             // has no key
+users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, user.id })    // has no key
+
+
+```
+
+
+--------------------------------
+# distinct count
+```
+----------------
+select count(distinct id)
+select count(*) from (select distinct fatherid,motherId from "User" u) u;
+
+```
+
+
+--------------------------------
+# single column
+```
+select count(*) from [dbo].[User] as t0; 
+select count(userFatherId) from [dbo].[User];
+select count(distinct userFatherId) from [dbo].[User];
+```

+ 1 - 2
src/Vitorm.Data/README.md

@@ -58,8 +58,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm.Data
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 No need to init Vitorm.Data
 
             // #2 Create Table
             Data.Drop<User>();

+ 3 - 25
src/Vitorm.MySql/SqlTranslateService.cs

@@ -7,9 +7,7 @@ using Vit.Linq.ExpressionTree.ComponentModel;
 
 using Vitorm.Entity;
 using Vitorm.MySql.TranslateService;
-using Vitorm.Sql;
 using Vitorm.Sql.SqlTranslate;
-using Vitorm.StreamQuery;
 
 namespace Vitorm.MySql
 {
@@ -17,9 +15,9 @@ namespace Vitorm.MySql
     {
         public static readonly SqlTranslateService Instance = new SqlTranslateService();
 
-        protected QueryTranslateService queryTranslateService;
-        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
-        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+        protected override BaseQueryTranslateService queryTranslateService { get; }
+        protected override BaseQueryTranslateService executeUpdateTranslateService { get; }
+        protected override BaseQueryTranslateService executeDeleteTranslateService { get; }
 
         public SqlTranslateService()
         {
@@ -234,25 +232,5 @@ CREATE TABLE {DelimitTableName(entityDescriptor)} (
             return result;
         }
 
-        public override (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = queryTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam, arg.dataReader);
-        }
-
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeUpdateTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeDeleteTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-
-
     }
 }

+ 165 - 94
src/Vitorm.SqlServer/SqlTranslate/BaseQueryTranslateService.cs

@@ -1,5 +1,7 @@
-using System.Linq;
+using System;
+using System.Linq;
 
+using Vitorm.Sql.DataReader;
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
@@ -31,63 +33,136 @@ ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
         {
             if (stream.skip > 0)
             {
-                #region ROW_NUMBER() OVER(ORDER BY [m].[fatherId], [m].[motherId] DESC) AS [__RowNumber__]
-
-                string sql = "";
-
-
-                #region #0 select
-                sql += ReadSelect(arg, stream);
-
-                // , ROW_NUMBER() OVER(ORDER BY [m].[fatherId], [m].[motherId] DESC) AS [__RowNumber__]
-                if (stream.orders?.Any() == true)
+                if (stream.distinct != true)
                 {
-                    var orderBy = ReadOrderBy(arg, stream);
-                    sql += $", ROW_NUMBER() OVER(ORDER BY {orderBy}) AS [__RowNumber__]";
+                    return BuildQueryWithSkip(arg, stream);
                 }
                 else
                 {
-                    sql += $", ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]";
+                    string sql;
+
+                    var (skip, take, orders) = (stream.skip.Value, stream.take, stream.orders);
+                    (stream.skip, stream.take, stream.orders) = (null, null, null);
+
+                    var innerQuery = BuildQueryWithoutSkip(arg, stream);
+
+                    (stream.skip, stream.take, stream.orders) = (skip, take, orders);
+
+
+                    #region order by
+                    string sqlRowNumber;
+                    if (stream.orders?.Any() == true)
+                    {
+                        var sqlColumns = ((EntityReader)arg.dataReader).sqlColumns;
+                        var orderBy = ReadOrderBy(arg, stream);
+                        sqlRowNumber = $" ROW_NUMBER() OVER(ORDER BY {orderBy}) AS [__RowNumber__]";
+
+
+                        string ReadOrderBy(QueryTranslateArgument arg, CombinedStream stream)
+                        {
+                            var columns = stream.orders.Select(field =>
+                                {
+                                    var sqlColumnName = sqlTranslator.EvalExpression(arg, field.member);
+                                    var sqlColumnAlias = sqlColumns.GetColumnAliasBySqlColumnName(sqlColumnName);
+                                    if (sqlColumnAlias == null) throw new NotSupportedException("can not find sort column from result columns, sort column name:" + sqlColumnName);
+                                    return sqlColumnAlias + " " + (field.asc ? "asc" : "desc");
+                                }
+                            );
+                            return String.Join(", ", columns);
+                        }
+                    }
+                    else
+                    {
+                        sqlRowNumber = $" ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]";
+                    }
+                    #endregion
+
+                    #region page inner query
+                    /*
+SELECT *
+FROM (
+    SELECT *, ROW_NUMBER(ORDER BY @@RowCount) AS [__RowNumber__]
+    FROM ({innerQuery}) as [t]
+) AS [t]
+WHERE ([t].[__RowNumber__] > 1) AND ([t].[__RowNumber__] <= 13);
+ */
+                    sql = $@"
+                    SELECT *
+                    FROM (
+                        SELECT *, {sqlRowNumber}
+                        FROM ({innerQuery}) as [t]
+                    ) AS [t]
+                    WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take > 0 ? "AND [t].[__RowNumber__] <= " + (stream.take + stream.skip) : "")} ;
+                    ";
+                    #endregion
+
+                    return sql;
                 }
-                #endregion
+            }
+            else
+            {
+                return BuildQueryWithoutSkip(arg,stream);
+            }
+        }
 
+        public virtual string BuildQueryWithSkip(QueryTranslateArgument arg, CombinedStream stream) 
+        {
+            // ROW_NUMBER() OVER(ORDER BY [m].[fatherId], [m].[motherId] DESC) AS [__RowNumber__]
 
-                #region #1 from
-                sql += "\r\n from " + ReadInnerTable(arg, stream.source);
-                #endregion
+            string sql = "";
 
-                #region #2 join
-                if (stream.joins != null)
-                {
-                    sql += ReadJoin(arg, stream);
-                }
-                #endregion
 
-                // #3 where 1=1
-                if (stream.where != null)
-                {
-                    var where = sqlTranslator.EvalExpression(arg, stream.where);
-                    if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n where " + where;
-                }
+            #region #0 select
+            sql += ReadSelect(arg, stream);
 
-                #region #4 group by
-                if (stream.groupByFields != null)
-                {
-                    sql += "\r\n group by " + ReadGroupBy(arg, stream);
-                }
-                #endregion
+            if (stream.orders?.Any() == true)
+            {
+                var orderBy = ReadOrderBy(arg, stream);
+                sql += $", ROW_NUMBER() OVER(ORDER BY {orderBy}) AS [__RowNumber__]";
+            }
+            else
+            {
+                sql += $", ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]";
+            }
+            #endregion
 
-                #region #5 having
-                if (stream.having != null)
-                {
-                    var where = sqlTranslator.EvalExpression(arg, stream.having);
-                    if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n having " + where;
-                }
-                #endregion
 
+            #region #1 from
+            sql += "\r\n from " + ReadInnerTable(arg, stream.source);
+            #endregion
+
+            #region #2 join
+            if (stream.joins != null)
+            {
+                sql += ReadJoin(arg, stream);
+            }
+            #endregion
+
+            // #3 where 1=1
+            if (stream.where != null)
+            {
+                var where = sqlTranslator.EvalExpression(arg, stream.where);
+                if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n where " + where;
+            }
 
-                #region #6 Range
-                /*
+            #region #4 group by
+            if (stream.groupByFields != null)
+            {
+                sql += "\r\n group by " + ReadGroupBy(arg, stream);
+            }
+            #endregion
+
+            #region #5 having
+            if (stream.having != null)
+            {
+                var where = sqlTranslator.EvalExpression(arg, stream.having);
+                if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n having " + where;
+            }
+            #endregion
+
+
+            #region #6 Range
+            /*
 SELECT *
 FROM (
     SELECT *, ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
@@ -95,75 +170,71 @@ FROM (
     WHERE [m].[id] <> 2
 ) AS [t]
 WHERE ([t].[__RowNumber__] > 1) AND ([t].[__RowNumber__] <= 13);
-                 */
-                return $@"
+             */
+            return $@"
 SELECT *
 FROM (
     {sql}
 ) AS [t]
 WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take > 0 ? "AND [t].[__RowNumber__] <= " + (stream.take + stream.skip) : "")} ;
 ";
-
-                #endregion
+            #endregion
+        }
 
 
-                #endregion
 
-            }
-            else
-            {
-                #region select top 10 *
-
-                string sql = "select ";
+        public virtual string BuildQueryWithoutSkip(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            // "select * "   or   "select top 10 * "
 
-                // #0  select
-                if (stream.take != null) sql += "top " + stream.take + " ";
-                sql += ReadSelect(arg, stream, prefix: null);
+            string sql = "select ";
 
+            // #0  select
+            if (stream.take != null) sql += "top " + stream.take + " ";
+            sql += ReadSelect(arg, stream, prefix: null);
 
-                #region #1 from
-                sql += "\r\n from " + ReadInnerTable(arg, stream.source);
-                #endregion
 
-                #region #2 join
-                if (stream.joins != null)
-                {
-                    sql += ReadJoin(arg, stream);
-                }
-                #endregion
+            #region #1 from
+            sql += "\r\n from " + ReadInnerTable(arg, stream.source);
+            #endregion
 
-                // #3 where 1=1
-                if (stream.where != null)
-                {
-                    var where = sqlTranslator.EvalExpression(arg, stream.where);
-                    if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n where " + where;
-                }
+            #region #2 join
+            if (stream.joins != null)
+            {
+                sql += ReadJoin(arg, stream);
+            }
+            #endregion
 
-                #region #4 group by
-                if (stream.groupByFields != null)
-                {
-                    sql += "\r\n group by " + ReadGroupBy(arg, stream);
-                }
-                #endregion
+            // #3 where 1=1
+            if (stream.where != null)
+            {
+                var where = sqlTranslator.EvalExpression(arg, stream.where);
+                if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n where " + where;
+            }
 
-                #region #5 having
-                if (stream.having != null)
-                {
-                    var where = sqlTranslator.EvalExpression(arg, stream.having);
-                    if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n having " + where;
-                }
-                #endregion
+            #region #4 group by
+            if (stream.groupByFields != null)
+            {
+                sql += "\r\n group by " + ReadGroupBy(arg, stream);
+            }
+            #endregion
 
+            #region #5 having
+            if (stream.having != null)
+            {
+                var where = sqlTranslator.EvalExpression(arg, stream.having);
+                if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n having " + where;
+            }
+            #endregion
 
-                // #6 OrderBy
-                if (stream.orders?.Any() == true)
-                {
-                    sql += "\r\n order by " + ReadOrderBy(arg, stream);
-                }
 
-                return sql;
-                #endregion
+            // #6 OrderBy
+            if (stream.orders?.Any() == true)
+            {
+                sql += "\r\n order by " + ReadOrderBy(arg, stream);
             }
+
+            return sql;
         }
 
 

+ 1 - 1
src/Vitorm.SqlServer/SqlTranslate/ExecuteDeleteTranslateService.cs

@@ -1,7 +1,7 @@
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
-namespace Vitorm.SqlServer.TranslateService
+namespace Vitorm.SqlServer.SqlTranslate
 {
     public class ExecuteDeleteTranslateService : Vitorm.SqlServer.SqlTranslate.BaseQueryTranslateService
     {

+ 1 - 1
src/Vitorm.SqlServer/SqlTranslate/ExecuteUpdateTranslateService.cs

@@ -5,7 +5,7 @@ using System.Linq;
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
 
-namespace Vitorm.SqlServer.TranslateService
+namespace Vitorm.SqlServer.SqlTranslate
 {
     public class ExecuteUpdateTranslateService : Vitorm.SqlServer.SqlTranslate.BaseQueryTranslateService
     {

+ 7 - 6
src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs

@@ -1,6 +1,5 @@
 using System;
 
-using Vitorm.DataReader;
 using Vitorm.Sql.DataReader;
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.StreamQuery;
@@ -28,17 +27,19 @@ 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;
+            return $"select count(*) from ({BuildQuery(arg, stream)}) u;";
+        }
 
         protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
         {
             switch (stream.method)
             {
                 case "Count":
-                    {
-                        var reader = new NumScalarReader();
-                        arg.dataReader ??= reader;
-                        return prefix + " " + "count(*)";
-                    }
+                    //return prefix + " " + "count(*)";
                 case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
                     {
                         var reader = new EntityReader();

+ 6 - 29
src/Vitorm.SqlServer/SqlTranslateService.cs

@@ -6,10 +6,7 @@ using Vit.Linq;
 using Vit.Linq.ExpressionTree.ComponentModel;
 
 using Vitorm.Entity;
-using Vitorm.Sql;
 using Vitorm.Sql.SqlTranslate;
-using Vitorm.SqlServer.TranslateService;
-using Vitorm.StreamQuery;
 
 namespace Vitorm.SqlServer
 {
@@ -17,16 +14,16 @@ namespace Vitorm.SqlServer
     {
         public static readonly SqlTranslateService Instance = new SqlTranslateService();
 
-        protected Vitorm.SqlServer.SqlTranslate.QueryTranslateService queryTranslateService;
-        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
-        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+        protected override BaseQueryTranslateService queryTranslateService { get; }
+        protected override BaseQueryTranslateService executeUpdateTranslateService { get; }
+        protected override BaseQueryTranslateService executeDeleteTranslateService { get; }
 
 
         public SqlTranslateService()
         {
-            queryTranslateService = new(this);
-            executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
-            executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
+            queryTranslateService = new Vitorm.SqlServer.SqlTranslate.QueryTranslateService(this);
+            executeUpdateTranslateService = new Vitorm.SqlServer.SqlTranslate.ExecuteUpdateTranslateService(this);
+            executeDeleteTranslateService = new Vitorm.SqlServer.SqlTranslate.ExecuteDeleteTranslateService(this);
         }
 
         /// <summary>
@@ -164,8 +161,6 @@ namespace Vitorm.SqlServer
 
 
 
-
-
         #region PrepareCreate
         public override string PrepareCreate(IEntityDescriptor entityDescriptor)
         {
@@ -260,24 +255,6 @@ CREATE TABLE {DelimitTableName(entityDescriptor)} (
             return result;
         }
 
-        public override (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = queryTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam, arg.dataReader);
-        }
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeUpdateTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeDeleteTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-
 
     }
 }

+ 2 - 4
src/Vitorm.Sqlite/README.md

@@ -39,8 +39,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 Configure Vitorm
             using var dbContext = new Vitorm.Sql.SqlDbContext();
             dbContext.UseSqlite("data source=sqlite.db");
 
@@ -198,8 +197,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm.Data
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 No need to init Vitorm.Data
 
             // #2 Create Table
             Data.Drop<User>();

+ 3 - 26
src/Vitorm.Sqlite/SqlTranslateService.cs

@@ -6,10 +6,8 @@ using Vit.Linq;
 using Vit.Linq.ExpressionTree.ComponentModel;
 
 using Vitorm.Entity;
-using Vitorm.Sql;
 using Vitorm.Sql.SqlTranslate;
 using Vitorm.Sqlite.TranslateService;
-using Vitorm.StreamQuery;
 
 namespace Vitorm.Sqlite
 {
@@ -17,9 +15,9 @@ namespace Vitorm.Sqlite
     {
         public static readonly SqlTranslateService Instance = new SqlTranslateService();
 
-        protected QueryTranslateService queryTranslateService;
-        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
-        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+        protected override BaseQueryTranslateService queryTranslateService { get; }
+        protected override BaseQueryTranslateService executeUpdateTranslateService { get; }
+        protected override BaseQueryTranslateService executeDeleteTranslateService { get; }
 
         public SqlTranslateService()
         {
@@ -197,26 +195,5 @@ CREATE TABLE {DelimitTableName(entityDescriptor)} (
             return $@" DROP TABLE if exists {DelimitTableName(entityDescriptor)};";
         }
 
-
-        public override (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = queryTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam, arg.dataReader);
-        }
-
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeUpdateTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream)
-        {
-            string sql = executeDeleteTranslateService.BuildQuery(arg, combinedStream);
-            return (sql, arg.sqlParam);
-        }
-
-
-
     }
 }

+ 143 - 18
src/Vitorm/Sql/DataReader/EntityReader.cs

@@ -7,13 +7,107 @@ using System.Reflection;
 using Vit.Linq.ExpressionTree;
 using Vit.Linq.ExpressionTree.ComponentModel;
 
+using Vitorm.Entity;
 using Vitorm.Sql.SqlTranslate;
 
 namespace Vitorm.Sql.DataReader
 {
     public class EntityReader : IDbDataReader
     {
-        public List<string> sqlFields { get; private set; } = new List<string>();
+
+        public class SqlColumns
+        {
+            List<Column> columns = new();
+
+            /// <summary>
+            /// entity field , try get sql column and return sqlColumnIndex
+            /// </summary>
+            /// <param name="sqlTranslator"></param>
+            /// <param name="tableName"></param>
+            /// <param name="columnDescriptor"></param>
+            /// <returns></returns>
+            public int AddSqlColumnAndGetIndex(ISqlTranslateService sqlTranslator, string tableName, IColumnDescriptor columnDescriptor)
+            {
+                var sqlColumnName = sqlTranslator.GetSqlField(tableName, columnDescriptor.columnName);
+
+                var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
+                if (sqlColumnIndex < 0)
+                {
+                    sqlColumnIndex = columns.Count;
+                    columns.Add(new Column { tableName = tableName, columnDescriptor = columnDescriptor, sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
+                }
+                return sqlColumnIndex;
+            }
+
+            /// <summary>
+            ///  aggregate column in GroupBy
+            /// </summary>
+            /// <param name="sqlColumnSentence"> for example:   Sum([t0].[userId])  ,  [t0].[userFatherId]  </param>
+            /// <returns></returns>
+            public int AddSqlColumnAndGetIndex(string sqlColumnSentence)
+            {
+                var sqlColumnName = sqlColumnSentence;
+
+                var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
+                if (sqlColumnIndex < 0)
+                {
+                    sqlColumnIndex = columns.Count;
+                    columns.Add(new Column { sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
+                }
+                return sqlColumnIndex;
+            }
+
+            /// <summary>
+            ///  alias table column  (  users.Select(u=> new { u.id } )   )
+            /// </summary>
+            /// <param name="sqlTranslator"></param>
+            /// <param name="member"></param>
+            /// <param name="dbContext"></param>
+            /// <returns></returns>
+            public int AddSqlColumnAndGetIndex(ISqlTranslateService sqlTranslator, ExpressionNode_Member member, DbContext dbContext)
+            {
+                var sqlColumnName = sqlTranslator.GetSqlField(member, dbContext);
+
+                var sqlColumnIndex = columns.FirstOrDefault(m => m.sqlColumnName == sqlColumnName)?.sqlColumnIndex ?? -1;
+                if (sqlColumnIndex < 0)
+                {
+                    sqlColumnIndex = columns.Count;
+                    columns.Add(new Column { member = member, sqlColumnName = sqlColumnName, sqlColumnAlias = "c" + sqlColumnIndex, sqlColumnIndex = sqlColumnIndex });
+                }
+                return sqlColumnIndex;
+            }
+
+
+            public string GetSqlColumns()
+            {
+                var sqlColumns = columns.Select(column => column.sqlColumnName + " as " + column.sqlColumnAlias);
+                return String.Join(", ", sqlColumns);
+            }
+
+            public string GetColumnAliasBySqlColumnName(string sqlColumnName)
+            {
+                return columns.FirstOrDefault(col => col.sqlColumnName == sqlColumnName)?.sqlColumnAlias;
+            }
+
+            class Column
+            {
+                // or table alias
+                public string tableName;
+                public IColumnDescriptor columnDescriptor;
+                public ExpressionNode_Member member;
+
+                public string sqlColumnName;
+                public string sqlColumnAlias;
+
+                public int sqlColumnIndex;
+            }
+
+        }
+
+        public SqlColumns sqlColumns = new();
+
+       
+
 
         protected Type entityType;
         protected List<IArgReader> entityArgReaders = new List<IArgReader>();
@@ -40,13 +134,15 @@ namespace Vitorm.Sql.DataReader
                 else if (node?.nodeType == NodeType.MethodCall)
                 {
                     ExpressionNode_MethodCall methodCall = node;
+
+                    // deal with aggregate functions like Sum(id)
                     if (methodCall.methodCall_typeName == "Enumerable")
                     {
                         string argName = null;
 
-                        var sqlField = sqlTranslateService.EvalExpression(arg, node);
-                        var fieldType = methodCall.MethodCall_GetReturnType();
-                        argName = GetArgument(sqlField, fieldType);
+                        var sqlColumnSentence = sqlTranslateService.EvalExpression(arg, node);
+                        var columnType = methodCall.MethodCall_GetReturnType();
+                        argName = GetArgument(sqlColumnSentence, columnType);
                         if (argName != null)
                         {
                             return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
@@ -55,22 +151,50 @@ namespace Vitorm.Sql.DataReader
                 }
                 return default;
             };
-            ExpressionNode_New newExp = cloner.Clone(selectedFields);
+            ExpressionNode newSelectedFields = cloner.Clone(selectedFields);
 
+            // Compile Lambda
+            lambdaCreateEntity = CompileExpression(convertService, entityArgReaders.Select(m => m.argName).ToArray(), newSelectedFields);
 
-            #region Compile Lambda
-            var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), (ExpressionNode)newExp);
-            //var strNode = Json.Serialize(lambdaNode);
+            return sqlColumns.GetSqlColumns();
+        }
+
+
+        Delegate CompileExpression(ExpressionConvertService convertService, string[] parameterNames, ExpressionNode newExp)
+        {
+            var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), newExp);
+            // var strNode = Json.Serialize(lambdaNode);
 
             var lambdaExp = convertService.ToLambdaExpression(lambdaNode, entityArgReaders.Select(m => m.argType).ToArray());
 
-            lambdaCreateEntity = lambdaExp.Compile();
-            #endregion
+            return lambdaExp.Compile();
+        }
+
+        #region CompileExpressionWithCache
+        /*
+        // If it's anonymous CompilerGenerated type, reuse Compiled invoke.
+        // not work  because even if it's anonymous CompilerGenerated type, it also can be reused in same method of different lines.
+        static bool cacheEntityCompile = true;
+        static ConcurrentDictionary<Type, Delegate> delegateCache = new();
+        Delegate CompileExpressionWithCache(ExpressionConvertService convertService, string[] parameterNames, ExpressionNode_New newExp)
+        {
+            var type = entityType ?? newExp.New_GetType();
+
+            if (!cacheEntityCompile || type == null)
+                return CompileExpression(convertService, parameterNames, newExp);
+
+            var isCacheable = Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false);
+
+            if (isCacheable && delegateCache.TryGetValue(type, out var lambda)) return lambda;
+
+            lambda = CompileExpression(convertService, parameterNames, newExp);
+
+            if (isCacheable) delegateCache[type] = lambda;
 
-            // sqlFields
-            var fields = sqlFields.Select((f, index) => f + " as c" + index);
-            return String.Join(", ", fields);
+            return lambda;
         }
+        //*/
+        #endregion
 
 
         public virtual object ReadData(IDataReader reader)
@@ -117,8 +241,8 @@ namespace Vitorm.Sql.DataReader
                 if (isValueType)
                 {
                     // Value arg
-                    string sqlFieldName = sqlTranslator.GetSqlField(member, arg.dbContext);
-                    argReader = new ValueReader(this, argType, argUniqueKey, argName, sqlFieldName);
+                    var sqlColumnIndex = sqlColumns.AddSqlColumnAndGetIndex(sqlTranslator, member, arg.dbContext);
+                    argReader = new ValueReader(argType, argUniqueKey, argName, sqlColumnIndex);
                 }
                 else
                 {
@@ -132,9 +256,9 @@ namespace Vitorm.Sql.DataReader
             return argReader.argName;
         }
 
-        protected string GetArgument(string sqlField, Type fieldType)
+        protected string GetArgument(string sqlColumnSentence, Type columnType)
         {
-            var argUniqueKey = $"argFunc_{sqlField}";
+            var argUniqueKey = $"argFunc_{sqlColumnSentence}";
 
             IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.argUniqueKey == argUniqueKey);
 
@@ -142,7 +266,8 @@ namespace Vitorm.Sql.DataReader
             {
                 var argName = "arg_" + entityArgReaders.Count;
 
-                argReader = new ValueReader(this, fieldType, argUniqueKey, argName, sqlField);
+                var sqlColumnIndex = sqlColumns.AddSqlColumnAndGetIndex(sqlColumnSentence);
+                argReader = new ValueReader(columnType, argUniqueKey, argName, sqlColumnIndex);
 
                 entityArgReaders.Add(argReader);
             }

+ 2 - 2
src/Vitorm/Sql/DataReader/EntityReader/ModelReader.EntityPropertyReader.cs

@@ -10,8 +10,8 @@ namespace Vitorm.Sql.DataReader
         {
             public IColumnDescriptor column { get; protected set; }
 
-            public EntityPropertyReader(EntityReader entityReader, IColumnDescriptor column, string sqlFieldName)
-                : base(entityReader.sqlFields, column.type, sqlFieldName)
+            public EntityPropertyReader(IColumnDescriptor column, int sqlColumnIndex)
+                : base(column.type, sqlColumnIndex)
             {
                 this.column = column;
             }

+ 6 - 18
src/Vitorm/Sql/DataReader/EntityReader/ModelReader.cs

@@ -13,8 +13,7 @@ namespace Vitorm.Sql.DataReader
         public string argUniqueKey { get; set; }
         public Type argType { get; set; }
 
-        //EntityPropertyReader keyPropertyReader;
-        readonly List<EntityPropertyReader> proppertyReaders = new();
+        readonly List<EntityPropertyReader> propertyReaders = new();
 
         public ModelReader(EntityReader entityReader, ISqlTranslateService sqlTranslator, string tableName, string argUniqueKey, string argName, Type argType, IEntityDescriptor entityDescriptor)
         {
@@ -22,32 +21,21 @@ namespace Vitorm.Sql.DataReader
             this.argName = argName;
             this.argType = argType;
 
-            // #1 key
-            {
-                //var column = entityDescriptor.key;
-                //var sqlFieldName = sqlTranslator.GetSqlField(tableName, column.name);
-                //keyPropertyReader = new EntityPropertyReader(entityReader, column, sqlFieldName);
-            }
-            // #2 properties
+            // properties
             {
                 foreach (var column in entityDescriptor.allColumns)
                 {
-                    var sqlFieldName = sqlTranslator.GetSqlField(tableName, column.columnName);
-                    proppertyReaders.Add(new EntityPropertyReader(entityReader, column, sqlFieldName));
+                    var sqlColumnIndex = entityReader.sqlColumns.AddSqlColumnAndGetIndex(sqlTranslator, tableName, columnDescriptor: column);
+                    propertyReaders.Add(new EntityPropertyReader(column, sqlColumnIndex));
                 }
             }
         }
         public object Read(IDataReader reader)
         {
-            // #1 key           
-            //var value = keyPropertyReader.Read(reader);
-            //if (value == null) return null;
-
             var entity = Activator.CreateInstance(argType);
-            //keyPropertyReader.column.SetValue(entity, value);
 
-            //#2 properties
-            foreach (var perpertyReader in proppertyReaders)
+            // properties
+            foreach (var perpertyReader in propertyReaders)
             {
                 if (!perpertyReader.Read(reader, entity))
                     return null;

+ 4 - 10
src/Vitorm/Sql/DataReader/EntityReader/SqlFieldReader.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Data;
 
 namespace Vitorm.Sql.DataReader
@@ -7,29 +6,24 @@ namespace Vitorm.Sql.DataReader
 
     class SqlFieldReader
     {
-        public int sqlFieldIndex { get; set; }
+        public int sqlColumnIndex { get; set; }
         protected Type valueType { get; set; }
         protected Type underlyingType;
 
 
-        public SqlFieldReader(List<string> sqlFields, Type valueType, string sqlFieldName)
+        public SqlFieldReader(Type valueType, int sqlColumnIndex)
         {
             this.valueType = valueType;
             underlyingType = TypeUtil.GetUnderlyingType(valueType);
 
-            sqlFieldIndex = sqlFields.IndexOf(sqlFieldName);
-            if (sqlFieldIndex < 0)
-            {
-                sqlFieldIndex = sqlFields.Count;
-                sqlFields.Add(sqlFieldName);
-            }
+            this.sqlColumnIndex = sqlColumnIndex;
         }
 
 
 
         public object Read(IDataReader reader)
         {
-            var value = reader.GetValue(sqlFieldIndex);
+            var value = reader.GetValue(sqlColumnIndex);
             return TypeUtil.ConvertToUnderlyingType(value, underlyingType);
         }
 

+ 2 - 2
src/Vitorm/Sql/DataReader/EntityReader/ValueReader.cs

@@ -11,8 +11,8 @@ namespace Vitorm.Sql.DataReader
 
         public Type argType { get => valueType; }
 
-        public ValueReader(EntityReader entityReader, Type valueType, string argUniqueKey, string argName, string sqlFieldName)
-                     : base(entityReader.sqlFields, valueType, sqlFieldName)
+        public ValueReader(Type valueType, string argUniqueKey, string argName, int sqlColumnIndex)
+                     : base(valueType, sqlColumnIndex)
         {
             this.argUniqueKey = argUniqueKey;
             this.argName = argName;

+ 0 - 20
src/Vitorm/Sql/DataReader/NumScalarReader.cs

@@ -1,20 +0,0 @@
-using System;
-using System.Data;
-
-using Vitorm.Sql;
-
-namespace Vitorm.DataReader
-{
-    public class NumScalarReader : IDbDataReader
-    {
-        public object ReadData(IDataReader reader)
-        {
-            if (reader.Read())
-            {
-                var count = reader.GetValue(0);
-                return Convert.ToInt32(count);
-            }
-            return -1;
-        }
-    }
-}

+ 16 - 2
src/Vitorm/Sql/SqlDbContext.cs

@@ -330,13 +330,27 @@ namespace Vitorm.Sql
                     {
                         // 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, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
+                        (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareCountQuery(arg, combinedStream);
 
                         var count = ExecuteScalar(sql: sql, param: sqlParam, useReadOnly: true);
-                        return Convert.ToInt32(count);
+                        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):
                     {

+ 18 - 9
src/Vitorm/Sql/SqlTranslate/BaseQueryTranslateService.cs

@@ -20,7 +20,7 @@ namespace Vitorm.Sql.SqlTranslate
             this.sqlTranslator = sqlTranslator;
         }
 
-
+        public virtual string BuildCountQuery(QueryTranslateArgument arg, CombinedStream stream) => throw new NotImplementedException();
 
         public virtual string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
         {
@@ -132,14 +132,14 @@ namespace Vitorm.Sql.SqlTranslate
         }
         protected virtual string ReadOrderBy(QueryTranslateArgument arg, CombinedStream stream)
         {
-            var fields = stream.orders.Select(field =>
+            var columns = stream.orders.Select(field =>
                 {
-                    var sqlField = sqlTranslator.EvalExpression(arg, field.member);
-                    return sqlField + " " + (field.asc ? "asc" : "desc");
+                    var sqlColumnName = sqlTranslator.EvalExpression(arg, field.member);
+                    return sqlColumnName + " " + (field.asc ? "asc" : "desc");
                 }
-            ).ToList();
+            );
 
-            return String.Join(", ", fields);
+            return String.Join(", ", columns);
         }
         #endregion
 
@@ -181,9 +181,18 @@ namespace Vitorm.Sql.SqlTranslate
             //if (resultEntityType == null)
             //    throw new NotSupportedException("resultEntityType could not be null");
 
-            var sqlFields = reader.BuildSelect(arg, resultEntityType, sqlTranslator, arg.dbContext.convertService, selectedFields);
-            arg.dataReader ??= reader;
-            return (stream.distinct == true ? "distinct " : "") + sqlFields;
+            if (stream.method == "Count")
+            {
+                var sqlColumns = reader.BuildSelect(arg, resultEntityType, sqlTranslator, arg.dbContext.convertService, selectedFields);
+                arg.dataReader ??= reader;
+                return (stream.distinct == true ? "distinct " : "") + sqlColumns;
+            }
+
+            {
+                var sqlColumns = reader.BuildSelect(arg, resultEntityType, sqlTranslator, arg.dbContext.convertService, selectedFields);
+                arg.dataReader ??= reader;
+                return (stream.distinct == true ? "distinct " : "") + sqlColumns;
+            }
         }
 
         protected virtual void ReverseOrder(QueryTranslateArgument arg, CombinedStream stream)

+ 1 - 0
src/Vitorm/Sql/SqlTranslate/ISqlTranslateService.cs

@@ -46,6 +46,7 @@ namespace Vitorm.Sql.SqlTranslate
         // #2 Retrieve : PrepareGet PrepareQuery
         string PrepareGet(SqlTranslateArgument arg);
         (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream);
+        (string sql, Dictionary<string, object> sqlParam) PrepareCountQuery(QueryTranslateArgument arg, CombinedStream combinedStream);
 
 
 

+ 6 - 6
src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs

@@ -1,6 +1,5 @@
 using System;
 
-using Vitorm.DataReader;
 using Vitorm.Sql.DataReader;
 using Vitorm.StreamQuery;
 
@@ -25,16 +24,17 @@ 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;";
+        }
+
         protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
         {
             switch (stream.method)
             {
                 case "Count":
-                    {
-                        var reader = new NumScalarReader();
-                        arg.dataReader ??= reader;
-                        return prefix + " " + "count(*)";
-                    }
                 case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
                     {
                         var reader = new EntityReader();

+ 26 - 6
src/Vitorm/Sql/SqlTranslate/SqlTranslateService.cs

@@ -383,7 +383,19 @@ namespace Vitorm.Sql.SqlTranslate
             return sql;
         }
 
-        public abstract (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream);
+        protected abstract BaseQueryTranslateService queryTranslateService { get; }
+        public virtual (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream)
+        {
+            string sql = queryTranslateService.BuildQuery(arg, combinedStream);
+            return (sql, arg.sqlParam, arg.dataReader);
+        }
+
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareCountQuery(QueryTranslateArgument arg, CombinedStream combinedStream)
+        {
+            string sql = queryTranslateService.BuildCountQuery(arg, combinedStream);
+            return (sql, arg.sqlParam);
+        }
+
         #endregion
 
 
@@ -428,7 +440,13 @@ namespace Vitorm.Sql.SqlTranslate
             return (sql, GetSqlParams);
         }
 
-        public abstract (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream);
+
+        protected abstract BaseQueryTranslateService executeUpdateTranslateService { get; }
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream)
+        {
+            string sql = executeUpdateTranslateService.BuildQuery(arg, combinedStream);
+            return (sql, arg.sqlParam);
+        }
 
         #endregion
 
@@ -476,10 +494,12 @@ namespace Vitorm.Sql.SqlTranslate
         }
 
 
-
-        public abstract (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream);
-
-
+        protected abstract BaseQueryTranslateService executeDeleteTranslateService { get; }
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream)
+        {
+            string sql = executeDeleteTranslateService.BuildQuery(arg, combinedStream);
+            return (sql, arg.sqlParam);
+        }
         #endregion
 
 

+ 9 - 0
test/Vitorm.Data.Benchmark/01.Run benchmark.bat

@@ -0,0 +1,9 @@
+
+rd /s/q "./BenchmarkDotNet.Artifacts/results"
+
+
+dotnet run -c Release
+
+
+pause
+ 

+ 4 - 0
test/Vitorm.Data.Benchmark/02.Open result.bat

@@ -0,0 +1,4 @@
+
+
+start "" "BenchmarkDotNet.Artifacts\results\App.VitormBenchmark-report.html"
+

+ 104 - 0
test/Vitorm.Data.Benchmark/OtherTest/VitormBenchmark_ReduceMember.cs

@@ -0,0 +1,104 @@
+using BenchmarkDotNet.Attributes;
+
+using Vit.Linq.ExpressionTree;
+
+using Vitorm;
+
+namespace App.OtherTest
+{
+    //[Orderer(SummaryOrderPolicy.FastestToSlowest)]
+    [InProcess]
+    public class VitormBenchmark_ReduceMember
+    {
+        [Params(100)]
+        public int N;
+
+        [Params(true, false)]
+        public bool queryJoin;
+
+        [Params(true, false)]
+        public bool reduceMember;
+
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            DataConvertArgument.CalculateToConstant_ManuallyReduceMember = reduceMember;
+        }
+
+        [Benchmark]
+        public void Run()
+        {
+            Run(N, queryJoin);
+        }
+
+
+        public static void Run(int N, bool queryJoin)
+        {
+            for (int i = 0; i < N; i++)
+            {
+                if (queryJoin) QueryJoin();
+                else Query();
+            }
+        }
+
+        public static void QueryJoin()
+        {
+            var userSet = Data.Query<User>();
+            var query =
+                    from user in userSet
+                    from father in userSet.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                    from mother in userSet.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
+                    where user.id > 1
+                    orderby user.id
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father.name != null ? true : false
+                    }
+                    ;
+
+            query = query.Skip(1).Take(2);
+
+            var sql = query.ToExecuteString();
+            //var userList = query.ToList();
+        }
+
+        public static void Query()
+        {
+            var userSet = Data.Query<User>();
+            var query1 =
+                    from user in userSet
+                    where user.id > 1
+                    orderby user.id
+                    select user;
+
+            var query = query1.Skip(1).Take(2);
+
+            var sql = query.ToExecuteString();
+            //var userList = query.ToList();
+        }
+
+
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+            public int? motherId { get; set; }
+        }
+
+    }
+
+
+
+
+}

+ 36 - 91
test/Vitorm.Data.Benchmark/Program.cs

@@ -1,117 +1,62 @@
-using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Running;
+using App.QueryTest;
 
-using Vitorm;
-using Vitorm.Sql;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Order;
+using BenchmarkDotNet.Running;
 
 namespace App
 {
 
-    public class VitormBenchmark
+    public class Program
     {
-        [Params(10000)]
-        public int N;
-
-        [Params("Data", "Vitorm")]
-        public string dataType;
-
-        [Params(false, true)]
-        public bool queryJoin;
-
-        //[Params(false, true)]
-        //public bool reduceMember;
-
-        [GlobalSetup]
-        public void Setup()
+        static void Main(string[] args)
         {
-        }
+            QueryTest_Vitorm.InitDb();
 
-        [Benchmark]
-        public void Run()
-        {
-            Run(N, dataType, queryJoin);
-        }
+            //new QueryTest_Vitorm().Query(take: 100);
+            //new QueryTest_Vitorm().QueryJoin(take: 100);
 
+            //new QueryTest_EntityFramework().Query(take: 100);
+            //new QueryTest_EntityFramework().QueryJoin(take: 100);
 
-        public static void Run(int N, string dataType, bool queryJoin)
-        {
-            for (int i = 0; i < N; i++)
-            {
-                var query = GetQuery(dataType);
-                if (queryJoin) QueryJoin(query);
-                else Query(query);
-            }
-        }
 
-        static IQueryable<User> GetQuery(string dataType)
-        {
-            var connectionString = "data source=sqlite.db;";
-            return dataType == "Data" ? Data.Query<User>() : new SqlDbContext().UseSqlite(connectionString).AutoDisposeAfterQuery().Query<User>();
+            var summary = BenchmarkRunner.Run<VitormBenchmark>();
+            //BenchmarkRunner.Run<OtherTest.VitormBenchmark_ReduceMember>();
         }
 
 
-        public static void QueryJoin(IQueryable<User> userSet)
+        [Orderer(SummaryOrderPolicy.FastestToSlowest)]
+        [InProcess]
+        public class VitormBenchmark
         {
-            var query =
-                    from user in userSet
-                    from father in userSet.Where(father => user.fatherId == father.id).DefaultIfEmpty()
-                    from mother in userSet.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
-                    where user.id > 1
-                    orderby user.id
-                    select new
-                    {
-                        user,
-                        father,
-                        mother,
-                        testId = user.id + 100,
-                        hasFather = father.name != null ? true : false
-                    };
-
-            query = query.Skip(1).Take(2);
-
-            var sql = query.ToExecuteString();
-            //var userList = query.ToList();
-        }
+            [Params(100)]
+            public int N;
 
-        public static void Query(IQueryable<User> userSet)
-        {
-            var query1 =
-                    from user in userSet
-                    where user.id > 1
-                    orderby user.id
-                    select user;
+            [Params(10, 100, 500, 1000)]
+            public int rowCount;
 
-            var query = query1.Skip(1).Take(2);
+            [Params(typeof(QueryTest_Vitorm), typeof(QueryTest_EntityFramework))]
+            public Type testType;
 
-            var sql = query.ToExecuteString();
-            //var userList = query.ToList();
-        }
+            [Params(true, false)]
+            public bool queryJoin;
 
 
+            IBenchmarkQuery queryTest;
 
-    }
+            [GlobalSetup]
+            public void Setup()
+            {
+                queryTest = Activator.CreateInstance(testType) as IBenchmarkQuery;
+            }
 
-    public class Program
-    {
-        static void Main(string[] args)
-        {
-            var summary = BenchmarkRunner.Run<VitormBenchmark>();
+            [Benchmark]
+            public void Run()
+            {
+                var config = new QueryConfig { repeatCount = N, queryJoin = queryJoin, take = rowCount };
+                queryTest.Query(config);
+            }
         }
-
     }
 
-
-
-
-    // Entity Definition
-    [System.ComponentModel.DataAnnotations.Schema.Table("User")]
-    public class User
-    {
-        [System.ComponentModel.DataAnnotations.Key]
-        public int id { get; set; }
-        public string name { get; set; }
-        public DateTime? birth { get; set; }
-        public int? fatherId { get; set; }
-        public int? motherId { get; set; }
-    }
 }

+ 15 - 0
test/Vitorm.Data.Benchmark/QueryTest/IBenchmarkQuery.cs

@@ -0,0 +1,15 @@
+namespace App.QueryTest
+{
+    public interface IBenchmarkQuery
+    {
+        void Query(QueryConfig config); 
+    }
+
+    public class QueryConfig 
+    {
+        public int repeatCount;
+
+        public int take;
+        public bool queryJoin;
+    }
+}

+ 95 - 0
test/Vitorm.Data.Benchmark/QueryTest/QueryTest_EntityFramework.cs

@@ -0,0 +1,95 @@
+using Microsoft.EntityFrameworkCore;
+
+using Vit.Core.Util.ConfigurationManager;
+
+using Vitorm;
+
+namespace App.QueryTest
+{
+    public class QueryTest_EntityFramework : IBenchmarkQuery
+    {
+
+        MyDbContext myDbContext = new MyDbContext();
+        public IQueryable<User> GetQueryable() => myDbContext.users;
+
+
+        public void Query(QueryConfig config)
+        {
+            for (int i = 0; i < config.repeatCount; i++)
+            {
+                if (config.queryJoin) QueryJoin(config.take);
+                else Query(config.take);
+            }
+        }
+
+        public void QueryJoin(int take)
+        {
+            var queryable = GetQueryable();
+            var query =
+                    from user in queryable
+                    from father in queryable.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                    from mother in queryable.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
+                    where user.id > 1
+                    orderby user.id
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father.name != null ? true : false
+                    }
+                    ;
+
+            query = query.Skip(1).Take(take);
+
+            var userList = query.ToList();
+            var rowCount = query.Count();
+            if (rowCount != take) throw new Exception($"query failed, expected row count : {take} , actual count: {rowCount} ");
+        }
+
+        public void Query(int take)
+        {
+            var userSet = Data.Query<User>();
+            var query1 =
+                    from user in userSet
+                    where user.id > 1
+                    orderby user.id
+                    select user;
+
+            var query = query1.Skip(1).Take(take);
+
+            var userList = query.ToList();
+            var rowCount = query.Count();
+            if (rowCount != take) throw new Exception($"query failed, expected row count : {take} , actual count: {rowCount} ");
+        }
+
+
+
+
+        public class MyDbContext : Microsoft.EntityFrameworkCore.DbContext
+        {
+            public Microsoft.EntityFrameworkCore.DbSet<User> users { get; set; }
+            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+            {
+                var connectionString = Appsettings.json.GetStringByPath("Vitorm.Data[0].connectionString");
+                optionsBuilder.UseSqlite(connectionString);
+            }
+        }
+
+
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+            public int? motherId { get; set; }
+        }
+
+    }
+}

+ 100 - 0
test/Vitorm.Data.Benchmark/QueryTest/QueryTest_Vitorm.cs

@@ -0,0 +1,100 @@
+using Vitorm;
+
+namespace App.QueryTest
+{
+    public class QueryTest_Vitorm : IBenchmarkQuery
+    {
+        public static void InitDb()
+        {
+            Data.Drop<User>();
+            Data.Create<User>();
+
+            var users = new List<User> {
+                    new User { id=1, name="u146", fatherId=4, motherId=6 },
+                    new User { id=2, name="u246", fatherId=4, motherId=6 },
+                    new User { id=3, name="u356", fatherId=5, motherId=6 },
+                    new User { id=4, name="u400" },
+                    new User { id=5, name="u500" },
+                    new User { id=6, name="u600" },
+                };
+            Data.AddRange(users);
+
+            users = Enumerable.Range(7, 1000).Select(id => new User { id = id, name = "user" + id }).ToList();
+            Data.AddRange(users);
+        }
+
+
+        IQueryable<User> users = Data.Query<User>();
+        public IQueryable<User> GetQueryable() => users;
+
+
+
+
+        public void Query(QueryConfig config)
+        {
+            for (int i = 0; i < config.repeatCount; i++)
+            {
+                if (config.queryJoin) QueryJoin(config.take);
+                else Query(config.take);
+            }
+        }
+
+        public void QueryJoin(int take)
+        {
+            var queryable = GetQueryable();
+            var query =
+                    from user in queryable
+                    from father in queryable.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                    from mother in queryable.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
+                    where user.id > 1
+                    orderby user.id
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father.name != null ? true : false
+                    }
+                    ;
+
+            query = query.Skip(1).Take(take);
+
+            var userList = query.ToList();
+            var rowCount = userList.Count();
+            if (rowCount != take) throw new Exception($"query failed, expected row count : {take} , actual count: {rowCount} ");
+        }
+
+        public void Query(int take)
+        {
+            var userSet = Data.Query<User>();
+            var query1 =
+                    from user in userSet
+                    where user.id > 1
+                    orderby user.id
+                    select user;
+
+            var query = query1.Skip(1).Take(take);
+
+            var userList = query.ToList();
+            var rowCount = userList.Count();
+            if (rowCount != take) throw new Exception($"query failed, expected row count : {take} , actual count: {rowCount} ");
+        }
+
+
+
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+            public int? motherId { get; set; }
+        }
+
+    }
+}

+ 3 - 0
test/Vitorm.Data.Benchmark/Vitorm.Data.Benchmark.csproj

@@ -5,10 +5,13 @@
         <TargetFramework>net6.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
+        <RootNamespace>App</RootNamespace>
     </PropertyGroup>
 
     <ItemGroup>
       <PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
+      <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.31" />
+      <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.31" />
     </ItemGroup>
 
     <ItemGroup>

+ 1 - 2
test/Vitorm.Data.Console/Program.cs

@@ -6,8 +6,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm.Data
-            File.WriteAllBytes("sqlite.db", new byte[0]);
+            // #1 No need to init Vitorm.Data
 
             // #2 Create Table
             Data.Drop<User>();

+ 0 - 3
test/Vitorm.Data.MsTest/CommonTest/SqliteReadOnly_Test.cs

@@ -66,9 +66,6 @@ namespace Vitorm.MsTest
         public void Init(string fileName)
         {
             var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName);
-            if (File.Exists(filePath)) File.Delete(filePath);
-            File.WriteAllBytes(filePath, new byte[0]);
-
             using var dbContext = new SqlDbContext().UseSqlite(connectionString: $"data source={fileName};");
 
             dbContext.Drop<User>();

+ 0 - 6
test/Vitorm.Data.MsTest/CommonTest/Sqlite_Test.cs

@@ -41,12 +41,6 @@ namespace Vitorm.MsTest
 
         public void Init()
         {
-
-            var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"sqlite.db");
-            if (File.Exists(filePath)) File.Delete(filePath);
-            File.WriteAllBytes(filePath, new byte[0]);
-
-
             using var dbContext = Data.DataProvider<User>()?.CreateDbContext();
 
             dbContext.Drop<User>();

+ 1 - 2
test/Vitorm.Sqlite.Console/Program.cs

@@ -6,8 +6,7 @@ namespace App
     {
         static void Main(string[] args)
         {
-            // #1 Create an empty SQLite database file and configures Vitorm
-            File.WriteAllBytes("sqlite.db", Array.Empty<byte>());
+            // #1 Configures Vitorm
             using var dbContext = new Vitorm.Sql.SqlDbContext();
             dbContext.UseSqlite("data source=sqlite.db");
 

+ 218 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Count_Test.cs

@@ -0,0 +1,218 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_LinqMethods_Count_Test
+    {
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            TestAllCase(userQuery);
+        }
+
+
+
+        [TestMethod]
+        public void Test_SkipAndTake_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            TestAllCase(userQuery, new Config { skip = 1, take = 100 });
+        }
+
+
+        [TestMethod]
+        public void Test_Distinct_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            TestAllCase(userQuery, new Config { distinct = true });
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_Distinct_SkipAndTake_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            TestAllCase(userQuery, new Config { distinct = true, skip = 1, take = 100 });
+        }
+
+
+
+
+
+        void TestAllCase(IQueryable<User> users, Config config = null)
+        {
+
+            #region no orderBy
+            {
+                // single table
+                Test(users.Select(user => user.fatherId), config);
+                Test(users.Select(user => new { user.fatherId }), config);
+
+                Test(users.Select(user => user), config);
+                Test(users.Select(user => new { user }), config);
+                Test(users.Select(user => new { user, user.fatherId }), config);
+                Test(users.Select(user => new { user.id, user.fatherId }), config);
+
+
+
+                // joinedTable
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user.fatherId), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user.fatherId }), config);
+
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user }), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, user.id }), config);
+
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, father }), config);
+
+
+
+                // groupedTable Lambda Expression
+                {
+                    var query =
+                         users
+                        .GroupBy(user => new { user.fatherId, user.motherId })
+                        .Select(userGroup => new
+                        {
+                            userGroup.Key.fatherId,
+                            userGroup.Key.motherId,
+                            sumId = userGroup.Sum(m => m.id),
+                        });
+
+                    Test(query, config);
+                }
+                // groupedTable Linq Expression
+                {
+                    var query =
+                         from user in users
+                         group user by new { user.fatherId, user.motherId } into userGroup
+                         select new
+                         {
+                             userGroup.Key.fatherId,
+                             userGroup.Key.motherId,
+                             sumId = userGroup.Sum(m => m.id),
+                         };
+
+                    Test(query, config);
+                }
+            }
+            #endregion
+
+            #region with orderBy
+            {
+                // single table
+                Test(users.OrderBy(m => m.fatherId).Select(user => user.fatherId), config);
+                Test(users.OrderBy(m => m.fatherId).Select(user => new { user.fatherId }), config);
+
+                Test(users.OrderBy(m => m.fatherId).Select(user => user), config);
+                Test(users.OrderBy(m => m.fatherId).Select(user => new { user }), config);
+                Test(users.OrderBy(m => m.fatherId).Select(user => new { user, user.fatherId }), config);
+                Test(users.OrderBy(m => m.fatherId).Select(user => new { user.id, user.fatherId }), config);
+
+
+
+                // joinedTable
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user.fatherId).OrderBy(m => m), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user.fatherId }).OrderBy(m => m.fatherId), config);
+
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => user).OrderBy(m => m.fatherId), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user }).OrderBy(m => m.user.fatherId), config);
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, user.id }).OrderBy(m => m.user.fatherId), config);
+
+                Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, father }).OrderBy(m => m.user.fatherId), config);
+
+
+                // order by alias column
+                {
+                    Test(users.Select(user => user.fatherId).OrderBy(m => m), config);
+                    Test(users.Select(user => new { user.fatherId }).OrderBy(m => m.fatherId), config);
+
+                    Test(users.Select(user => user).OrderBy(m => m.fatherId), config);
+                    Test(users.Select(user => new { user }).OrderBy(m => m.user.fatherId), config);
+                    Test(users.Select(user => new { user, user.fatherId }).OrderBy(m => m.fatherId), config);
+                    Test(users.Select(user => new { user.id, user.fatherId }).OrderBy(m => m.fatherId), config);
+
+                    Test(users.Select(user => new { user.id, fid = user.fatherId ?? -1 }).OrderBy(m => m.id), config);
+                }
+
+
+
+                // groupedTable Lambda Expression
+                {
+                    var query =
+                         users
+                        .GroupBy(user => new { user.fatherId, user.motherId })
+                        .OrderBy(m => m.Key.fatherId)
+                        .Select(userGroup => new
+                        {
+                            userGroup.Key.fatherId,
+                            userGroup.Key.motherId,
+                            sumId = userGroup.Sum(m => m.id),
+                        });
+
+                    Test(query, config);
+                }
+                // groupedTable Linq Expression
+                {
+                    var query =
+                         from user in users
+                         group user by new { user.fatherId, user.motherId } into userGroup
+                         orderby userGroup.Key.fatherId
+                         select new
+                         {
+                             userGroup.Key.fatherId,
+                             userGroup.Key.motherId,
+                             sumId = userGroup.Sum(m => m.id),
+                         };
+
+                    Test(query, config);
+                }
+
+            }
+            #endregion
+
+
+        }
+
+
+
+        void Test<Entity>(IQueryable<Entity> query, Config config = null)
+        {
+            if (config?.distinct == true) query = query.Distinct();
+            if (config?.skip.HasValue == true) query = query.Skip(config.skip.Value);
+            if (config?.take.HasValue == true) query = query.Take(config.take.Value);
+
+            var sql = query.ToExecuteString();
+
+            var rows = query.ToList();
+            int expectedCount = rows.Count;
+
+
+            var count = query.Count();
+            Assert.AreEqual(expectedCount, count);
+        }
+        class Config
+        {
+            public bool? distinct;
+
+            public int? skip;
+            public int? take;
+        }
+    }
+}

+ 29 - 7
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Distinct_Test.cs

@@ -15,24 +15,46 @@ namespace Vitorm.MsTest.CommonTest
             using var dbContext = DataSource.CreateDbContext();
             var userQuery = dbContext.Query<User>();
 
+            {
+                var query = userQuery.Select(u => u.fatherId).Distinct();
+
+                //var sql = query.ToExecuteString();
+                var fatherIds = query.ToList();
+
+                Assert.AreEqual(3, fatherIds.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
+            }
             {
                 var query = userQuery.Select(u => new { u.fatherId }).Distinct();
 
                 //var sql = query.ToExecuteString();
                 var userList = query.ToList();
-                var ids = userList.Select(u => u.fatherId).ToList();
+                var fatherIds = userList.Select(u => u.fatherId).ToList();
 
-                Assert.AreEqual(3, ids.Count);
-                Assert.AreEqual(0, ids.Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(3, fatherIds.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
             }
             {
-                var query = userQuery.Select(u => u.fatherId).Distinct();
+                var query = userQuery.Select(u => new { u.fatherId, u.motherId }).Distinct();
+
+                query = query.Skip(1).Take(100);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                var fatherIds = userList.Select(u => u.fatherId).ToList();
+                var motherIds = userList.Select(u => u.motherId).ToList();
+
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(0, motherIds.Except(new int?[] { 6, null }).Count());
+            }
+            {
+                var query = userQuery.Select(u => new { user = u, u.fatherId, u.motherId }).Distinct();
 
                 //var sql = query.ToExecuteString();
-                var ids = query.ToList();
+                var userList = query.ToList();
 
-                Assert.AreEqual(3, ids.Count);
-                Assert.AreEqual(0, ids.Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(6, userList.Count);
             }
             {
                 var query = userQuery.Distinct();

+ 44 - 6
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LinqMethods_Test.cs

@@ -122,22 +122,60 @@ namespace Vitorm.MsTest.CommonTest
 
         }
 
+
+        [TestMethod]
+        public void Test_OrderBy()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = userQuery.OrderByDescending(user => user.id);
+
+                //var sql = query.ToExecuteString();
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(6, userList[0].id);
+            }
+            {
+                var query = userQuery.OrderByDescending(user => user.id).Select(user => new { fid = user.fatherId, user.id });
+
+                //var sql = query.ToExecuteString();
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(6, userList[0].id);
+            }
+
+        }
+
+
+
+
+
         [TestMethod]
         public void Test_Count()
         {
             using var dbContext = DataSource.CreateDbContext();
             var userQuery = dbContext.Query<User>();
 
+            // Count
             {
-                var count = (from user in userQuery
-                             where user.id > 2
-                             select new
-                             {
-                                 user
-                             }).Count();
+                var query = userQuery.Where(user => user.id > 2);
 
+                var count = query.Count();
                 Assert.AreEqual(4, count);
             }
+            // Skip Take Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = query.Count();
+                Assert.AreEqual(3, count);
+            }
         }
 
 

+ 89 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_ScopeParam_LeftJoin_Test.cs

@@ -0,0 +1,89 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_ScopeParam_LeftJoin_Test
+    {
+
+        [TestMethod]
+        public void Test_Join_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // params form method arg
+            QueryByArg(userQuery, 2);
+
+            // params from scope
+            {
+                var id = 2;
+                var query =
+                        from user in userQuery
+                        from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                        where user.id > id
+                        orderby user.id
+                        select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(5, userList[0].father?.id);
+                Assert.AreEqual(4, userList[1].user.id);
+                Assert.AreEqual(null, userList[1].father?.name);
+            }
+
+            // params from scope
+            {
+                var userArg = new { id = 2 };
+                var query =
+                        from user in userQuery
+                        from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                        where user.id > userArg.id
+                        orderby user.id
+                        select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(5, userList[0].father?.id);
+                Assert.AreEqual(4, userList[1].user.id);
+                Assert.AreEqual(null, userList[1].father?.name);
+            }
+
+        }
+
+
+        void QueryByArg(IQueryable<User> userQuery, int id)
+        {
+            // Linq Expression
+            {
+                var query =
+                        from user in userQuery
+                        from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                        where user.id > id
+                        orderby user.id
+                        select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(5, userList[0].father?.id);
+                Assert.AreEqual(4, userList[1].user.id);
+                Assert.AreEqual(null, userList[1].father?.name);
+            }
+        }
+
+
+
+
+    }
+}

+ 80 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_ScopeParam_Test.cs

@@ -0,0 +1,80 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_Query_ScopeParam_Test_Test
+    {
+
+        [TestMethod]
+        public void Test_Join_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // params form method arg
+            QueryByArg(userQuery, 2);
+
+            // params from scope
+            {
+                var id = 2;
+                var query =
+                        from user in userQuery
+                        where user.id > id
+                        orderby user.id
+                        select new { user };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(4, userList[1].user.id);
+            }
+
+            // params from scope
+            {
+                var userArg = new { id = 2 };
+                var query =
+                        from user in userQuery
+                        where user.id > userArg.id
+                        orderby user.id
+                        select new { user };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(4, userList[1].user.id);
+            }
+
+        }
+
+
+        void QueryByArg(IQueryable<User> userQuery, int id)
+        {
+            // Linq Expression
+            {
+                var query =
+                from user in userQuery
+                where user.id > id
+                orderby user.id
+                select new { user };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(4, userList[1].user.id);
+            }
+        }
+
+
+
+
+    }
+}

+ 0 - 4
test/Vitorm.Sqlite.MsTest/DataSource.cs

@@ -56,10 +56,6 @@ namespace Vitorm.MsTest
         {
             var guid = Guid.NewGuid().ToString();
             var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"{guid}.sqlite.db");
-            if (File.Exists(filePath)) File.Delete(filePath);
-            File.WriteAllBytes(filePath, new byte[0]);
-
-
             var connectionString = $"data source={filePath}";
 
             var dbContext = new SqlDbContext();