Explorar el Código

fix Count issue (work for skip take and distinct)

Lith hace 10 meses
padre
commit
b1b9724ee4
Se han modificado 40 ficheros con 1307 adiciones y 406 borrados
  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();