ソースを参照

remove reference for Dapper

Lith 11 ヶ月 前
コミット
4f905f0e6c
76 ファイル変更5868 行追加219 行削除
  1. 1 47
      README.md
  2. 54 5
      Vitorm.sln
  3. 46 0
      src/Vitorm.MySql/DbContext_Extensions.cs
  4. 77 0
      src/Vitorm.MySql/SqlTransactionScope.cs
  5. 145 0
      src/Vitorm.MySql/SqlTransactionScope_Command.cs
  6. 279 0
      src/Vitorm.MySql/SqlTranslateService.cs
  7. 56 0
      src/Vitorm.MySql/TranslateService/ExecuteDeleteTranslateService.cs
  8. 82 0
      src/Vitorm.MySql/TranslateService/ExecuteUpdateTranslateService.cs
  9. 27 0
      src/Vitorm.MySql/Vitorm.MySql.csproj
  10. 32 0
      src/Vitorm.SqlServer/DbContext_Extensions.cs
  11. 77 0
      src/Vitorm.SqlServer/SqlTransactionScope.cs
  12. 173 0
      src/Vitorm.SqlServer/SqlTranslate/BaseQueryTranslateService.cs
  13. 56 0
      src/Vitorm.SqlServer/SqlTranslate/ExecuteDeleteTranslateService.cs
  14. 85 0
      src/Vitorm.SqlServer/SqlTranslate/ExecuteUpdateTranslateService.cs
  15. 67 0
      src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs
  16. 241 0
      src/Vitorm.SqlServer/SqlTranslateService.cs
  17. 27 0
      src/Vitorm.SqlServer/Vitorm.SqlServer.csproj
  18. 32 0
      src/Vitorm.Sqlite/DbContext_Extensions.cs
  19. 75 0
      src/Vitorm.Sqlite/SqlTransactionScope.cs
  20. 255 0
      src/Vitorm.Sqlite/SqlTranslateService.cs
  21. 56 0
      src/Vitorm.Sqlite/TranslateService/ExecuteDeleteTranslateService.cs
  22. 88 0
      src/Vitorm.Sqlite/TranslateService/ExecuteUpdateTranslateService.cs
  23. 27 0
      src/Vitorm.Sqlite/Vitorm.Sqlite.csproj
  24. 2 3
      src/Vitorm/DbContext.cs
  25. 19 0
      src/Vitorm/DbFunction.cs
  26. 31 7
      src/Vitorm/Entity/ColumnDescriptor.cs
  27. 0 68
      src/Vitorm/Entity/Dapper/EntityDescriptor.cs
  28. 60 0
      src/Vitorm/Entity/DataAnnotations/EntityDescriptor.FromType.cs
  29. 64 0
      src/Vitorm/Entity/DataAnnotations/EntityDescriptor.cs
  30. 19 4
      src/Vitorm/Entity/IColumnDescriptor.cs
  31. 23 0
      src/Vitorm/Environment.cs
  32. 121 0
      src/Vitorm/Extensions/IDbConnection_Execute_Extensions.cs
  33. 28 0
      src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ExecuteDelete.cs
  34. 23 0
      src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ExecuteUpdate.cs
  35. 28 0
      src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ToExecuteString.cs
  36. 2 2
      src/Vitorm/Sql/DataReader/EntityReader/ModelReader.EntityPropertyReader.cs
  37. 62 49
      src/Vitorm/Sql/SqlDbContext.cs
  38. 3 1
      src/Vitorm/Sql/SqlTranslate/BaseQueryTranslateService.cs
  39. 1 1
      src/Vitorm/Sql/SqlTranslate/IQueryTranslateService.cs
  40. 3 2
      src/Vitorm/Sql/SqlTranslate/ISqlTranslateService.cs
  41. 1 1
      src/Vitorm/Sql/SqlTranslate/QueryTranslateArgument.cs
  42. 3 7
      src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs
  43. 60 17
      src/Vitorm/Sql/SqlTranslate/SqlTranslateService.cs
  44. 1 1
      src/Vitorm/Sql/TypeUtil.cs
  45. 97 0
      src/Vitorm/StreamQuery/CombinedStream.cs
  46. 7 0
      src/Vitorm/StreamQuery/EJoinType.cs
  47. 7 0
      src/Vitorm/StreamQuery/IStream.cs
  48. 29 0
      src/Vitorm/StreamQuery/SelectedFields.cs
  49. 24 0
      src/Vitorm/StreamQuery/SourceStream.cs
  50. 81 0
      src/Vitorm/StreamQuery/StreamReader.DeepClone.cs
  51. 61 0
      src/Vitorm/StreamQuery/StreamReader.GroupBy.cs
  52. 131 0
      src/Vitorm/StreamQuery/StreamReader.Join.cs
  53. 163 0
      src/Vitorm/StreamQuery/StreamReader.SelectMany.cs
  54. 477 0
      src/Vitorm/StreamQuery/StreamReader.cs
  55. 15 0
      src/Vitorm/StreamQuery/StreamToJoin.cs
  56. 33 0
      src/Vitorm/StreamQuery/StreamToUpdate.cs
  57. 6 4
      src/Vitorm/Vitorm.csproj
  58. 58 0
      test/Vitorm.MySql.MsTest/DataSource.cs
  59. 48 0
      test/Vitorm.MySql.MsTest/DbFunction_Test.cs
  60. 35 0
      test/Vitorm.MySql.MsTest/Vitorm.MySql.MsTest.csproj
  61. 7 0
      test/Vitorm.MySql.MsTest/appsettings.json
  62. 66 0
      test/Vitorm.SqlServer.MsTest/DataSource.cs
  63. 48 0
      test/Vitorm.SqlServer.MsTest/DbFunction_Test.cs
  64. 35 0
      test/Vitorm.SqlServer.MsTest/Vitorm.SqlServer.MsTest.csproj
  65. 7 0
      test/Vitorm.SqlServer.MsTest/appsettings.json
  66. 281 0
      test/Vitorm.Sqlite.MsTest/CommonTest/CRUD_Test.cs
  67. 182 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_Group_Test.cs
  68. 218 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_InnerJoin_ByJoin_Test.cs
  69. 135 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_InnerJoin_BySelectMany_Test.cs
  70. 107 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LeftJoin_ByGroupJoin_Test.cs
  71. 200 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_LeftJoin_BySelectMany_Test.cs
  72. 430 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Query_Test.cs
  73. 132 0
      test/Vitorm.Sqlite.MsTest/CommonTest/Transaction_Test.cs
  74. 55 0
      test/Vitorm.Sqlite.MsTest/DataSource.cs
  75. 56 0
      test/Vitorm.Sqlite.MsTest/DbFunction_Test.cs
  76. 25 0
      test/Vitorm.Sqlite.MsTest/Vitorm.Sqlite.MsTest.csproj

+ 1 - 47
README.md

@@ -12,8 +12,6 @@ Vitorm: an simple orm by Vit.Linq
 |![](https://github.com/VitormLib/Vitorm/workflows/ki_multibranch/badge.svg) | [![](https://img.shields.io/nuget/v/Vitorm.svg)](https://www.nuget.org/packages/Vitorm/) ![](https://img.shields.io/nuget/dt/Vitorm.svg) |
 
 
-
-
 complex-query-operators https://learn.microsoft.com/en-us/ef/core/querying/complex-query-operators
 sqlite/transactions  https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/transactions
 
@@ -21,15 +19,9 @@ sqlite/transactions  https://learn.microsoft.com/en-us/dotnet/standard/data/sqli
 # cur
 
 
-
-# rename to Vitorm
-
 # support ElasticSearch
 # support ClickHouse
 
-
-
-# remove depency of Dapper
 # try to make it clean
 
 
@@ -39,42 +31,4 @@ sqlite/transactions  https://learn.microsoft.com/en-us/dotnet/standard/data/sqli
 # TODO
 
 # Save SaveRange
-# DbFunction.PrimitiveSql
-
-
-#region #4 cross database join
-{
-    var dbContext = DataSource.BuildInitedDatabase(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
-    var users2 = dbContext.Query<User>();
-
-    var query = (from user in users
-                from father in users2.Where(father => user.fatherId == father.id).DefaultIfEmpty()
-                select new
-                {
-                    user,
-                    father
-                });  
-
-    var userList = query.ToList();
-    Assert.AreEqual(4, userList.Count);
-    Assert.AreEqual(3, userList.First().user.id);
-}
-#endregion
-
-##   where (`t0`.`id` + 1 = 4) and (`t0`.`fatherId` = cast(5 as integer))
-
-# will cause column mismatch if using inner select like:
-select `t2`.`id`,`t2`.`name`,`t2`.`birth`,`t2`.`fatherId`,`t2`.`motherId`,`t3`.`name`
- from 
- (
-	 select `t0`.`id`,`t0`.`name`,`t0`.`birth`,`t0`.`fatherId`,`t0`.`motherId`,`t1`.`id`,`t1`.`name`,`t1`.`birth`,`t1`.`fatherId`,`t1`.`motherId`
-	 from `User` as t0
-	 left join `User` as t1 on `t0`.`fatherId` = `t1`.`id`
- ) as t2
- left join `User` as t3 on `t2`.`motherId` = `t3`.`id`
-
-
---------------
-# Done
-
-# support Mysql
+# DbFunction.PrimitiveSql

+ 54 - 5
Vitorm.sln

@@ -8,7 +8,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{75C25D0B-852
 		README.md = README.md
 	EndProjectSection
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm", "src\Vitorm\Vitorm.csproj", "{EDBF76EF-3DEB-4C59-9215-FEB4CC8EC6F2}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7904FE51-04FF-4477-8E3A-CC340389EE32}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm", "src\Vitorm\Vitorm.csproj", "{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{05176905-A2A5-4015-9F04-2904506C902F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.MySql", "src\Vitorm.MySql\Vitorm.MySql.csproj", "{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Sqlite", "src\Vitorm.Sqlite\Vitorm.Sqlite.csproj", "{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.SqlServer", "src\Vitorm.SqlServer\Vitorm.SqlServer.csproj", "{39137BC4-2069-4061-8822-884F4E4C1418}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.MySql.MsTest", "test\Vitorm.MySql.MsTest\Vitorm.MySql.MsTest.csproj", "{8DD2F4AD-F533-408D-A527-9737D675A5F0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Sqlite.MsTest", "test\Vitorm.Sqlite.MsTest\Vitorm.Sqlite.MsTest.csproj", "{B036D75F-AF60-4329-ABBB-252BF47F9435}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.SqlServer.MsTest", "test\Vitorm.SqlServer.MsTest\Vitorm.SqlServer.MsTest.csproj", "{31CE5879-959E-4882-8FBC-0D763FD73A0C}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -16,14 +32,47 @@ Global
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{EDBF76EF-3DEB-4C59-9215-FEB4CC8EC6F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{EDBF76EF-3DEB-4C59-9215-FEB4CC8EC6F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{EDBF76EF-3DEB-4C59-9215-FEB4CC8EC6F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{EDBF76EF-3DEB-4C59-9215-FEB4CC8EC6F2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{39137BC4-2069-4061-8822-884F4E4C1418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{39137BC4-2069-4061-8822-884F4E4C1418}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{39137BC4-2069-4061-8822-884F4E4C1418}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{39137BC4-2069-4061-8822-884F4E4C1418}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8DD2F4AD-F533-408D-A527-9737D675A5F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8DD2F4AD-F533-408D-A527-9737D675A5F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8DD2F4AD-F533-408D-A527-9737D675A5F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8DD2F4AD-F533-408D-A527-9737D675A5F0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B036D75F-AF60-4329-ABBB-252BF47F9435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B036D75F-AF60-4329-ABBB-252BF47F9435}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B036D75F-AF60-4329-ABBB-252BF47F9435}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B036D75F-AF60-4329-ABBB-252BF47F9435}.Release|Any CPU.Build.0 = Release|Any CPU
+		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{31CE5879-959E-4882-8FBC-0D763FD73A0C}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{FA62D5F8-F6C1-4677-B115-DC32CF9EFC50} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{6ED8DCE2-FAE1-4EC3-9C2A-74F8ACCAF3CB} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{0E70816E-4E8B-4A6D-B1BA-440A7C50D91D} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{39137BC4-2069-4061-8822-884F4E4C1418} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{8DD2F4AD-F533-408D-A527-9737D675A5F0} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+		{B036D75F-AF60-4329-ABBB-252BF47F9435} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+		{31CE5879-959E-4882-8FBC-0D763FD73A0C} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {C7DA16E3-9949-49FA-B0B4-F830636DE60F}
 	EndGlobalSection

+ 46 - 0
src/Vitorm.MySql/DbContext_Extensions.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Data;
+
+using Vitorm.Entity;
+using Vitorm.Entity.Dapper;
+using Vitorm.Sql;
+using Vitorm.Sql.SqlTranslate;
+
+namespace Vit.Extensions
+{
+    public static class DbContext_Extensions
+    {
+        /*
+         // ref: https://dev.mysql.com/doc/refman/8.4/en/savepoint.html
+         //  https://dev.mysql.com/doc/refman/8.4/en/commit.html
+
+        START TRANSACTION;
+            SET autocommit=0;
+            SAVEPOINT tran0;
+                select '';
+            -- ROLLBACK WORK TO SAVEPOINT tran0;
+            RELEASE SAVEPOINT tran0;
+        COMMIT;
+        -- ROLLBACK;
+         */
+        public static SqlDbContext UseMySql(this SqlDbContext dbContext, string ConnectionString)
+        {
+            ISqlTranslateService sqlTranslateService =   Vitorm.MySql.SqlTranslateService.Instance;
+
+            Func<IDbConnection> createDbConnection = () => new MySqlConnector.MySqlConnection(ConnectionString);
+
+            Func<Type, IEntityDescriptor> getEntityDescriptor = (type) => EntityDescriptor.GetEntityDescriptor(type);
+
+
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, getEntityDescriptor: getEntityDescriptor);
+
+            dbContext.createTransactionScope = (dbContext) => new Vitorm.MySql.SqlTransactionScope(dbContext);
+            //dbContext.createTransactionScope = (dbContext) => new Vitorm.Mysql.SqlTransactionScope_Command(dbContext);
+
+            return dbContext;
+        }
+
+
+
+    }
+}

+ 77 - 0
src/Vitorm.MySql/SqlTransactionScope.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Data;
+
+using Vitorm.Extensions;
+using Vitorm.Sql;
+using Vitorm.Sql.Transaction;
+using SqlTransaction = MySqlConnector.MySqlTransaction;
+
+namespace Vitorm.MySql
+{
+    public class SqlTransactionScope : Vitorm.Sql.Transaction.SqlTransactionScope
+    {
+        int savePointCount = 0;
+        public DbTransactionWrap CreateTransactionSavePoint(IDbTransaction originalTransaction)
+        {
+            var savePointName = "tran" + savePointCount++;
+            return new DbTransactionWrapSavePoint(originalTransaction, savePointName);
+        }
+        public SqlTransactionScope(SqlDbContext dbContext) : base(dbContext)
+        {
+        }
+
+        public override IDbTransaction BeginTransaction()
+        {
+            DbTransactionWrap transactionWrap;
+            IDbTransaction originalTransaction = GetCurrentTransaction();
+            if (originalTransaction == null)
+            {
+                var dbConnection = dbContext.dbConnection as MySqlConnector.MySqlConnection;
+                if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
+
+                originalTransaction = dbConnection.BeginTransaction();
+                dbConnection.Execute("SET autocommit=0;", transaction: originalTransaction);
+                transactionWrap = new DbTransactionWrap(originalTransaction);
+            }
+            else
+            {
+                transactionWrap = CreateTransactionSavePoint(originalTransaction);
+            }
+
+            transactions.Push(transactionWrap);
+            return transactionWrap;
+        }
+
+
+
+        public class DbTransactionWrapSavePoint : DbTransactionWrap
+        {
+            public SqlTransaction sqlTran => (SqlTransaction)originalTransaction;
+            string savePointName;
+            public DbTransactionWrapSavePoint(IDbTransaction transaction, string savePointName) : base(transaction)
+            {
+                this.savePointName = savePointName;
+                sqlTran.Save(savePointName);
+            }
+
+            public override void Commit()
+            {
+                sqlTran.Release(savePointName);
+                TransactionState = ETransactionState.Committed;
+            }
+
+            public override void Dispose()
+            {
+                if (TransactionState == ETransactionState.Active)
+                    sqlTran.Rollback(savePointName);
+                TransactionState = ETransactionState.Disposed;
+            }
+
+            public override void Rollback()
+            {
+                sqlTran.Rollback(savePointName);
+                TransactionState = ETransactionState.RolledBack;
+            }
+        }
+    }
+}

+ 145 - 0
src/Vitorm.MySql/SqlTransactionScope_Command.cs

@@ -0,0 +1,145 @@
+using System.Data;
+
+using Vitorm.Sql;
+using Vitorm.Sql.Transaction;
+using System.Collections.Generic;
+using static Vitorm.Sql.Transaction.DbTransactionWrap;
+using Vitorm.Extensions;
+
+namespace Vitorm.MySql
+{
+    public class SqlTransactionScope_Command : ITransactionScope
+    {
+        protected SqlDbContext dbContext;
+        protected Stack<DbTransactionWrapSavePoint> savePoints = new();
+        int savePointCount = 0;
+        public SqlTransactionScope_Command(SqlDbContext dbContext)
+        {
+            this.dbContext = dbContext;
+        }
+
+        DbTransactionWrap_Command dbTransactionWrap;
+        public virtual IDbTransaction BeginTransaction()
+        {
+            if (dbTransactionWrap == null)
+            {
+                var dbConnection = dbContext.dbConnection;
+                if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
+
+                dbTransactionWrap = new DbTransactionWrap_Command(dbConnection);
+                return dbTransactionWrap;
+
+            }
+            var savePointName = "tran" + savePointCount++;
+            var savePoint = dbTransactionWrap.BeginSavePoint(savePointName);
+
+            savePoints.Push(savePoint);
+            return savePoint;
+        }
+
+        public virtual IDbTransaction GetCurrentTransaction() => null;
+
+        public virtual void Dispose()
+        {
+            while (savePoints?.Count > 0)
+            {
+                var transaction = savePoints.Pop();
+                if (transaction?.TransactionState != DbTransactionWrap.ETransactionState.Disposed)
+                {
+                    transaction?.Dispose();
+                }
+            }
+            savePoints = null;
+
+            dbTransactionWrap?.Dispose();
+            dbTransactionWrap = null;
+        }
+
+
+
+        public class DbTransactionWrap_Command : IDbTransaction
+        {
+            public virtual System.Data.IsolationLevel IsolationLevel => default;
+            public IDbConnection Connection { get; protected set; }
+
+            public virtual ETransactionState TransactionState { get; protected set; } = ETransactionState.Active;
+
+            public DbTransactionWrap_Command(IDbConnection connection)
+            {
+                this.Connection = connection;
+                Execute($"START TRANSACTION; SET autocommit=0;");
+            }
+
+            public void Commit()
+            {
+                Execute($"COMMIT;");
+                TransactionState = ETransactionState.Committed;
+            }
+
+            public void Dispose()
+            {
+                if (TransactionState == ETransactionState.Active)
+                {
+                    Execute($"ROLLBACK;");
+                }
+                TransactionState = ETransactionState.Disposed;
+            }
+
+            public void Rollback()
+            {
+                Execute($"ROLLBACK;");
+                TransactionState = ETransactionState.RolledBack;
+            }
+            public DbTransactionWrapSavePoint BeginSavePoint(string savePoint)
+            {
+                return new DbTransactionWrapSavePoint(Connection, savePoint);
+            }
+            protected virtual void Execute(string sql)
+            {
+                Connection.Execute(sql);
+            }
+        }
+
+        public class DbTransactionWrapSavePoint : IDbTransaction
+        {
+            public virtual System.Data.IsolationLevel IsolationLevel => default;
+            public IDbConnection Connection { get; protected set; }
+
+            public virtual ETransactionState TransactionState { get; protected set; } = ETransactionState.Active;
+            protected string savePointName;
+
+
+            protected virtual void Execute(string sql)
+            {
+                Connection.Execute(sql);
+            }
+            public DbTransactionWrapSavePoint(IDbConnection connection, string savePointName)
+            {
+                this.Connection = connection;
+                this.savePointName = savePointName;
+                Execute($"SAVEPOINT {savePointName};");
+            }
+
+            public void Commit()
+            {
+                Execute($"RELEASE SAVEPOINT {savePointName};");
+                TransactionState = ETransactionState.Committed;
+            }
+
+            public void Dispose()
+            {
+                if (TransactionState == ETransactionState.Active)
+                {
+                    Execute($"ROLLBACK WORK TO SAVEPOINT {savePointName};");
+                }
+                TransactionState = ETransactionState.Disposed;
+            }
+
+            public void Rollback()
+            {
+                Execute($"ROLLBACK WORK TO SAVEPOINT {savePointName};");
+                TransactionState = ETransactionState.RolledBack;
+            }
+        }
+    }
+}

+ 279 - 0
src/Vitorm.MySql/SqlTranslateService.cs

@@ -0,0 +1,279 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+using Vit.Extensions.Linq_Extensions;
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vitorm.Entity;
+using Vitorm.Sql;
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.MySql.TranslateService;
+using System.ComponentModel.DataAnnotations.Schema;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MySql
+{
+    public class SqlTranslateService : Vitorm.Sql.SqlTranslate.SqlTranslateService
+    {
+        public static readonly SqlTranslateService Instance = new SqlTranslateService();
+
+        protected QueryTranslateService queryTranslateService;
+        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
+        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+
+        public SqlTranslateService()
+        {
+            queryTranslateService = new QueryTranslateService(this);
+            executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
+            executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
+        }
+        /// <summary>
+        ///     Generates the delimited SQL representation of an identifier (column name, table name, etc.).
+        /// </summary>
+        /// <param name="identifier">The identifier to delimit.</param>
+        /// <returns>
+        ///     The generated string.
+        /// </returns>
+        public override string DelimitIdentifier(string identifier) => $"`{EscapeIdentifier(identifier)}`"; // Interpolation okay; strings
+
+        /// <summary>
+        ///     Generates the escaped SQL representation of an identifier (column name, table name, etc.).
+        /// </summary>
+        /// <param name="identifier">The identifier to be escaped.</param>
+        /// <returns>
+        ///     The generated string.
+        /// </returns>
+        public override string EscapeIdentifier(string identifier) => identifier.Replace("`", "\\`");
+
+
+        #region EvalExpression
+        /// <summary>
+        /// read where or value or on
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        /// <exception cref="NotSupportedException"></exception>
+        /// <param name="data"></param>
+        public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode data)
+        {
+            switch (data.nodeType)
+            {
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = data;
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 ToString
+                            case nameof(object.ToString):
+                                {
+                                    return $"cast({EvalExpression(arg, methodCall.@object)} as char)";
+                                }
+
+                            #region ##2 String method:  StartsWith EndsWith Contains
+                            case nameof(string.StartsWith): // String.StartsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like concat({EvalExpression(arg, value)},'%')";
+                                }
+                            case nameof(string.EndsWith): // String.EndsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)})";
+                                }
+                            case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)},'%')";
+                                }
+                                #endregion
+                        }
+                        break;
+                    }
+
+                #region Read Value
+                case NodeType.Convert:
+                    {
+                        // cast( 4.1 as signed)
+
+                        ExpressionNode_Convert convert = data;
+
+                        Type targetType = convert.valueType?.ToType();
+
+                        if (targetType == typeof(object)) return EvalExpression(arg, convert.body);
+
+                        // Nullable
+                        if (targetType.IsGenericType) targetType = targetType.GetGenericArguments()[0];
+
+                        string targetDbType = GetDbType(targetType);
+
+                        var sourceType = convert.body.Member_GetType();
+                        if (sourceType != null)
+                        {
+                            if (sourceType.IsGenericType) sourceType = sourceType.GetGenericArguments()[0];
+
+                            if (targetDbType == GetDbType(sourceType)) return EvalExpression(arg, convert.body);
+                        }
+
+                        if (targetType == typeof(string))
+                        {
+                            return $"cast({EvalExpression(arg, convert.body)} as char)";
+                        }
+
+                        return $"cast({EvalExpression(arg, convert.body)} as {targetDbType})";
+                    }
+                case nameof(ExpressionType.Add):
+                    {
+                        ExpressionNode_Binary binary = data;
+
+                        // ##1 String Add
+                        if (data.valueType?.ToType() == typeof(string))
+                        {
+                            return $"CONCAT({EvalExpression(arg, binary.left)} ,{EvalExpression(arg, binary.right)})";
+                        }
+
+                        // ##2 Numberic Add
+                        return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
+                    }
+                case nameof(ExpressionType.Coalesce):
+                    {
+                        ExpressionNode_Binary binary = data;
+                        return $"COALESCE({EvalExpression(arg, binary.left)},{EvalExpression(arg, binary.right)})";
+                    }
+                    #endregion
+
+            }
+
+            return base.EvalExpression(arg, data);
+        }
+        #endregion
+
+
+
+        #region PrepareCreate
+        public override string PrepareCreate(IEntityDescriptor entityDescriptor)
+        {
+            /* //sql
+CREATE TABLE user (
+  id int NOT NULL PRIMARY KEY,
+  name varchar(100) DEFAULT NULL,
+  birth date DEFAULT NULL,
+  fatherId int DEFAULT NULL,
+  motherId int DEFAULT NULL
+) ;
+              */
+            List<string> sqlFields = new();
+
+            // #1 primary key
+            sqlFields.Add(GetColumnSql(entityDescriptor.key) + " PRIMARY KEY " + (entityDescriptor.key.databaseGenerated == DatabaseGeneratedOption.Identity ? "AUTO_INCREMENT " : ""));
+
+            // #2 columns
+            entityDescriptor.columns?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
+
+            return $@"
+CREATE TABLE {DelimitIdentifier(entityDescriptor.tableName)} (
+{string.Join(",\r\n  ", sqlFields)}
+)";
+
+
+            string GetColumnSql(IColumnDescriptor column)
+            {
+                var dbType = column.databaseType ?? GetDbType(column.type);
+                // name varchar(100) DEFAULT NULL
+                return $"  {DelimitIdentifier(column.name)} {dbType} {(column.nullable ? "DEFAULT NULL" : "NOT NULL")}";
+            }
+        }
+        protected override string GetDbType(Type type)
+        {
+            type = TypeUtil.GetUnderlyingType(type);
+
+            if (type == typeof(DateTime))
+                return "DATETIME";
+
+            if (type == typeof(string))
+                return "varchar(1000)";
+
+            if (type == typeof(float)) return "FLOAT";
+            if (type == typeof(double) || type == typeof(decimal))
+                return "DOUBLE";
+
+            if (type == typeof(Int32)) return "INTEGER";
+            if (type == typeof(Int16)) return "SMALLINT";
+            if (type == typeof(byte)) return "TINYINT";
+            if (type == typeof(bool)) return "TINYINT";
+
+            if (type.Name.ToLower().Contains("int")) return "INTEGER";
+
+            throw new NotSupportedException("unsupported column type:" + type.Name);
+        }
+        #endregion
+
+
+        public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg)
+        {
+            /* //sql
+             insert into user(name,birth,fatherId,motherId) values('','','');
+             select seq from sqlite_sequence where name='user';
+              */
+            var entityDescriptor = arg.entityDescriptor;
+
+            var columns = entityDescriptor.columns;
+
+            // #1 GetSqlParams 
+            Func<object, Dictionary<string, object>> GetSqlParams = (entity) =>
+            {
+                var sqlParam = new Dictionary<string, object>();
+                foreach (var column in columns)
+                {
+                    var columnName = column.name;
+                    var value = column.GetValue(entity);
+
+                    sqlParam[columnName] = value;
+                }
+                return sqlParam;
+            };
+
+            #region #2 columns 
+            List<string> columnNames = new List<string>();
+            List<string> valueParams = new List<string>();
+            string columnName;
+
+            foreach (var column in columns)
+            {
+                columnName = column.name;
+
+                columnNames.Add(DelimitIdentifier(columnName));
+                valueParams.Add(GenerateParameterName(columnName));
+            }
+            #endregion
+
+            // #3 build sql
+            string sql = $@"insert into {DelimitIdentifier(entityDescriptor.tableName)}({string.Join(",", columnNames)}) values({string.Join(",", valueParams)});";
+            sql += "select last_insert_id();";
+            return (sql, GetSqlParams);
+        }
+
+        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);
+        }
+
+
+
+    }
+}

+ 56 - 0
src/Vitorm.MySql/TranslateService/ExecuteDeleteTranslateService.cs

@@ -0,0 +1,56 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MySql.TranslateService
+{
+    public class ExecuteDeleteTranslateService : BaseQueryTranslateService
+    {
+        /*
+WITH tmp AS (
+    select u.id 
+    from User u
+    left join User father on u.fatherId = father.id 
+    where u.id > 0
+)
+delete from User where id in ( SELECT id FROM tmp );
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            var sqlInner = base.BuildQuery(arg, stream);
+
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"delete from {sqlTranslator.DelimitIdentifier(tableName)} ";
+
+            sql += $"{NewLine}where {sqlTranslator.DelimitIdentifier(keyName)} in ( SELECT {sqlTranslator.DelimitIdentifier(keyName)} FROM tmp ); {NewLine}";
+
+            return sql;
+        }
+
+
+        public ExecuteDeleteTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            // primary key
+            return $"{prefix} {sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}";
+        }
+
+
+
+    }
+}

+ 82 - 0
src/Vitorm.MySql/TranslateService/ExecuteUpdateTranslateService.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MySql.TranslateService
+{
+    public class ExecuteUpdateTranslateService : BaseQueryTranslateService
+    {
+        /*
+
+-- multiple
+WITH tmp AS (
+    select  concat('u' , cast(u.id as char) , '_' , COALESCE(cast(father.id as char),'') ) as name , u.id 
+    from `User` u
+    left join `User` father on u.fatherId = father.id 
+    where u.id > 0
+)
+UPDATE `User` t0,tmp
+  SET t0.name =  tmp.name
+where t0.id = tmp.id ;
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(arg, stream);
+
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate?.memberArgs;
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"UPDATE {sqlTranslator.DelimitIdentifier(tableName)} t0, tmp{NewLine}";
+            sql += $"Set ";
+
+            var sqlToUpdateCols = columnsToUpdate
+                .Select(m => m.name)
+                .Select(name => $"{NewLine}  {sqlTranslator.GetSqlField("t0", name)} = {sqlTranslator.GetSqlField("tmp", name)} ");
+
+            sql += string.Join(",", sqlToUpdateCols);
+
+            sql += $"{NewLine}where {sqlTranslator.GetSqlField("t0", keyName)}={sqlTranslator.GetSqlField("tmp", keyName)} ";
+
+            return sql;
+        }
+
+
+        public ExecuteUpdateTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate?.memberArgs;
+
+            if (columnsToUpdate?.Any() != true) throw new ArgumentException("can not get columns to update");
+
+            var sqlFields = new List<string>();
+
+            foreach (var column in columnsToUpdate)
+            {
+                sqlFields.Add($"({sqlTranslator.EvalExpression(arg, column.value)}) as {sqlTranslator.DelimitIdentifier(column.name)}");
+            }
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}");
+
+            return prefix + " " + String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 27 - 0
src/Vitorm.MySql/Vitorm.MySql.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <pack>nuget</pack>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <Version>1.0.0-temp</Version>
+        <LangVersion>9.0</LangVersion>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Authors>Lith</Authors>
+        <Description>orm for MySql</Description>
+        <PackageProjectUrl>https://github.com/VitormLib/Vitorm.MySql</PackageProjectUrl>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="MySqlConnector" Version="2.3.7" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Vitorm\Vitorm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 32 - 0
src/Vitorm.SqlServer/DbContext_Extensions.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Data;
+
+using Vitorm.Entity;
+using Vitorm.Entity.Dapper;
+using Vitorm.Sql;
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.SqlServer;
+
+namespace Vit.Extensions
+{
+    public static class DbContext_Extensions
+    {
+        public static SqlDbContext UseSqlServer(this SqlDbContext dbContext, string ConnectionString)
+        {
+            ISqlTranslateService sqlTranslateService = Vitorm.SqlServer.SqlTranslateService.Instance;
+
+            Func<IDbConnection> createDbConnection = () => new Microsoft.Data.SqlClient.SqlConnection(ConnectionString);
+
+            Func<Type, IEntityDescriptor> getEntityDescriptor = (type) => EntityDescriptor.GetEntityDescriptor(type);
+
+
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, getEntityDescriptor: getEntityDescriptor);
+            dbContext.createTransactionScope = (dbContext) => new Vitorm.SqlServer.SqlTransactionScope(dbContext);
+
+            return dbContext;
+        }
+
+
+
+    }
+}

+ 77 - 0
src/Vitorm.SqlServer/SqlTransactionScope.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Data;
+
+using Vitorm.Sql;
+using Vitorm.Sql.Transaction;
+
+using SqlTransaction = Microsoft.Data.SqlClient.SqlTransaction;
+
+namespace Vitorm.SqlServer
+{
+    public class SqlTransactionScope : Vitorm.Sql.Transaction.SqlTransactionScope
+    {
+        int savePointCount = 0;
+        public DbTransactionWrap CreateTransactionSavePoint(IDbTransaction originalTransaction)
+        {
+            var savePointName = "tran" + savePointCount++;
+            return new DbTransactionWrapSavePoint(originalTransaction, savePointName);
+        }
+        public SqlTransactionScope(SqlDbContext dbContext) : base(dbContext)
+        {
+        }
+
+        public override IDbTransaction BeginTransaction()
+        {
+            DbTransactionWrap transactionWrap;
+            IDbTransaction originalTransaction = GetCurrentTransaction();
+            if (originalTransaction == null)
+            {
+                var dbConnection = dbContext.dbConnection;
+                if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
+                originalTransaction = dbConnection.BeginTransaction();
+
+                transactionWrap = new DbTransactionWrap(originalTransaction);
+            }
+            else
+            {
+                transactionWrap = CreateTransactionSavePoint(originalTransaction);
+            }
+
+            transactions.Push(transactionWrap);
+            return transactionWrap;
+        }
+
+    }
+
+    public class DbTransactionWrapSavePoint : DbTransactionWrap
+    {
+        public SqlTransaction sqlTran => (SqlTransaction)originalTransaction;
+        string savePointName;
+        public DbTransactionWrapSavePoint(IDbTransaction transaction, string savePointName) : base(transaction)
+        {
+            this.savePointName = savePointName;
+            sqlTran.Save(savePointName);
+        }
+
+        public override void Commit()
+        {
+            // no need to commit savepoint for sqlserver, ref: https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqltransaction.save
+
+            //sqlTran.Commit(savePointName);
+            TransactionState = ETransactionState.Committed;
+        }
+
+        public override void Dispose()
+        {
+            if (TransactionState == ETransactionState.Active)
+                sqlTran.Rollback(savePointName);
+            TransactionState = ETransactionState.Disposed;
+        }
+
+        public override void Rollback()
+        {
+            sqlTran.Rollback(savePointName);
+            TransactionState = ETransactionState.RolledBack;
+        }
+    }
+}

+ 173 - 0
src/Vitorm.SqlServer/SqlTranslate/BaseQueryTranslateService.cs

@@ -0,0 +1,173 @@
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.SqlServer.SqlTranslate
+{
+
+    public abstract class BaseQueryTranslateService : Vitorm.Sql.SqlTranslate.BaseQueryTranslateService
+    {
+        public BaseQueryTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+
+        /*
+SELECT [t].[id], [t].[birth], [t].[fatherId], [t].[motherId], [t].[name]
+FROM (
+    SELECT [m].[id], [m].[birth], [m].[fatherId], [m].[motherId], [m].[name], ROW_NUMBER() OVER(ORDER BY [m].[fatherId], [m].[motherId] DESC) AS [__RowNumber__]
+    FROM [User] AS [m]
+    WHERE [m].[id] <> 2
+) AS [t]
+WHERE ([t].[__RowNumber__] > 1) AND ([t].[__RowNumber__] <= 13);
+
+// if no orders:
+ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
+
+         */
+
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            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)
+                {
+                    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 #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 #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__]
+    FROM [User] AS [m]
+    WHERE [m].[id] <> 2
+) AS [t]
+WHERE ([t].[__RowNumber__] > 1) AND ([t].[__RowNumber__] <= 13);
+                 */
+                return $@"
+SELECT *
+FROM (
+    {sql}
+) AS [t]
+WHERE [t].[__RowNumber__] > {stream.skip} {(stream.take > 0 ? "AND [t].[__RowNumber__] <= " + (stream.take + stream.skip) : "")} ;
+";
+
+                #endregion
+
+
+                #endregion
+
+            }
+            else
+            {
+                #region select top 10 *
+
+                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
+
+                // #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 #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
+            }
+        }
+
+
+
+    }
+
+}

+ 56 - 0
src/Vitorm.SqlServer/SqlTranslate/ExecuteDeleteTranslateService.cs

@@ -0,0 +1,56 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.SqlServer.TranslateService
+{
+    public class ExecuteDeleteTranslateService : Vitorm.SqlServer.SqlTranslate.BaseQueryTranslateService
+    {
+        /*
+WITH tmp AS (
+    select u.id 
+    from User u
+    left join User father on u.fatherId = father.id 
+    where u.id > 0
+)
+delete from User where id in ( SELECT id FROM tmp );
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            var sqlInner = base.BuildQuery(arg, stream);
+
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"delete from {sqlTranslator.DelimitIdentifier(tableName)} ";
+
+            sql += $"{NewLine}where {sqlTranslator.DelimitIdentifier(keyName)} in ( SELECT {sqlTranslator.DelimitIdentifier(keyName)} FROM tmp ); {NewLine}";
+
+            return sql;
+        }
+
+
+        public ExecuteDeleteTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            // primary key
+            return $"{prefix} {sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}";
+        }
+
+
+
+    }
+}

+ 85 - 0
src/Vitorm.SqlServer/SqlTranslate/ExecuteUpdateTranslateService.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.SqlServer.TranslateService
+{
+    public class ExecuteUpdateTranslateService : Vitorm.SqlServer.SqlTranslate.BaseQueryTranslateService
+    {
+        /*
+
+-- multiple
+WITH tmp AS (
+    select  ('u' + cast(u.id as varchar(max)) + '_' + COALESCE(cast(father.id as varchar(max)),'') ) as name , u.id 
+    from [User] u
+    left join [User] father on u.fatherId = father.id 
+    where u.id > 0
+)
+UPDATE [User]
+  SET name =  tmp.name
+  from [User] t0
+  inner join tmp on t0.id=tmp.id ;
+
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(arg, stream);
+
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate?.memberArgs;
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"UPDATE {sqlTranslator.DelimitIdentifier(tableName)}{NewLine}";
+            sql += $"Set ";
+
+            var sqlToUpdateCols = columnsToUpdate
+                .Select(m => m.name)
+                .Select(name => $"{NewLine}  {sqlTranslator.DelimitIdentifier(name)} = {sqlTranslator.GetSqlField("tmp", name)} ");
+
+            sql += string.Join(",", sqlToUpdateCols);
+
+            sql += $"{NewLine}from {sqlTranslator.DelimitIdentifier(tableName)} t0";
+            sql += $"{NewLine}inner join tmp on t0.{sqlTranslator.DelimitIdentifier(keyName)}=tmp.{sqlTranslator.DelimitIdentifier(keyName)}";
+
+            return sql;
+        }
+
+
+        public ExecuteUpdateTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate) ?.fieldsToUpdate?.memberArgs;
+
+            if (columnsToUpdate?.Any() != true) throw new ArgumentException("can not get columns to update");
+
+            var sqlFields = new List<string>();
+
+            foreach (var column in columnsToUpdate)
+            {
+                sqlFields.Add($"({sqlTranslator.EvalExpression(arg, column.value)}) as {sqlTranslator.DelimitIdentifier(column.name)}");
+            }
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}");
+
+            return prefix + " " + String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 67 - 0
src/Vitorm.SqlServer/SqlTranslate/QueryTranslateService.cs

@@ -0,0 +1,67 @@
+using System;
+
+using Vit.Extensions.Vitorm_Extensions;
+
+using Vitorm.DataReader;
+using Vitorm.Sql.DataReader;
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.SqlServer.SqlTranslate
+{
+    public class QueryTranslateService: BaseQueryTranslateService
+    {
+
+        /* // sql
+SELECT [t].[id], [t].[birth], [t].[fatherId], [t].[motherId], [t].[name]
+FROM (
+    SELECT [m].[id], [m].[birth], [m].[fatherId], [m].[motherId], [m].[name], ROW_NUMBER() OVER(ORDER BY [m].[fatherId], [m].[motherId] DESC) AS [__RowNumber__]
+    FROM [User] AS [m]
+    WHERE [m].[id] <> 2
+) AS [t]
+WHERE ([t].[__RowNumber__] > 1) AND ([t].[__RowNumber__] <= 13);
+
+// if no orders:
+ROW_NUMBER() OVER(ORDER BY @@RowCount) AS [__RowNumber__]
+
+         */
+
+        public QueryTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            switch (stream.method)
+            {
+                case "Count":
+                    {
+                        var reader = new NumScalarReader();
+                        if (arg.dataReader == null) arg.dataReader = reader;
+                        return prefix+" "+ "count(*)";
+                    }
+                case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
+                    {
+                        var reader = new EntityReader();
+                        return prefix + " " + BuildReader(arg, stream, reader);
+                    }
+                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                    {
+                        stream.take = 1;
+                        stream.skip = null;
+
+                        if (stream.method.Contains("Last"))
+                            ReverseOrder(arg, stream);
+
+                        var nullable = stream.method.Contains("OrDefault");
+                        var reader = new FirstEntityReader { nullable = nullable };
+                        return prefix + " " + BuildReader(arg, stream, reader);
+                    }
+            }
+            throw new NotSupportedException("not supported method: " + stream.method);
+        }
+
+
+    }
+}

+ 241 - 0
src/Vitorm.SqlServer/SqlTranslateService.cs

@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq.Expressions;
+
+using Vit.Extensions.Linq_Extensions;
+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
+{
+    public class SqlTranslateService : Vitorm.Sql.SqlTranslate.SqlTranslateService
+    {
+        public static readonly SqlTranslateService Instance = new SqlTranslateService();
+
+        protected Vitorm.SqlServer.SqlTranslate.QueryTranslateService queryTranslateService;
+        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
+        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+
+    
+        public SqlTranslateService()
+        {
+            queryTranslateService = new(this);
+            executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
+            executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
+        }
+
+        /// <summary>
+        ///     Generates the delimited SQL representation of an identifier (column name, table name, etc.).
+        /// </summary>
+        /// <param name="identifier">The identifier to delimit.</param>
+        /// <returns>
+        ///     The generated string.
+        /// </returns>
+        public override string DelimitIdentifier(string identifier) => $"[{EscapeIdentifier(identifier)}]"; // Interpolation okay; strings
+
+        /// <summary>
+        ///     Generates the escaped SQL representation of an identifier (column name, table name, etc.).
+        /// </summary>
+        /// <param name="identifier">The identifier to be escaped.</param>
+        /// <returns>
+        ///     The generated string.
+        /// </returns>
+        public override string EscapeIdentifier(string identifier) => identifier.Replace("[", "\"[").Replace("]", "\"]");
+
+
+        #region EvalExpression
+        /// <summary>
+        /// read where or value or on
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        /// <exception cref="NotSupportedException"></exception>
+        /// <param name="data"></param>
+        public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode data)
+        {
+            switch (data.nodeType)
+            {
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = data;
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 ToString
+                            case nameof(object.ToString):
+                                {
+                                    return $"cast({EvalExpression(arg, methodCall.@object)} as varchar(max))";
+                                }
+
+                            #region ##2 String method:  StartsWith EndsWith Contains
+                            case nameof(string.StartsWith): // String.StartsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like {EvalExpression(arg, value)}+'%'";
+                                }
+                            case nameof(string.EndsWith): // String.EndsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like '%'+{EvalExpression(arg, value)}";
+                                }
+                            case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like '%'+{EvalExpression(arg, value)}+'%'";
+                                }
+                            #endregion
+                        }
+                        break;
+                    }
+
+                #region Read Value
+                case NodeType.Convert:
+                    {
+                        // cast( 4.1 as signed)
+
+                        ExpressionNode_Convert convert = data;
+
+                        Type targetType = convert.valueType?.ToType();
+
+                        if (targetType == typeof(object)) return EvalExpression(arg, convert.body);
+
+                        // Nullable
+                        if (targetType.IsGenericType) targetType = targetType.GetGenericArguments()[0];
+
+                        string targetDbType = GetDbType(targetType);
+
+                        var sourceType = convert.body.Member_GetType();
+                        if (sourceType != null)
+                        {
+                            if (sourceType.IsGenericType) sourceType = sourceType.GetGenericArguments()[0];
+
+                            if (targetDbType == GetDbType(sourceType)) return EvalExpression(arg, convert.body);
+                        }
+
+                        return $"cast({EvalExpression(arg, convert.body)} as {targetDbType})";
+                    }
+                case nameof(ExpressionType.Add):
+                    {
+                        ExpressionNode_Binary binary = data;
+
+                        // ##1 String Add
+                        if (data.valueType?.ToType() == typeof(string))
+                        {
+                            return $"CONCAT({EvalExpression(arg, binary.left)} ,{EvalExpression(arg, binary.right)})";
+                        }
+
+                        // ##2 Numberic Add
+                        return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
+                    }
+                case nameof(ExpressionType.Coalesce):
+                    {
+                        ExpressionNode_Binary binary = data;
+                        return $"COALESCE({EvalExpression(arg, binary.left)},{EvalExpression(arg, binary.right)})";
+                    }
+                    #endregion
+
+            }
+
+            return base.EvalExpression(arg, data);
+        }
+        #endregion
+
+
+        #region PrepareCreate
+        public override string PrepareCreate(IEntityDescriptor entityDescriptor)
+        {
+            /* //sql
+CREATE TABLE user (
+  id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  name varchar(100) DEFAULT NULL,
+  birth date DEFAULT NULL,
+  fatherId int DEFAULT NULL,
+  motherId int DEFAULT NULL
+) ;
+              */
+            List<string> sqlFields = new();
+
+            // #1 primary key
+            sqlFields.Add(GetColumnSql(entityDescriptor.key) + " " + (entityDescriptor.key.databaseGenerated == DatabaseGeneratedOption.Identity ? "PRIMARY KEY IDENTITY(1,1) " : ""));
+
+            // #2 columns
+            entityDescriptor.columns?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
+
+            return $@"
+CREATE TABLE [dbo].{DelimitIdentifier(entityDescriptor.tableName)} (
+{string.Join(",\r\n  ", sqlFields)}
+)";
+
+
+            string GetColumnSql(IColumnDescriptor column)
+            {
+                var dbType = column.databaseType ?? GetDbType(column.type);
+                // name varchar(100) DEFAULT NULL
+                return $"  {DelimitIdentifier(column.name)} {dbType} {(column.nullable ? "DEFAULT NULL" : "NOT NULL")}";
+            }
+        }
+
+        protected readonly static Dictionary<Type, string> dbTypeMap = new()
+        {
+            [typeof(DateTime)] = "datetime",
+            [typeof(string)] = "varchar(max)",
+
+            [typeof(float)] = "float",
+            [typeof(double)] = "float",
+            [typeof(decimal)] = "float",
+
+            [typeof(Int32)] = "int",
+            [typeof(Int16)] = "smallint",
+            [typeof(byte)] = "tinyint",
+            [typeof(bool)] = "bit",
+        };
+        protected override string GetDbType(Type type)
+        {
+            type = TypeUtil.GetUnderlyingType(type);
+
+            if (dbTypeMap.TryGetValue(type, out var dbType)) return dbType;
+            throw new NotSupportedException("unsupported column type:" + type.Name);
+        }
+        #endregion
+
+
+
+        public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg)
+        {
+            var result = base.PrepareAdd(arg);
+
+            // get generated id
+            result.sql += "select convert(int,isnull(SCOPE_IDENTITY(),-1));";
+
+            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);
+        }
+
+
+
+    }
+}

+ 27 - 0
src/Vitorm.SqlServer/Vitorm.SqlServer.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <pack>nuget</pack>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <Version>1.0.0-temp</Version>
+        <LangVersion>9.0</LangVersion>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Authors>Lith</Authors>
+        <Description>orm for SqlServer</Description>
+        <PackageProjectUrl>https://github.com/VitormLib/Vitorm.SqlServer</PackageProjectUrl>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Vitorm\Vitorm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 32 - 0
src/Vitorm.Sqlite/DbContext_Extensions.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Data;
+
+using Vitorm.Entity;
+using Vitorm.Entity.Dapper;
+using Vitorm.Sql;
+using Vitorm.Sql.SqlTranslate;
+
+namespace Vit.Extensions
+{
+    public static class DbContext_Extensions
+    {
+        public static SqlDbContext UseSqlite(this SqlDbContext dbContext, string ConnectionString)
+        {
+            ISqlTranslateService sqlTranslateService =   Vitorm.Sqlite.SqlTranslateService.Instance;
+
+            Func<IDbConnection> createDbConnection = () => new Microsoft.Data.Sqlite.SqliteConnection(ConnectionString);
+
+            Func<Type, IEntityDescriptor> getEntityDescriptor = (type) => EntityDescriptor.GetEntityDescriptor(type);
+
+
+            dbContext.Init(sqlTranslateService: sqlTranslateService, createDbConnection: createDbConnection, getEntityDescriptor: getEntityDescriptor);
+
+            dbContext.createTransactionScope = (dbContext) => new Vitorm.Sqlite.SqlTransactionScope(dbContext);
+
+            return dbContext;
+        }
+
+
+
+    }
+}

+ 75 - 0
src/Vitorm.Sqlite/SqlTransactionScope.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Data;
+
+using Vitorm.Sql;
+using Vitorm.Sql.Transaction;
+
+using SqlTransaction = Microsoft.Data.Sqlite.SqliteTransaction;
+
+namespace Vitorm.Sqlite
+{
+    public class SqlTransactionScope : Vitorm.Sql.Transaction.SqlTransactionScope
+    {
+        int savePointCount = 0;
+        public DbTransactionWrap CreateTransactionSavePoint(IDbTransaction originalTransaction)
+        {
+            var savePointName = "tran" + savePointCount++;
+            return new DbTransactionWrapSavePoint(originalTransaction, savePointName);
+        }
+        public SqlTransactionScope(SqlDbContext dbContext) : base(dbContext)
+        {
+        }
+
+        public override IDbTransaction BeginTransaction()
+        {
+            DbTransactionWrap transactionWrap;
+            IDbTransaction originalTransaction = GetCurrentTransaction();
+            if (originalTransaction == null)
+            {
+                var dbConnection = dbContext.dbConnection;
+                if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
+                originalTransaction = dbConnection.BeginTransaction();
+
+                transactionWrap = new DbTransactionWrap(originalTransaction);
+            }
+            else
+            {
+                transactionWrap = CreateTransactionSavePoint(originalTransaction);
+            }
+
+            transactions.Push(transactionWrap);
+            return transactionWrap;
+        }
+
+    }
+
+    public class DbTransactionWrapSavePoint : DbTransactionWrap
+    {
+        public SqlTransaction sqlTran => (SqlTransaction)originalTransaction;
+        string savePointName;
+        public DbTransactionWrapSavePoint(IDbTransaction transaction, string savePointName) : base(transaction)
+        {
+            this.savePointName = savePointName;
+            sqlTran.Save(savePointName);
+        }
+
+        public override void Commit()
+        {
+            sqlTran.Release(savePointName);
+            TransactionState = ETransactionState.Committed;
+        }
+
+        public override void Dispose()
+        {
+            if (TransactionState == ETransactionState.Active)
+                sqlTran.Rollback(savePointName);
+            TransactionState = ETransactionState.Disposed;
+        }
+
+        public override void Rollback()
+        {
+            sqlTran.Rollback(savePointName);
+            TransactionState = ETransactionState.RolledBack;
+        }
+    }
+}

+ 255 - 0
src/Vitorm.Sqlite/SqlTranslateService.cs

@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+using Vit.Extensions.Linq_Extensions;
+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
+{
+    public class SqlTranslateService : Vitorm.Sql.SqlTranslate.SqlTranslateService
+    {
+        public static readonly SqlTranslateService Instance = new SqlTranslateService();
+
+        protected QueryTranslateService queryTranslateService;
+        protected ExecuteUpdateTranslateService executeUpdateTranslateService;
+        protected ExecuteDeleteTranslateService executeDeleteTranslateService;
+
+        public SqlTranslateService()
+        {
+            queryTranslateService = new QueryTranslateService(this);
+            executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
+            executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
+        }
+
+        #region EvalExpression
+        /// <summary>
+        /// read where or value or on
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        /// <exception cref="NotSupportedException"></exception>
+        /// <param name="data"></param>
+        public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode data)
+        {
+            switch (data.nodeType)
+            {
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = data;
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 ToString
+                            case nameof(object.ToString):
+                                {
+                                    return $"cast({EvalExpression(arg, methodCall.@object)} as text)";
+                                }
+
+                            #region ##2 String method:  StartsWith EndsWith Contains
+                            case nameof(string.StartsWith): // String.StartsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like {EvalExpression(arg, value)}||'%'";
+                                }
+                            case nameof(string.EndsWith): // String.EndsWith
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like '%'||{EvalExpression(arg, value)}";
+                                }
+                            case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
+                                {
+                                    var str = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+                                    return $"{EvalExpression(arg, str)} like '%'||{EvalExpression(arg, value)}||'%'";
+                                }
+                            #endregion
+                        }
+                        break;
+                    }
+
+                #region Read Value
+                case NodeType.Convert:
+                    {
+                        // cast( 4.1 as signed)
+
+                        ExpressionNode_Convert convert = data;
+
+                        Type targetType = convert.valueType?.ToType();
+
+                        if (targetType == typeof(object)) return EvalExpression(arg, convert.body);
+
+                        // Nullable
+                        if (targetType.IsGenericType) targetType = targetType.GetGenericArguments()[0];
+
+                        string targetDbType = GetDbType(targetType);
+
+                        var sourceType = convert.body.Member_GetType();
+                        if (sourceType != null)
+                        {
+                            if (sourceType.IsGenericType) sourceType = sourceType.GetGenericArguments()[0];
+
+                            if (targetDbType == GetDbType(sourceType)) return EvalExpression(arg, convert.body);
+                        }
+
+                        if (targetDbType == "datetime")
+                        {
+                            return $"DATETIME({EvalExpression(arg, convert.body)})";
+                        }
+                        return $"cast({EvalExpression(arg, convert.body)} as {targetDbType})";
+                    }
+                case nameof(ExpressionType.Add):
+                    {
+                        ExpressionNode_Binary binary = data;
+
+                        // ##1 String Add
+                        if (data.valueType?.ToType() == typeof(string))
+                        {
+                            return $"{EvalExpression(arg, binary.left)} || {EvalExpression(arg, binary.right)}";
+                        }
+
+                        // ##2 Numberic Add
+                        return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
+                    }
+                case nameof(ExpressionType.Coalesce):
+                    {
+                        ExpressionNode_Binary binary = data;
+                        return $"COALESCE({EvalExpression(arg, binary.left)},{EvalExpression(arg, binary.right)})";
+                    }
+                    #endregion
+
+            }
+
+            return base.EvalExpression(arg, data);
+        }
+        #endregion
+
+
+
+        #region PrepareCreate
+        public override string PrepareCreate(IEntityDescriptor entityDescriptor)
+        {
+            /* //sql
+CREATE TABLE user (
+  id int NOT NULL PRIMARY KEY,
+  name varchar(100) DEFAULT NULL,
+  birth date DEFAULT NULL,
+  fatherId int DEFAULT NULL,
+  motherId int DEFAULT NULL
+) ;
+              */
+            List<string> sqlFields = new();
+
+            // #1 primary key
+            sqlFields.Add(GetColumnSql(entityDescriptor.key) + " PRIMARY KEY");
+
+            // #2 columns
+            entityDescriptor.columns?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
+
+            return $@"
+CREATE TABLE {DelimitIdentifier(entityDescriptor.tableName)} (
+{string.Join(",\r\n  ", sqlFields)}
+)";
+
+
+            string GetColumnSql(IColumnDescriptor column)
+            {
+                var dbType = column.databaseType ?? GetDbType(column.type);
+                // name varchar(100) DEFAULT NULL
+                return $"  {DelimitIdentifier(column.name)} {dbType} {(column.nullable ? "DEFAULT NULL" : "NOT NULL")}";
+            }
+        }
+        protected override string GetDbType(Type type)
+        {
+            type = TypeUtil.GetUnderlyingType(type);
+
+            if (type == typeof(DateTime))
+                return "datetime";
+
+            if (type == typeof(string))
+                return "text";
+
+            if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
+                return "real";
+
+            if (type == typeof(bool) || type.Name.ToLower().Contains("int")) return "integer";
+
+            throw new NotSupportedException("unsupported column type:" + type.Name);
+        }
+        #endregion
+
+
+        public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg)
+        {
+            /* //sql
+             insert into user(name,birth,fatherId,motherId) values('','','');
+             select seq from sqlite_sequence where name='user';
+              */
+            var entityDescriptor = arg.entityDescriptor;
+
+            var columns = entityDescriptor.allColumns;
+
+            // #1 GetSqlParams 
+            Func<object, Dictionary<string, object>> GetSqlParams = (entity) =>
+            {
+                var sqlParam = new Dictionary<string, object>();
+                foreach (var column in columns)
+                {
+                    var columnName = column.name;
+                    var value = column.GetValue(entity);
+
+                    sqlParam[columnName] = value;
+                }
+                return sqlParam;
+            };
+
+            #region #2 columns 
+            List<string> columnNames = new List<string>();
+            List<string> valueParams = new List<string>();
+            string columnName;
+
+            foreach (var column in columns)
+            {
+                columnName = column.name;
+
+                columnNames.Add(DelimitIdentifier(columnName));
+                valueParams.Add(GenerateParameterName(columnName));
+            }
+            #endregion
+
+            // #3 build sql
+            string sql = $@"insert into {DelimitIdentifier(entityDescriptor.tableName)}({string.Join(",", columnNames)}) values({string.Join(",", valueParams)});";
+            //sql+=$"select seq from sqlite_sequence where name = '{tableName}'; ";
+            sql += "select null;";
+            return (sql, GetSqlParams);
+        }
+
+        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);
+        }
+
+
+
+    }
+}

+ 56 - 0
src/Vitorm.Sqlite/TranslateService/ExecuteDeleteTranslateService.cs

@@ -0,0 +1,56 @@
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sqlite.TranslateService
+{
+    public class ExecuteDeleteTranslateService : BaseQueryTranslateService
+    {
+        /*
+WITH tmp AS (
+    select u.id 
+    from User u
+    left join User father on u.fatherId = father.id 
+    where u.id > 0
+)
+delete from User where id in ( SELECT id FROM tmp );
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            var sqlInner = base.BuildQuery(arg, stream);
+
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"delete from {sqlTranslator.DelimitIdentifier(tableName)} ";
+
+            sql += $"{NewLine}where {sqlTranslator.DelimitIdentifier(keyName)} in ( SELECT {sqlTranslator.DelimitIdentifier(keyName)} FROM tmp ); {NewLine}";
+
+            return sql;
+        }
+
+
+        public ExecuteDeleteTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+
+            // primary key
+            return $"{prefix} {sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}";
+        }
+
+
+
+    }
+}

+ 88 - 0
src/Vitorm.Sqlite/TranslateService/ExecuteUpdateTranslateService.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.Sqlite.TranslateService
+{
+    public class ExecuteUpdateTranslateService : BaseQueryTranslateService
+    {
+        /*
+
+-- multiple
+WITH tmp AS (
+    select   ('u' || u.id || '_' || COALESCE(father.id,'') ) as _name , u.id 
+    from User u
+    left join User father on u.fatherId = father.id 
+    where u.id > 0
+)
+UPDATE User  
+  SET name =  ( SELECT _name FROM tmp WHERE tmp.id =User.id )
+where id in ( SELECT id FROM tmp );
+
+
+--- single
+UPDATE User SET name = 'u'||id  where id > 0;
+         */
+        public override string BuildQuery(QueryTranslateArgument arg, CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(arg, stream);
+
+
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate?.memberArgs;
+
+            var NewLine = "\r\n";
+            var keyName = entityDescriptor.keyName;
+            var tableName = entityDescriptor.tableName;
+
+
+            var sql = $"WITH tmp AS ( {NewLine}";
+            sql += sqlInner;
+
+            sql += $"{NewLine}){NewLine}";
+            sql += $"UPDATE {sqlTranslator.DelimitIdentifier(tableName)}{NewLine}";
+            sql += $"Set ";
+
+            var sqlToUpdateCols = columnsToUpdate
+                .Select(m => m.name)
+                .Select(name => $"{NewLine}  {sqlTranslator.DelimitIdentifier(name)} = (SELECT {sqlTranslator.DelimitIdentifier("_" + name)} FROM tmp WHERE tmp.{sqlTranslator.DelimitIdentifier(keyName)} ={sqlTranslator.GetSqlField(tableName, keyName)} )");
+
+            sql += string.Join(",", sqlToUpdateCols);
+
+            sql += $"{NewLine}where {sqlTranslator.DelimitIdentifier(keyName)} in ( SELECT {sqlTranslator.DelimitIdentifier(keyName)} FROM tmp ); {NewLine}";
+
+            return sql;
+        }
+ 
+
+        public ExecuteUpdateTranslateService(SqlTranslateService sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(QueryTranslateArgument arg, CombinedStream stream, string prefix = "select")
+        {
+            var entityDescriptor = arg.dbContext.GetEntityDescriptor(arg.resultEntityType);
+            var columnsToUpdate = (stream as StreamToUpdate) ?.fieldsToUpdate?.memberArgs;
+
+            if (columnsToUpdate?.Any() != true) throw new ArgumentException("can not get columns to update");
+
+            var sqlFields = new List<string>();
+
+            foreach (var column in columnsToUpdate)
+            {
+                sqlFields.Add($"({sqlTranslator.EvalExpression( arg,  column.value)}) as {sqlTranslator.DelimitIdentifier("_" + column.name)}");
+            }
+
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}");
+
+            return prefix + " " + String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 27 - 0
src/Vitorm.Sqlite/Vitorm.Sqlite.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <pack>nuget</pack>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <Version>1.0.0-temp</Version>
+        <LangVersion>9.0</LangVersion>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Authors>Lith</Authors>
+        <Description>orm for Sqlite</Description>
+        <PackageProjectUrl>https://github.com/VitormLib/Vitorm.Sqlite</PackageProjectUrl>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Vitorm\Vitorm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 2 - 3
src/Vitorm/DbContext.cs

@@ -1,10 +1,9 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.Linq;
-using System.Transactions;
 
 using Vit.Linq.ExpressionTree;
+
 using Vitorm.Entity;
 
 namespace Vitorm
@@ -13,7 +12,7 @@ namespace Vitorm
     {
         public DbContext() { }
 
-        public virtual ExpressionConvertService convertService => ExpressionConvertService.Instance;
+        public virtual ExpressionConvertService convertService => Environment.convertService;
 
         public Func<Type, IDbSet> dbSetCreator { set; protected get; }
 

+ 19 - 0
src/Vitorm/DbFunction.cs

@@ -0,0 +1,19 @@
+using System;
+
+using Vit.Linq.ExpressionTree;
+
+namespace Vitorm
+{
+
+    [ValueType(EValueType.other)]
+    public static partial class DbFunction
+    {
+        public static Return Call<Return>(string functionName) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg0, object arg1) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg0, object arg1, object arg2) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg0, object arg1, object arg2, object arg3) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg0, object arg1, object arg2, object arg3, object arg4) => throw new NotImplementedException();
+        public static Return Call<Return>(string functionName, object arg0, object arg1, object arg2, object arg3, object arg4, object arg5) => throw new NotImplementedException();
+    }
+}

+ 31 - 7
src/Vitorm/Entity/ColumnDescriptor.cs

@@ -1,27 +1,51 @@
 using System;
+using System.ComponentModel.DataAnnotations.Schema;
 using System.Reflection;
 
 namespace Vitorm.Entity
 {
     public class ColumnDescriptor : IColumnDescriptor
     {
-        public ColumnDescriptor(PropertyInfo propertyInfo, bool isPrimaryKey)
+        public ColumnDescriptor(PropertyInfo propertyInfo, string name, bool isKey, DatabaseGeneratedOption? databaseGenerated, string databaseType, bool nullable)
         {
             this.propertyInfo = propertyInfo;
-            this.isPrimaryKey = isPrimaryKey;
+            type = propertyInfo.PropertyType;
+
+            this.name = name;
+            this.isKey = isKey;
+            this.databaseGenerated = databaseGenerated;
+            this.databaseType = databaseType;
+            this.nullable = nullable;
         }
 
         PropertyInfo propertyInfo;
-        public bool isPrimaryKey { get; private set; }
-        public string name => propertyInfo?.Name;
+        public Type type { get; private set; }
+
+
+        public string name { get; private set; }
+
+        public bool isKey { get; private set; }
+        /// <summary>
+        /// Specifies how the database generates values for a property.   None / Identity / Computed
+        /// </summary>
+        public DatabaseGeneratedOption? databaseGenerated { get; private set; }
+        /// <summary>
+        /// database provider specific data type of the column the property is mapped to.  example:  varchar(1000)
+        /// </summary>
+        public string databaseType { get; private set; }
+        /// <summary>
+        /// whether column could be null
+        /// </summary>
+        public bool nullable { get; private set; }
+
+
 
-        public Type type => propertyInfo?.PropertyType;
 
-        public void Set(object entity, object value)
+        public void SetValue(object entity, object value)
         {
             propertyInfo?.SetValue(entity, value);
         }
-        public object Get(object entity)
+        public object GetValue(object entity)
         {
             return propertyInfo?.GetValue(entity, null);
         }

+ 0 - 68
src/Vitorm/Entity/Dapper/EntityDescriptor.cs

@@ -1,68 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-
-namespace Vitorm.Entity.Dapper
-{
-    public class EntityDescriptor : IEntityDescriptor
-    {
-        static ConcurrentDictionary<Type, EntityDescriptor> descMap = new();
-        static EntityDescriptor New(Type entityType) => new EntityDescriptor(entityType);
-
-        public static string GetTableName(Type entityType) => entityType?.GetCustomAttribute<global::Dapper.Contrib.Extensions.TableAttribute>()?.Name;
-        public static EntityDescriptor GetEntityDescriptor(Type entityType)
-        {
-            if (GetTableName(entityType) == null) return null;
-
-            return descMap.GetOrAdd(entityType, New);
-        }
-
-        public static EntityDescriptor GetEntityDescriptor<Entity>()
-        {
-            return GetEntityDescriptor(typeof(Entity));
-        }
-
-        EntityDescriptor(Type entityType)
-        {
-            this.entityType = entityType;
-
-            tableName = GetTableName(entityType);
-
-            var entityProperties = entityType?.GetProperties(BindingFlags.Public | BindingFlags.Instance) ?? new PropertyInfo[0];
-
-            var keyProperty = entityProperties.FirstOrDefault(p => p.GetCustomAttribute<global::Dapper.Contrib.Extensions.KeyAttribute>() != null);
-            this.key = new ColumnDescriptor(keyProperty, true);
-
-            var properties = entityProperties.Where(p => p.GetCustomAttribute<global::Dapper.Contrib.Extensions.KeyAttribute>() == null);
-            this.columns = properties.Select(p => new ColumnDescriptor(p, false)).ToArray();
-
-            allColumns = new List<IColumnDescriptor> { key }.Concat(columns).ToArray();
-        }
-
-
-        public Type entityType { get; protected set; }
-        public string tableName { get; protected set; }
-
-        /// <summary>
-        /// primary key name
-        /// </summary>
-        public string keyName => key?.name;
-
-        /// <summary>
-        /// primary key
-        /// </summary>
-        public IColumnDescriptor key { get; protected set; }
-
-        /// <summary>
-        /// not include primary key
-        /// </summary>
-        public IColumnDescriptor[] columns { get; protected set; }
-
-
-        public IColumnDescriptor[] allColumns { get; protected set; }
-
-
-    }
-}

+ 60 - 0
src/Vitorm/Entity/DataAnnotations/EntityDescriptor.FromType.cs

@@ -0,0 +1,60 @@
+using System;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Reflection;
+
+namespace Vitorm.Entity.Dapper
+{
+    public partial class EntityDescriptor  
+    {
+        public static bool GetTableName(Type entityType, out string tableName, out string schema)
+        {
+            var attribute = entityType?.GetCustomAttribute<global::System.ComponentModel.DataAnnotations.Schema.TableAttribute>();
+            tableName = attribute?.Name;
+            schema = attribute?.Schema;
+            return attribute != null;
+        }
+        public static string GetTableName(Type entityType) => GetTableName(entityType, out var tableName, out _) ? tableName : null;
+
+
+        private static EntityDescriptor LoadFromType(Type entityType)
+        {
+            if (!GetTableName(entityType, out var tableName, out var schema)) return null;
+
+            IColumnDescriptor[] allColumns = entityType?.GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(propertyInfo =>
+             {
+                 if (propertyInfo.GetCustomAttribute<System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute>() != null) return null;
+
+                 // #1 isKey
+                 bool isKey = propertyInfo.GetCustomAttribute<System.ComponentModel.DataAnnotations.KeyAttribute>() != null;
+
+                 // #2 column name and type
+                 string name; string databaseType;
+                 var columnAttr = propertyInfo.GetCustomAttribute<System.ComponentModel.DataAnnotations.Schema.ColumnAttribute>();
+                 name = columnAttr?.Name ?? propertyInfo.Name;
+                 databaseType = columnAttr?.TypeName;
+
+                 // #3 databaseGenerated
+                 DatabaseGeneratedOption? databaseGenerated = propertyInfo.GetCustomAttribute<System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute>()?.DatabaseGeneratedOption;
+
+                 // #4 nullable
+                 bool nullable;
+                 if (propertyInfo.GetCustomAttribute<System.ComponentModel.DataAnnotations.RequiredAttribute>() != null) nullable = false;
+                 else
+                 {
+                     var type = propertyInfo.PropertyType;
+                     if (type == typeof(string)) nullable = true;
+                     else
+                     {
+                         nullable = (type.IsGenericType && typeof(Nullable<>) == type.GetGenericTypeDefinition());
+                     }
+                 }
+
+                 return new ColumnDescriptor(propertyInfo, name: name, isKey: isKey, databaseGenerated: databaseGenerated, databaseType: databaseType, nullable: nullable); 
+             }).Where(column => column != null).ToArray();
+
+            return new EntityDescriptor(entityType, allColumns, tableName, schema);
+        }
+
+    }
+}

+ 64 - 0
src/Vitorm/Entity/DataAnnotations/EntityDescriptor.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+
+namespace Vitorm.Entity.Dapper
+{
+    public partial class EntityDescriptor : IEntityDescriptor
+    {
+
+        static ConcurrentDictionary<Type, EntityDescriptor> descMap = new();
+
+
+        public static EntityDescriptor GetEntityDescriptor(Type entityType)
+        {
+            if (descMap.TryGetValue(entityType, out var entityDescriptor)) return entityDescriptor;
+
+            entityDescriptor = LoadFromType(entityType);
+            if (entityDescriptor != null) descMap[entityType] = entityDescriptor;
+
+            return entityDescriptor;
+        }
+
+        public static EntityDescriptor GetEntityDescriptor<Entity>()
+        {
+            return GetEntityDescriptor(typeof(Entity));
+        }
+
+        public EntityDescriptor(Type entityType, IColumnDescriptor[] allColumns, string tableName, string schema = null)
+        {
+            this.entityType = entityType;
+            this.tableName = tableName;
+            this.schema = schema;
+
+            this.allColumns = allColumns;
+            this.key = allColumns.FirstOrDefault(m => m.isKey);
+            this.columns = allColumns.Where(m => !m.isKey).ToArray();
+        }
+
+
+        public Type entityType { get; protected set; }
+        public string tableName { get; protected set; }
+        public string schema { get; protected set; }
+
+        /// <summary>
+        /// primary key name
+        /// </summary>
+        public string keyName => key?.name;
+
+        /// <summary>
+        /// primary key
+        /// </summary>
+        public IColumnDescriptor key { get; protected set; }
+
+        /// <summary>
+        /// not include primary key
+        /// </summary>
+        public IColumnDescriptor[] columns { get; protected set; }
+
+
+        public IColumnDescriptor[] allColumns { get; protected set; }
+
+
+    }
+}

+ 19 - 4
src/Vitorm/Entity/IColumnDescriptor.cs

@@ -1,13 +1,28 @@
 using System;
+using System.ComponentModel.DataAnnotations.Schema;
 
 namespace Vitorm.Entity
 {
     public interface IColumnDescriptor
     {
-        bool isPrimaryKey { get; }
-        string name { get; }
         Type type { get; }
-        void Set(object entity, object value);
-        object Get(object entity);
+        string name { get; }
+        bool isKey { get; }
+
+        /// <summary>
+        /// Specifies how the database generates values for a property.   None / Identity / Computed
+        /// </summary>
+        DatabaseGeneratedOption? databaseGenerated { get; }
+        /// <summary>
+        /// database provider specific data type of the column the property is mapped to.  example:  varchar(1000)
+        /// </summary>
+        string databaseType { get; }
+        /// <summary>
+        /// whether column could be null
+        /// </summary>
+        bool nullable { get; }
+
+        void SetValue(object entity, object value);
+        object GetValue(object entity);
     }
 }

+ 23 - 0
src/Vitorm/Environment.cs

@@ -0,0 +1,23 @@
+using Vit.Extensions.Vitorm_Extensions;
+using Vit.Linq.ExpressionTree;
+using Vit.Linq.ExpressionTree.ExpressionConvertor.MethodCalls;
+
+namespace Vitorm
+{
+    public class Environment
+    {
+        public static ExpressionConvertService convertService;
+        static Environment()
+        {
+            convertService = GetInitedConvertService();
+        }
+
+        public static ExpressionConvertService GetInitedConvertService()
+        {
+            var convertService = new ExpressionConvertService();
+            convertService.RegisterMethodConvertor(new MethodConvertor_ForType(typeof(DbFunction)));
+            convertService.RegisterMethodConvertor(new MethodConvertor_ForType(typeof(Orm_Extensions)));
+            return convertService;
+        }
+    }
+}

+ 121 - 0
src/Vitorm/Extensions/IDbConnection_Execute_Extensions.cs

@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Runtime.CompilerServices;
+
+
+namespace Vitorm.Extensions
+{
+    public static partial class IDbConnection_Execute_Extensions
+    {
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static int Execute(this IDbConnection conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+            // #1 setup command
+            using var cmd = conn.CreateCommand();
+            if (transaction != null) cmd.Transaction = transaction;
+            if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+            cmd.Connection = conn;
+            cmd.CommandText = sql;
+            AddParameter(cmd, param);
+
+
+            // #2 execute
+            bool wasClosed = conn.State == ConnectionState.Closed;
+            try
+            {
+                if (wasClosed) conn.Open();
+                return cmd.ExecuteNonQuery();
+            }
+            finally
+            {
+                if (wasClosed) conn.Close();
+            }
+
+        }
+
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static object ExecuteScalar(this IDbConnection conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+            // #1 setup command
+            using var cmd = conn.CreateCommand();
+            if (transaction != null) cmd.Transaction = transaction;
+            if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+            cmd.Connection = conn;
+            cmd.CommandText = sql;
+            AddParameter(cmd, param);
+
+            // #2 execute
+            bool wasClosed = conn.State == ConnectionState.Closed;
+            try
+            {
+                if (wasClosed) conn.Open();
+                return cmd.ExecuteScalar();
+            }
+            finally
+            {
+                if (wasClosed) conn.Close();
+            }
+        }
+
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static IDataReader ExecuteReader(this IDbConnection conn, string sql, IDictionary<string, object> param = null, IDbTransaction transaction = null, int? commandTimeout = null)
+        {
+
+            IDbCommand cmd = null;
+
+            bool wasClosed = conn.State == ConnectionState.Closed, disposeCommand = true;
+            try
+            {
+                // #1 setup command
+                cmd = conn.CreateCommand();
+                if (transaction != null) cmd.Transaction = transaction;
+                if (commandTimeout.HasValue) cmd.CommandTimeout = commandTimeout.Value;
+                cmd.Connection = conn;
+                cmd.CommandText = sql;
+                AddParameter(cmd, param);
+
+                // #2 execute
+                var commandBehavior = wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default;
+                if (wasClosed) conn.Open();
+
+                var reader = cmd.ExecuteReader(commandBehavior);
+                wasClosed = false; // don't dispose before giving it to them!
+                disposeCommand = false;
+                return reader;
+            }
+            finally
+            {
+                if (wasClosed) conn.Close();
+                if (disposeCommand)
+                {
+                    //cmd.Parameters.Clear();
+                    cmd.Dispose();
+                }
+            }
+
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        static void AddParameter(IDbCommand cmd, IDictionary<string, object> param)
+        {
+            if (param != null)
+            {
+                foreach (var entry in param)
+                {
+                    var p = cmd.CreateParameter();
+                    p.ParameterName = entry.Key;
+                    p.Value = entry.Value ?? DBNull.Value;
+                    cmd.Parameters.Add(p);
+                }
+            }
+        }
+
+    }
+}

+ 28 - 0
src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ExecuteDelete.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Vit.Extensions.Vitorm_Extensions
+{
+
+    public static partial class Orm_Extensions
+    {
+        /// <summary>
+        /// delete from first collection if joined multiple collections
+        /// </summary>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public static int ExecuteDelete(this IQueryable source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<int>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable, int>(ExecuteDelete).Method
+                    , source.Expression));
+        }
+    }
+}

+ 23 - 0
src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ExecuteUpdate.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Vit.Extensions.Vitorm_Extensions
+{
+
+    public static partial class Orm_Extensions
+    {
+        public static int ExecuteUpdate<Entity, EntityToUpdate>(this IQueryable<Entity> source, Expression<Func<Entity, EntityToUpdate>> update)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<int>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable<Entity>, Expression<Func<Entity, EntityToUpdate>>, int>(ExecuteUpdate).Method
+                    , source.Expression
+                    , update));
+        }
+    }
+}

+ 28 - 0
src/Vitorm/Extensions/Vitorm_Extensions/Orm_Extensions_ToExecuteString.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace Vit.Extensions.Vitorm_Extensions
+{
+
+    public static partial class Orm_Extensions
+    {
+        /// <summary>
+        /// if mysql or sqlserver or sqlite , will get sql string
+        /// </summary>
+        /// <param name="source"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public static string ToExecuteString(this IQueryable source)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return source.Provider.Execute<string>(
+                Expression.Call(
+                    null,
+                    new Func<IQueryable, string>(ToExecuteString).Method
+                    , source.Expression));
+        }
+    }
+}

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

@@ -20,11 +20,11 @@ namespace Vitorm.Sql.DataReader
                 var value = Read(reader);
                 if (value != null)
                 {
-                    column.Set(entity, value);
+                    column.SetValue(entity, value);
                     return true;
                 }
 
-                if (column.isPrimaryKey) return false;
+                if (column.isKey) return false;
                 return true;
             }
         }

+ 62 - 49
src/Vitorm/Sql/SqlDbContext.cs

@@ -1,18 +1,16 @@
-using Dapper;
-
-using System;
+using System;
 using System.Collections.Generic;
 using System.Data;
 using System.Linq;
 using System.Linq.Expressions;
-
-using Vit.Extensions.Linq_Extensions;
-using Vit.Linq.ExpressionTree.CollectionsQuery;
 using Vit.Linq.ExpressionTree.ComponentModel;
 using Vit.Linq;
 using Vitorm.Entity;
 using Vitorm.Sql.Transaction;
 using Vitorm.Sql.SqlTranslate;
+using Vitorm.StreamQuery;
+using Vit.Extensions.Vitorm_Extensions;
+using Vitorm.Extensions;
 
 namespace Vitorm.Sql
 {
@@ -85,14 +83,21 @@ namespace Vitorm.Sql
             var sqlParam = GetSqlParams(entity);
 
             // #3 execute
-            var newKeyValue = ExecuteScalar(sql: sql, param: (object)sqlParam);
-
-            if (newKeyValue != null)
+            if (entityDescriptor.key.databaseGenerated == System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)
             {
                 var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
+                var newKeyValue = ExecuteScalar(sql: sql, param: sqlParam);
                 newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
-                entityDescriptor.key.Set(entity, newKeyValue);
+                if (newKeyValue != null)
+                {
+                    entityDescriptor.key.SetValue(entity, newKeyValue);
+                }
+            }
+            else
+            {
+                Execute(sql: sql, param: sqlParam);
             }
+
             return entity;
         }
         public override void AddRange<Entity>(IEnumerable<Entity> entitys)
@@ -105,20 +110,33 @@ namespace Vitorm.Sql
             (string sql, Func<object, Dictionary<string, object>> GetSqlParams) = sqlTranslateService.PrepareAdd(arg);
 
             // #2 execute
-            var affectedRowCount = 0;
+            var affectedRowCount = 0; 
 
-            var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
-            foreach (var entity in entitys)
+            if (entityDescriptor.key.databaseGenerated == System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)
             {
-                var sqlParam = GetSqlParams(entity);
-                var newKeyValue = ExecuteScalar(sql: sql, param: (object)sqlParam);
-                if (newKeyValue != null)
+                var keyType = TypeUtil.GetUnderlyingType(entityDescriptor.key.type);
+                foreach (var entity in entitys)
                 {
+                    var sqlParam = GetSqlParams(entity);
+                    var newKeyValue = ExecuteScalar(sql: sql, param: sqlParam);
                     newKeyValue = TypeUtil.ConvertToUnderlyingType(newKeyValue, keyType);
-                    entityDescriptor.key.Set(entity, newKeyValue);
+                    if (newKeyValue != null)
+                    {
+                        entityDescriptor.key.SetValue(entity, newKeyValue);
+                    }
+                    affectedRowCount++;
                 }
-                affectedRowCount++;
             }
+            else
+            {
+                foreach (var entity in entitys)
+                {
+                    var sqlParam = GetSqlParams(entity);
+                    Execute(sql: sql, param: sqlParam);
+                    affectedRowCount++;
+                }
+            }
+
         }
 
         #endregion
@@ -142,13 +160,15 @@ namespace Vitorm.Sql
             sqlParam[entityDescriptor.keyName] = keyValue;
 
             // #3 execute
-            using var reader = ExecuteReader(sql: sql, param: (object)sqlParam);
+            using var reader = ExecuteReader(sql: sql, param: sqlParam);
             if (reader.Read())
             {
                 var entity = (Entity)Activator.CreateInstance(typeof(Entity));
                 foreach (var column in entityDescriptor.allColumns)
                 {
-                    column.Set(entity, TypeUtil.ConvertToType(reader[column.name], column.type));
+                    var value = TypeUtil.ConvertToType(reader[column.name], column.type);
+                    if (value != null)
+                        column.SetValue(entity, value);
                 }
                 return entity;
             }
@@ -189,7 +209,7 @@ namespace Vitorm.Sql
 
                     (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteUpdate(arg, streamToUpdate);
 
-                    return Execute(sql: sql, param: (object)sqlParam);
+                    return Execute(sql: sql, param: sqlParam);
                 }
 
 
@@ -201,7 +221,7 @@ namespace Vitorm.Sql
                 // #3.3.2 execute and read result
                 switch (combinedStream.method)
                 {
-                    case nameof(Queryable_Extensions.ToExecuteString):
+                    case nameof(Orm_Extensions.ToExecuteString):
                         {
                             // ToExecuteString
 
@@ -220,10 +240,10 @@ namespace Vitorm.Sql
 
                             (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
 
-                            var count = ExecuteScalar(sql: sql, param: (object)sqlParam);
+                            var count = ExecuteScalar(sql: sql, param: sqlParam);
                             return Convert.ToInt32(count);
                         }
-                    case nameof(Queryable_Extensions.ExecuteDelete):
+                    case nameof(Orm_Extensions.ExecuteDelete):
                         {
                             // ExecuteDelete
 
@@ -233,7 +253,7 @@ namespace Vitorm.Sql
 
                             (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareExecuteDelete(arg, combinedStream);
 
-                            var count = Execute(sql: sql, param: (object)sqlParam);
+                            var count = Execute(sql: sql, param: sqlParam);
                             return count;
                         }
                     case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
@@ -244,7 +264,7 @@ namespace Vitorm.Sql
 
                             (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
 
-                            using var reader = ExecuteReader(sql: sql, param: (object)sqlParam);
+                            using var reader = ExecuteReader(sql: sql, param: sqlParam);
                             return dataReader.ReadData(reader);
                         }
                     case "ToList":
@@ -259,7 +279,7 @@ namespace Vitorm.Sql
 
                             (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslateService.PrepareQuery(arg, combinedStream);
 
-                            using var reader = ExecuteReader(sql: sql, param: (object)sqlParam);
+                            using var reader = ExecuteReader(sql: sql, param: sqlParam);
                             return dataReader.ReadData(reader);
                         }
                 }
@@ -288,7 +308,7 @@ namespace Vitorm.Sql
             var sqlParam = GetSqlParams(entity);
 
             // #3 execute
-            var affectedRowCount = Execute(sql: sql, param: (object)sqlParam);
+            var affectedRowCount = Execute(sql: sql, param: sqlParam);
 
             return affectedRowCount;
 
@@ -309,7 +329,7 @@ namespace Vitorm.Sql
             foreach (var entity in entitys)
             {
                 var sqlParam = GetSqlParams(entity);
-                affectedRowCount += Execute(sql: sql, param: (object)sqlParam);
+                affectedRowCount += Execute(sql: sql, param: sqlParam);
             }
             return affectedRowCount;
         }
@@ -322,9 +342,8 @@ namespace Vitorm.Sql
         {
             // #0 get arg
             var entityDescriptor = GetEntityDescriptor(typeof(Entity));
-            SqlTranslateArgument arg = new SqlTranslateArgument(this, entityDescriptor);
 
-            var key = entityDescriptor.key.Get(entity);
+            var key = entityDescriptor.key.GetValue(entity);
             return DeleteByKey<Entity>(key);
         }
 
@@ -332,9 +351,8 @@ namespace Vitorm.Sql
         {
             // #0 get arg
             var entityDescriptor = GetEntityDescriptor(typeof(Entity));
-            SqlTranslateArgument arg = new SqlTranslateArgument(this, entityDescriptor);
 
-            var keys = entitys.Select(entity => entityDescriptor.key.Get(entity)).ToList();
+            var keys = entitys.Select(entity => entityDescriptor.key.GetValue(entity)).ToList();
             return DeleteByKeys<Entity, object>(keys);
         }
 
@@ -353,7 +371,7 @@ namespace Vitorm.Sql
             sqlParam[entityDescriptor.keyName] = keyValue;
 
             // #3 execute
-            var affectedRowCount = Execute(sql: sql, param: (object)sqlParam);
+            var affectedRowCount = Execute(sql: sql, param: sqlParam);
 
             return affectedRowCount;
 
@@ -365,15 +383,10 @@ namespace Vitorm.Sql
             SqlTranslateArgument arg = new SqlTranslateArgument(this, entityDescriptor);
 
             // #1 prepare sql
-            string sql = sqlTranslateService.PrepareDeleteRange(arg);
-
-            // #2 get sql params
-            var sqlParam = new Dictionary<string, object>();
-            sqlParam["keys"] = keys;
-
-            // #3 execute
-            var affectedRowCount = Execute(sql: sql, param: (object)sqlParam);
+            (string sql, Dictionary<string, object> sqlParam) = sqlTranslateService.PrepareDeleteByKeys(arg, keys);
 
+            // #2 execute
+            var affectedRowCount = Execute(sql: sql, param: sqlParam);
             return affectedRowCount;
         }
 
@@ -398,27 +411,27 @@ namespace Vitorm.Sql
 
         #region Execute
 
-        public int commandTimeout = 0;
+        public int? commandTimeout;
 
-        public virtual int Execute(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        public virtual int Execute(string sql, IDictionary<string, object> param = null, int? commandTimeout = null)
         {
             var transaction = GetCurrentTransaction();
             commandTimeout ??= this.commandTimeout;
-            return dbConnection.Execute(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+            return dbConnection.Execute(sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
         }
 
-        public virtual IDataReader ExecuteReader(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        public virtual IDataReader ExecuteReader(string sql, IDictionary<string, object> param = null, int? commandTimeout = null)
         {
             var transaction = GetCurrentTransaction();
             commandTimeout ??= this.commandTimeout;
-            return dbConnection.ExecuteReader(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+            return dbConnection.ExecuteReader(sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
         }
 
-        public virtual object ExecuteScalar(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        public virtual object ExecuteScalar(string sql, IDictionary<string, object> param = null, int? commandTimeout = null)
         {
             var transaction = GetCurrentTransaction();
             commandTimeout ??= this.commandTimeout;
-            return dbConnection.ExecuteScalar(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+            return dbConnection.ExecuteScalar(sql, param: param, transaction: transaction, commandTimeout: commandTimeout);
         }
         #endregion
 

+ 3 - 1
src/Vitorm/Sql/SqlTranslate/BaseQueryTranslateService.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+
 using Vit.Linq.ExpressionTree.ComponentModel;
-using Vit.Linq.ExpressionTree.CollectionsQuery;
+
 using Vitorm.Sql.DataReader;
+using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql.SqlTranslate
 {

+ 1 - 1
src/Vitorm/Sql/SqlTranslate/IQueryTranslateService.cs

@@ -1,4 +1,4 @@
-using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql.SqlTranslate
 {

+ 3 - 2
src/Vitorm/Sql/SqlTranslate/ISqlTranslateService.cs

@@ -2,8 +2,9 @@
 using System.Collections.Generic;
 
 using Vit.Linq.ExpressionTree.ComponentModel;
-using Vit.Linq.ExpressionTree.CollectionsQuery;
+
 using Vitorm.Entity;
+using Vitorm.StreamQuery;
 
 namespace Vitorm.Sql.SqlTranslate
 {
@@ -54,7 +55,7 @@ namespace Vitorm.Sql.SqlTranslate
         // #4 Delete: PrepareDelete PrepareDeleteRange PrepareExecuteDelete
         string PrepareDelete(SqlTranslateArgument arg);
 
-        string PrepareDeleteRange(SqlTranslateArgument arg);
+        (string sql, Dictionary<string, object> sqlParam) PrepareDeleteByKeys<Key>(SqlTranslateArgument arg, IEnumerable<Key> keys);
 
         (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg,CombinedStream combinedStream);
 

+ 1 - 1
src/Vitorm/Sql/SqlTranslate/QueryTranslateArgument.cs

@@ -25,6 +25,6 @@ namespace Vitorm.Sql.SqlTranslate
         public Dictionary<string, object> sqlParam { get; protected set; } = new Dictionary<string, object>();
 
         protected int paramIndex = 0;
-        public string NewParamName() => "param" + (paramIndex++);
+        public string NewParamName() => "p" + (paramIndex++);
     }
 }

+ 3 - 7
src/Vitorm/Sql/SqlTranslate/QueryTranslateService.cs

@@ -1,12 +1,8 @@
 using System;
-using System.Linq;
-using Vit.Linq.ExpressionTree.ComponentModel;
 using Vitorm.DataReader;
 using Vitorm.Sql.DataReader;
-using Vit.Linq.ExpressionTree.CollectionsQuery;
-using Vit.Extensions.Linq_Extensions;
-using System.IO;
-
+using Vitorm.StreamQuery;
+using Vit.Extensions.Vitorm_Extensions;
 
 namespace Vitorm.Sql.SqlTranslate
 {
@@ -39,7 +35,7 @@ namespace Vitorm.Sql.SqlTranslate
                         if (arg.dataReader == null) arg.dataReader = reader;
                         return prefix + " " + "count(*)";
                     }
-                case "" or null or "ToList" or nameof(Queryable_Extensions.ToExecuteString):
+                case "" or null or "ToList" or nameof(Orm_Extensions.ToExecuteString):
                     {
                         var reader = new EntityReader();
                         return prefix + " " + BuildReader(arg, stream, reader);

+ 60 - 17
src/Vitorm/Sql/SqlTranslate/SqlTranslateService.cs

@@ -2,11 +2,11 @@
 using System.Collections.Generic;
 
 using Vit.Linq.ExpressionTree.ComponentModel;
-using Vit.Linq.ExpressionTree.CollectionsQuery;
 using Vitorm.Entity;
 using System.Linq;
-using System.Linq.Expressions;
-using Vit.Linq.ExpressionTree.ExpressionConvertor;
+using Vitorm.StreamQuery;
+using System.Collections;
+using System.Text;
 
 namespace Vitorm.Sql.SqlTranslate
 {
@@ -210,11 +210,40 @@ namespace Vitorm.Sql.SqlTranslate
                     return GetSqlField(data, arg.dbContext);
 
                 case NodeType.Constant:
-                    ExpressionNode_Constant constant = data;
-                    var paramName = arg.NewParamName();
-                    arg.sqlParam[paramName] = constant.value;
-                    return "@" + paramName;
+                    {
+                        ExpressionNode_Constant constant = data;
+                        var value = constant.value;
+                        if (value is not string && value is IEnumerable enumerable)
+                        {
+                            StringBuilder sql = null;
+
+                            foreach (var item in enumerable)
+                            {
+                                if (sql == null)
+                                {
+                                    sql = new StringBuilder("(");
+                                    var paramName = arg.NewParamName();
+                                    arg.sqlParam[paramName] = item;
+                                    sql.Append(GenerateParameterName(paramName));
+                                }
+                                else
+                                {
+                                    var paramName = arg.NewParamName();
+                                    arg.sqlParam[paramName] = item;
+                                    sql.Append(",").Append(GenerateParameterName(paramName));
+                                }
+                            }
+                            if (sql == null) return "(null)";
+                            return sql.Append(")").ToString();
+                        }
+                        else
+                        {
+                            var paramName = arg.NewParamName();
+                            arg.sqlParam[paramName] = constant.value;
+                            return GenerateParameterName(paramName);
+                        }
 
+                    }
                     #endregion
             }
             throw new NotSupportedException("[QueryTranslator] not suported nodeType: " + data.nodeType);
@@ -255,7 +284,7 @@ namespace Vitorm.Sql.SqlTranslate
                 foreach (var column in columns)
                 {
                     var columnName = column.name;
-                    var value = column.Get(entity);
+                    var value = column.GetValue(entity);
 
                     sqlParam[columnName] = value;
                 }
@@ -318,7 +347,7 @@ namespace Vitorm.Sql.SqlTranslate
                 foreach (var column in entityDescriptor.allColumns)
                 {
                     var columnName = column.name;
-                    var value = column.Get(entity);
+                    var value = column.GetValue(entity);
 
                     sqlParam[columnName] = value;
                 }
@@ -361,21 +390,35 @@ namespace Vitorm.Sql.SqlTranslate
             return sql;
         }
 
-
-        public virtual string PrepareDeleteRange(SqlTranslateArgument arg)
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareDeleteByKeys<Key>(SqlTranslateArgument arg, IEnumerable<Key> keys)
         {
-            /* //sql
-            delete from user where id in ( 7 ) ;
-            */
+            //  delete from user where id in ( 7 ) ;
+
             var entityDescriptor = arg.entityDescriptor;
 
-            // #2 build sql
-            string sql = $@"delete from {DelimitIdentifier(entityDescriptor.tableName)} where {DelimitIdentifier(entityDescriptor.keyName)} in {GenerateParameterName("keys")};";
+            StringBuilder sql = new StringBuilder();
+            Dictionary<string, object> sqlParam = new();
 
-            return sql;
+            sql.Append("delete from ").Append(DelimitIdentifier(entityDescriptor.tableName)).Append(" where ").Append(DelimitIdentifier(entityDescriptor.keyName)).Append(" in (");
+
+            int keyIndex = 0;
+            foreach (var key in keys)
+            {
+                var paramName = "p" + (keyIndex++);
+                sql.Append(GenerateParameterName(paramName)).Append(",");
+                sqlParam[paramName] = key;
+            }
+            if (keyIndex == 0) sql.Append("null);");
+            else
+            {
+                sql.Length--;
+                sql.Append(");");
+            }
+            return (sql.ToString(), sqlParam);
         }
 
 
+
         public abstract (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream);
 
 

+ 1 - 1
src/Vitorm/Sql/TypeUtil.cs

@@ -39,7 +39,7 @@ namespace Vitorm.Sql
 
         public static object ConvertToUnderlyingType(object value, Type underlyingType)
         {
-            if (value == null || value == DBNull.Value) return null;
+            if (value == null || value is DBNull) return null;
 
             if (!underlyingType.IsInstanceOfType(value))
                 value = Convert.ChangeType(value, underlyingType);

+ 97 - 0
src/Vitorm/StreamQuery/CombinedStream.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    /* //sql
+    select u.id, u.name, u.birth,u.fatherId ,u.motherId,    father.name,  mother.name
+    from `User` u
+    inner join `User` father on u.fatherId = father.id 
+    left join `User` mother on u.motherId = mother.id
+    where u.id>1
+    limit 1,5;
+     */
+
+    /* //linq
+value(Vit.Linq.Converter.OrderedQueryable`1[Vit.Linq.MsTest.Converter.Join_Test+User])
+.SelectMany(
+     user => value(Vit.Linq.MsTest.Converter.Join_Test+<>c__DisplayClass0_1).users
+             .Where(father => (Convert(father.id, Nullable`1) == user.fatherId)).DefaultIfEmpty(),
+     (user, father) => new <>f__AnonymousType4`2(user = user, father = father)
+ ).SelectMany(
+     item => value(Vit.Linq.MsTest.Converter.Join_Test+<>c__DisplayClass0_1).users
+                 .Where(mother => (Convert(mother.id, Nullable`1) == item.user.fatherId)).DefaultIfEmpty(),
+     (item, mother) => new <>f__AnonymousType5`3(user = item.user, father = item.father, mother = mother)
+ )
+.Skip().Take().Select()
+     */
+
+
+
+    public class CombinedStream : IStream
+    {
+
+        public CombinedStream(string alias)
+        {
+            this.alias = alias;
+        }
+
+        /// <summary>
+        /// default is ToList, could be :  Count | First | FirstOrDefault | Last | LastOrDefault | TotalCount
+        /// </summary>
+        public string method { get; set; }
+
+
+        public string alias { get; protected set; }
+
+        // ExpressionNode_New   new { c = a , d = b }
+        public SelectedFields select { get; set; }
+        public bool? distinct;
+
+
+        public IStream source;
+
+
+        public List<StreamToJoin> joins { get; set; }
+        public ExpressionNode GetSelectedFields(Type entityType)
+        {
+            var parameterValue = select?.fields as ExpressionNode;
+            if (parameterValue == null && joins?.Any() != true)
+            {
+                parameterValue = ExpressionNode_RenameableMember.Member(stream: source, entityType);
+            }
+            return parameterValue;
+        }
+
+
+
+        //  a1.id==b2.id
+        public ExpressionNode where { get; set; }
+
+
+
+        // ExpressionNode_New     new {  fatherId = left.fatherId, motherId = left.motherId }
+        // ExpressionNode_Member  left.fatherId
+        public ExpressionNode groupByFields;
+
+        //  left.fatherId >2 && left.Count()>2 && left.Max(m=>m.id) >2
+        public ExpressionNode having { get; set; }
+
+
+
+
+        //  a1.id, b2.id
+        public List<OrderField> orders { get; set; }
+
+
+        public int? skip { get; set; }
+        public int? take { get; set; }
+
+
+        public bool isJoinedStream => joins?.Any() == true;
+        public bool isGroupedStream => groupByFields != null;
+    }
+}

+ 7 - 0
src/Vitorm/StreamQuery/EJoinType.cs

@@ -0,0 +1,7 @@
+namespace Vitorm.StreamQuery
+{
+    public enum EJoinType
+    {
+        LeftJoin, InnerJoin
+    }
+}

+ 7 - 0
src/Vitorm/StreamQuery/IStream.cs

@@ -0,0 +1,7 @@
+namespace Vitorm.StreamQuery
+{
+    public interface IStream
+    {
+        string alias { get; }
+    }
+}

+ 29 - 0
src/Vitorm/StreamQuery/SelectedFields.cs

@@ -0,0 +1,29 @@
+using System.Linq;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public class SelectedFields
+    {
+        // root value of ExpressionNode_Member is IStream
+        public ExpressionNode_New fields;
+
+        public bool? isDefaultSelect { get; set; }
+        internal bool TryGetField(string fieldName, out ExpressionNode field)
+        {
+            field = null;
+
+            var fieldInfo = fields?.memberArgs?.FirstOrDefault(m => m.name == fieldName);
+
+            fieldInfo ??= fields?.constructorArgs?.FirstOrDefault(m => m.name == fieldName);
+
+            if (fieldInfo != null)
+            {
+                field = fieldInfo.value;
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 24 - 0
src/Vitorm/StreamQuery/SourceStream.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace Vitorm.StreamQuery
+{
+    public class SourceStream : IStream
+    {
+        public SourceStream(object source, string alias)
+        {
+            this.source = source;
+            this.alias = alias;
+        }
+        public string alias { get; private set; }
+        private object source;
+
+        public int? hashCode
+        {
+            get => source?.GetHashCode();
+        }
+
+        public object GetSource() => source;
+
+        public Type GetEntityType() => source?.GetType().GenericTypeArguments[0];
+    }
+}

+ 81 - 0
src/Vitorm/StreamQuery/StreamReader.DeepClone.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public partial class StreamReader
+    {
+        public static ExpressionNode DeepClone(ExpressionNode node, Func<ExpressionNode_Member, ExpressionNode> GetParameter = null)
+        {
+            var cloner = new ExpressionNodeCloner();
+            cloner.clone = (node) =>
+            {
+                if (node?.nodeType == NodeType.Member)
+                {
+                    ExpressionNode_Member member = node;
+
+                    ExpressionNode valueNode = null;
+
+                    if (!string.IsNullOrWhiteSpace(member.parameterName))
+                    {
+                        // {"nodeType":"Member", "parameterName":"a0", "memberName":"id"}
+                        valueNode = GetParameter?.Invoke(member);
+                    }
+                    else if (member.objectValue?.nodeType == NodeType.Member)
+                    {
+                        // reduce level:  {"nodeType":"Member","objectValue":{"parameterName":"a0","nodeType":"Member"},"memberName":"id"}
+                        var objectNode = cloner.Clone(member.objectValue);
+                        valueNode = GetMember(objectNode, member.memberName, sourceMember: member);
+                    }
+
+                    valueNode = Reduce(valueNode);
+
+                    if (valueNode != null) return (true, valueNode);
+                }
+                return default;
+            };
+
+            return cloner.Clone(node);
+
+            ExpressionNode Reduce(ExpressionNode node)
+            {
+                if (node?.nodeType == NodeType.Member && node.objectValue != null)
+                {
+                    if (node.memberName == null)
+                    {
+                        return Reduce(node.objectValue);
+                    }
+                    else
+                    {
+                        if (node.objectValue.nodeType == NodeType.New)
+                        {
+                            return Reduce(GetMemberFromNewNode(node.objectValue, node.memberName));
+                        }
+                    }
+                }
+                return node;
+            }
+            ExpressionNode GetMember(ExpressionNode node, string memberName, ExpressionNode_Member sourceMember)
+            {
+                if (node == null || string.IsNullOrEmpty(memberName)) return node;
+
+                if (node.nodeType == NodeType.New)
+                {
+                    return GetMemberFromNewNode(node, memberName);
+                }
+                return ExpressionNode.Member(objectValue: node, memberName: memberName).Member_SetType(sourceMember.Member_GetType());
+            }
+
+            ExpressionNode GetMemberFromNewNode(ExpressionNode_New newNode, string memberName)
+            {
+                return newNode.constructorArgs?.FirstOrDefault(bind => bind.name == memberName)?.value ?? newNode.memberArgs?.FirstOrDefault(bind => bind.name == memberName)?.value;
+            }
+        }
+
+
+    }
+}

+ 61 - 0
src/Vitorm/StreamQuery/StreamReader.GroupBy.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public partial class StreamReader
+    {
+        CombinedStream GroupBy(Argument arg, IStream source, ExpressionNode_Lambda resultSelector)
+        {
+            switch (source)
+            {
+                case SourceStream sourceStream:
+                case CombinedStream groupedStream when groupedStream.isGroupedStream:
+                    {
+                        var parameterName = resultSelector.parameterNames[0];
+                        var parameterValue = ExpressionNode_RenameableMember.Member(stream: source, resultSelector.Lambda_GetParamTypes()[0]);
+                        var newArg = arg.WithParameter(parameterName, parameterValue);
+                        var groupByFields = ReadFields(newArg, resultSelector);
+
+                        return new CombinedStream(NewAliasName()) { source = source, groupByFields = groupByFields };
+                    }
+                case CombinedStream combinedStream:
+                    {
+                        if (combinedStream.select?.isDefaultSelect == true && combinedStream.joins == null
+                            && combinedStream.groupByFields == null && combinedStream.having == null
+                            && combinedStream.orders == null
+                            && combinedStream.skip == null && combinedStream.take == null
+                           )
+                        {
+                            // nested GroupedStream
+
+                            var parameterName = resultSelector.parameterNames[0];
+                            var parameterValue = ExpressionNode_RenameableMember.Member(stream: combinedStream.source, resultSelector.Lambda_GetParamTypes()[0]);
+
+                            var newArg = arg.WithParameter(parameterName, parameterValue);
+                            var groupByFields = ReadFields(newArg, resultSelector);
+
+                            combinedStream.groupByFields = groupByFields;
+                            return combinedStream;
+                        }
+                        else
+                        {
+                            var parameterName = resultSelector.parameterNames[0];
+                            var parameterValue = (ExpressionNode)combinedStream.select.fields;
+
+                            var newArg = arg.WithParameter(parameterName, parameterValue);
+                            var groupByFields = ReadFields(newArg, resultSelector);
+
+                            return new CombinedStream(NewAliasName()) { source = source, groupByFields = groupByFields };
+                        }
+                    }
+            }
+
+            throw new NotSupportedException($"[StreamReader] do not support StreamType");
+        }
+
+    }
+}

+ 131 - 0
src/Vitorm/StreamQuery/StreamReader.Join.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public partial class StreamReader
+    {
+        // InnerJoin (Queryable.Join)
+        //   userQuery.Join(
+        //       userQuery
+        //       , user => user.fatherId
+        //       , father => father.id
+        //       , (user, father) => new { user, father }
+        //   );
+
+        CombinedStream Join(Argument arg, IStream source, IStream rightStream, ExpressionNode_Lambda leftKeySelector, ExpressionNode_Lambda rightKeySelector, ExpressionNode_Lambda resultSelector)
+        {
+
+            CombinedStream finalStream;
+            ExpressionNode parameterValueForLeftStream;
+
+            #region #1 get finalStream and parameterValueForLeftStream
+            switch (source)
+            {
+                case SourceStream sourceStream:
+                    {
+                        finalStream = new CombinedStream(NewAliasName()) { source = source };
+                        parameterValueForLeftStream = ExpressionNode_RenameableMember.Member(stream: sourceStream, leftKeySelector.Lambda_GetParamTypes()[0]);
+                        break;
+                    }
+                case CombinedStream combinedStream:
+                    {
+                        if (combinedStream.where == null && combinedStream.orders == null
+                            && combinedStream.skip == null && combinedStream.take == null
+                            && combinedStream.select?.isDefaultSelect == true)
+                        {
+                            // merge multiple join
+                            finalStream = combinedStream;
+                            parameterValueForLeftStream = combinedStream.select.fields as ExpressionNode;
+                            break;
+                        }
+                        throw new NotSupportedException($"[StreamReader] not support inner select in join sentence");
+                    }
+                default: throw new NotSupportedException($"[StreamReader] not supported StreamType : " + source?.GetType().Name);
+            }
+            #endregion
+
+
+            // #2 read leftKey and rightKey
+            ExpressionNode leftKeyFields, rightKeyFields;
+            {
+                var parameterName = leftKeySelector.parameterNames[0];
+                var parameterValue = parameterValueForLeftStream;
+                var newArg = arg.WithParameter(parameterName, parameterValue);
+                leftKeyFields = ReadFields(newArg, leftKeySelector);
+            }
+            {
+                var parameterName = rightKeySelector.parameterNames[0];
+                var parameterValue = ExpressionNode_RenameableMember.Member(stream: rightStream, rightKeySelector.Lambda_GetParamTypes()[0]);
+                var newArg = arg.WithParameter(parameterName, parameterValue);
+                rightKeyFields = ReadFields(newArg, rightKeySelector);
+            }
+
+
+
+            StreamToJoin rightStreamToJoin;
+            #region #3 read rightStreamToJoin
+            {
+                // read on
+                ExpressionNode on = null;
+                if (leftKeyFields.nodeType == NodeType.New)
+                {
+                    // ##1 key is multiple fields
+
+                    var leftKeys = leftKeyFields.constructorArgs;
+                    var rightKeys = rightKeyFields.constructorArgs;
+
+                    leftKeys.ForEach(leftKey =>
+                    {
+                        var rightKey = rightKeys.First(key => key.name == leftKey.name);
+                        var curWhere = ExpressionNode.Binary(NodeType.Equal, leftKey.value, rightKey.value);
+
+                        if (on == null) on = curWhere;
+                        else on = ExpressionNode.Binary(NodeType.And, on, curWhere);
+                    });
+                }
+                else
+                {
+                    // ##2 key is single field
+                    on = ExpressionNode.Binary(NodeType.Equal, leftKeyFields, rightKeyFields);
+                }
+
+                rightStreamToJoin = new StreamToJoin { joinType = EJoinType.InnerJoin, right = rightStream, on = on };
+            }
+            #endregion
+
+
+            // #4 read SelectedFields
+            SelectedFields select;
+            {
+                // left
+                var parameterName = resultSelector.parameterNames[0];
+                var parameterValue = parameterValueForLeftStream;
+                var argForSelect = arg.WithParameter(parameterName, parameterValue);
+
+                // right
+                parameterName = resultSelector.parameterNames[1];
+                parameterValue = ExpressionNode_RenameableMember.Member(stream: rightStreamToJoin.right, resultSelector.Lambda_GetParamTypes()[1]);
+                argForSelect = argForSelect.SetParameter(parameterName, parameterValue);
+
+                select = ReadFieldSelect(argForSelect, resultSelector);
+            }
+
+
+            // #4 combine stream
+            finalStream.joins ??= new List<StreamToJoin>();
+            finalStream.joins.Add(rightStreamToJoin);
+            finalStream.select = select;
+
+            return finalStream;
+
+        }
+
+
+
+    }
+}

+ 163 - 0
src/Vitorm/StreamQuery/StreamReader.SelectMany.cs

@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public partial class StreamReader
+    {
+        // Join with  Queryable.SelectMany
+        // users.SelectMany(
+        //      user => users.Where(father => (father.id == user.fatherId)).DefaultIfEmpty(),
+        //      (user, father) => new <>f__AnonymousType4`2(user = user, father = father)
+        //  )
+
+        CombinedStream SelectMany(Argument arg, IStream source, ExpressionNode_Lambda rightSelector, ExpressionNode_Lambda resultSelector)
+        {
+            CombinedStream finalStream;
+            ExpressionNode parameterValueForLeftStream;
+
+            #region #1 get finalStream and parameterValueForLeftStream
+            switch (source)
+            {
+                case SourceStream sourceStream:
+                    {
+                        finalStream = new CombinedStream(NewAliasName()) { source = source };
+                        parameterValueForLeftStream = ExpressionNode_RenameableMember.Member(stream: sourceStream, rightSelector.Lambda_GetParamTypes()[0]);
+                        break;
+                    }
+                case CombinedStream combinedStream:
+                    {
+                        if (combinedStream.where == null && combinedStream.orders == null
+                            && combinedStream.skip == null && combinedStream.take == null
+                            && combinedStream.select?.isDefaultSelect == true)
+                        {
+                            // merge multiple join
+                            finalStream = combinedStream;
+                            parameterValueForLeftStream = combinedStream.select.fields as ExpressionNode;
+                            break;
+                        }
+                        throw new NotSupportedException($"[StreamReader] not support inner select in join sentence");
+                    }
+                default: throw new NotSupportedException($"[StreamReader] not supported StreamType : " + source?.GetType().Name);
+            }
+            #endregion
+
+
+
+            StreamToJoin rightStreamToJoin;
+            #region #2 read rightStreamToJoin
+            {
+                // rightSelector:
+                //      user => users.Where(father => (father.id == user.fatherId)).DefaultIfEmpty()
+
+                var parameterName = rightSelector.parameterNames[0];
+                var parameterValue = parameterValueForLeftStream;
+                var argForRightStream = arg.WithParameter(parameterName, parameterValue);
+
+
+                rightStreamToJoin = new StreamToJoin();
+                rightStreamToJoin.joinType = EJoinType.InnerJoin;
+
+                ReadNode(argForRightStream, rightSelector.body);
+
+                void ReadNode(Argument argForRightStream, ExpressionNode node)
+                {
+                    switch (node.nodeType)
+                    {
+                        case NodeType.Member:
+                            {
+                                // to get rightStream (row.fathers) in line 08
+
+                                /* // LeftJoin (Queryable.GroupJoin)
+                                01  userQuery.GroupJoin(
+                                02      userQuery
+                                03      , user => user.fatherId
+                                04      , father => father.id
+                                05      , (user, fathers) => new { user, fathers }
+                                06  )
+                                07  .SelectMany(
+                                08      row => row.fathers.DefaultIfEmpty()
+                                09      , (row, father) => new { row, father }
+                                10  )
+                                11  .Where(row2 => row2.row.user.id > 2)
+                                12  .Select(row2 => new { row2.row.user, row2.father }); 
+                                 */
+
+                                var rightStream = argForRightStream.DeepClone(node);
+                                var rightStreamFromJoin = finalStream.joins.Last();
+                                if (rightStream.parameterName != rightStreamFromJoin.right.alias)
+                                {
+                                    throw new NotSupportedException("[StreamReader] unexpected expression sentence of GroupJoin");
+                                }
+                                finalStream.joins.Remove(rightStreamFromJoin);
+                                rightStreamToJoin = rightStreamFromJoin;
+                                return;
+                            }
+                        case NodeType.MethodCall:
+                            {
+                                ExpressionNode_MethodCall call = node;
+                                switch (call.methodName)
+                                {
+                                    case "Where":
+                                        {
+                                            if (rightStreamToJoin.on != null)
+                                                throw new Exception("[StreamReader] unexpected multiple where in join");
+
+                                            var source = ReadStream(argForRightStream, call.arguments[0]);
+                                            var predicateLambda = call.arguments[1] as ExpressionNode_Lambda;
+
+                                            rightStreamToJoin.right = source;
+                                            rightStreamToJoin.on = ReadWhere(argForRightStream, source, predicateLambda);
+
+                                            return;
+                                        }
+                                    case "DefaultIfEmpty":
+                                        {
+                                            var source = call.arguments[0];
+                                            ReadNode(argForRightStream, source);
+                                            rightStreamToJoin.joinType = EJoinType.LeftJoin;
+                                            return;
+                                        }
+                                }
+                                throw new Exception("[StreamReader] unexpected method call : " + call.methodName);
+                            }
+                    }
+                    throw new NotSupportedException($"[StreamReader] unexpected expression nodeType : {node.nodeType}");
+                }
+            }
+            #endregion
+
+
+            // #3 read SelectedFields
+            SelectedFields select;
+            {
+                // left
+                var parameterName = resultSelector.parameterNames[0];
+                var parameterValue = parameterValueForLeftStream;
+                var argForSelect = arg.WithParameter(parameterName, parameterValue);
+
+                // right
+                parameterName = resultSelector.parameterNames[1];
+                parameterValue = ExpressionNode_RenameableMember.Member(stream: rightStreamToJoin.right, resultSelector.Lambda_GetParamTypes()[1]);
+                argForSelect = argForSelect.SetParameter(parameterName, parameterValue);
+
+                select = ReadFieldSelect(argForSelect, resultSelector);
+            }
+
+            // #4 combine stream
+            finalStream.joins ??= new List<StreamToJoin>();
+            finalStream.joins.Add(rightStreamToJoin);
+            finalStream.select = select;
+
+            return finalStream;
+
+        }
+
+
+
+    }
+}

+ 477 - 0
src/Vitorm/StreamQuery/StreamReader.cs

@@ -0,0 +1,477 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Extensions.Vitorm_Extensions;
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+
+    public class ExpressionNode_RenameableMember : ExpressionNode
+    {
+        private IStream stream;
+        public override string parameterName
+        {
+            get => stream?.alias;
+            set => throw new NotSupportedException();
+        }
+        public static ExpressionNode Member(IStream stream, Type memberType)
+        {
+            var node = new ExpressionNode_RenameableMember
+            {
+                nodeType = NodeType.Member,
+                stream = stream
+            };
+            node.Member_SetType(memberType);
+            return node;
+        }
+    }
+
+
+
+    public class Argument
+    {
+        Dictionary<string, ExpressionNode> parameterMap { get; set; }
+
+        public virtual ExpressionNode GetParameter(ExpressionNode_Member member)
+        {
+            if (member.nodeType == NodeType.Member && !string.IsNullOrWhiteSpace(member.parameterName))
+            {
+                if (parameterMap?.TryGetValue(member.parameterName, out var parameterValue) == true)
+                {
+                    if (string.IsNullOrWhiteSpace(member.memberName))
+                    {
+                        return parameterValue;
+                    }
+                    else
+                    {
+                        return ExpressionNode.Member(objectValue: parameterValue, memberName: member.memberName).Member_SetType(member.Member_GetType());
+                    }
+                }
+            }
+            return null;
+        }
+
+
+        public Argument SetParameter(string parameterName, ExpressionNode parameterValue)
+        {
+            parameterMap ??= new();
+            parameterMap[parameterName] = parameterValue;
+            return this;
+        }
+
+
+        public Argument WithParameter(string parameterName, ExpressionNode parameterValue)
+        {
+            var arg = new Argument();
+
+            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
+            arg.parameterMap[parameterName] = parameterValue;
+            return arg;
+        }
+
+        #region SupportNoChildParameter
+        public Argument WithParameter(string parameterName, ExpressionNode parameterValue, ExpressionNode noChildParameterValue)
+        {
+            var arg = new Argument_SupportNoChildParameter { noChildParameterName = parameterName, noChildParameterValue = noChildParameterValue };
+
+            arg.parameterMap = parameterMap?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new();
+            arg.parameterMap[parameterName] = parameterValue;
+            return arg;
+        }
+
+        class Argument_SupportNoChildParameter : Argument
+        {
+            public string noChildParameterName;
+            public ExpressionNode noChildParameterValue;
+            public override ExpressionNode GetParameter(ExpressionNode_Member member)
+            {
+                if (member.nodeType == NodeType.Member && member.parameterName == noChildParameterName && member.memberName == null)
+                {
+                    return noChildParameterValue;
+                }
+                return base.GetParameter(member);
+            }
+
+        }
+        #endregion
+
+
+        public ExpressionNode DeepClone(ExpressionNode node)
+        {
+            Func<ExpressionNode_Member, ExpressionNode> GetParameter = this.GetParameter;
+
+            return StreamReader.DeepClone(node, GetParameter);
+        }
+   
+    }
+
+    public partial class StreamReader
+    {
+
+        /// <summary>
+        /// lambda:
+        ///     (query,query2) => query.SelectMany(query2).Where().OrderBy().Skip().Take().Select().ToList();
+        /// </summary>
+        /// <param name="lambda"> </param>
+        /// <returns> </returns>
+        public static IStream ReadNode(ExpressionNode_Lambda lambda)
+        {
+            return new StreamReader().ReadFromNode(lambda);
+        }
+
+
+        /// <summary>
+        /// lambda:
+        ///     (query,query2) => query.SelectMany(query2).Where().OrderBy().Skip().Take().Select().ToList();
+        /// </summary>
+        /// <param name="lambda"> </param>
+        /// <returns> </returns>
+        public IStream ReadFromNode(ExpressionNode_Lambda lambda)
+        {
+            var arg = new Argument();
+            return ReadStream(arg, lambda.body);
+        }
+        int aliasNameCount = 0;
+        string NewAliasName()
+        {
+            return "t" + (aliasNameCount++);
+        }
+
+        CombinedStream ReadStreamWithWhere(Argument arg, IStream source, ExpressionNode_Lambda predicateLambda)
+        {
+            switch (source)
+            {
+                case SourceStream sourceStream:
+                    {
+                        ExpressionNode where = ReadWhere(arg, source, predicateLambda);
+
+                        var combinedStream = ToCombinedStream(sourceStream);
+                        combinedStream.where = where;
+
+                        return combinedStream;
+                    }
+                case CombinedStream groupedStream when groupedStream.isGroupedStream:
+                    {
+                        var parameterName = predicateLambda.parameterNames[0];
+                        var groupByFields = groupedStream.groupByFields;
+                        var memberBind = new MemberBind { name = "Key", value = groupByFields };
+                        var parameterValue = ExpressionNode.New(constructorArgs: new() { memberBind });
+                        var newArg = arg.WithParameter(parameterName, parameterValue);
+
+                        ExpressionNode having = ReadWhere(newArg, predicateLambda.body);
+                        if (groupedStream.having == null)
+                        {
+                            groupedStream.having = having;
+                        }
+                        else
+                        {
+                            groupedStream.having = ExpressionNode.And(groupedStream.having, having);
+                        }
+                        return groupedStream;
+                    }
+                case CombinedStream combinedStream:
+                    {
+                        var parameterName = predicateLambda.parameterNames[0];
+                        //var parameterValue = (ExpressionNode)combinedStream.select.fields;
+                        var entityType = predicateLambda.Lambda_GetParamTypes()[0];
+                        var parameterValue = combinedStream.GetSelectedFields(entityType);
+
+                        var newArg = arg.WithParameter(parameterName, parameterValue);
+
+                        ExpressionNode where = ReadWhere(newArg, predicateLambda.body);
+                        if (combinedStream.where == null)
+                        {
+                            combinedStream.where = where;
+                        }
+                        else
+                        {
+                            combinedStream.where = ExpressionNode.And(combinedStream.where, where);
+                        }
+                        return combinedStream;
+                    }
+            }
+            return default;
+        }
+        CombinedStream ToCombinedStream(SourceStream source)
+        {
+            Type entityType = source.GetEntityType();
+            var selectedFields = ExpressionNode.Member(parameterName: source.alias, memberName: null).Member_SetType(entityType);
+            var select = new SelectedFields { fields = selectedFields, isDefaultSelect = true };
+
+            return new CombinedStream(NewAliasName()) { source = source, select = select };
+        }
+        CombinedStream AsCombinedStream(IStream source)
+        {
+            if (source is CombinedStream combinedStream) return combinedStream;
+            if (source is SourceStream sourceStream) return ToCombinedStream(sourceStream);
+            return null;
+        }
+
+
+        // query.SelectMany(query2).Where().Where().OrderBy().Skip().Take().Select()
+        IStream ReadStream(Argument arg, ExpressionNode node)
+        {
+            switch (node.nodeType)
+            {
+                case NodeType.Member:
+                    {
+                        ExpressionNode_Member member = node;
+                        var oriValue = member.Member_GetOriValue();
+                        if (oriValue != null)
+                            return new SourceStream(oriValue, NewAliasName());
+                        break;
+                    }
+                case NodeType.Constant:
+                    {
+                        ExpressionNode_Constant constant = node;
+                        var oriValue = constant.value;
+                        return new SourceStream(oriValue, NewAliasName());
+                    }
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall call = node;
+                        var source = ReadStream(arg, call.arguments[0]);
+
+                        switch (call.methodName)
+                        {
+                            case "Where":
+                                {
+                                    var predicateLambda = call.arguments[1] as ExpressionNode_Lambda;
+                                    var stream = ReadStreamWithWhere(arg, source, predicateLambda);
+                                    if (stream == default) break;
+                                    return stream;
+                                }
+                            case "FirstOrDefault" or "First" or "LastOrDefault" or "Last" when call.arguments.Length == 2:
+                                {
+                                    var predicateLambda = call.arguments[1] as ExpressionNode_Lambda;
+                                    var stream = ReadStreamWithWhere(arg, source, predicateLambda);
+                                    if (stream == default) break;
+                                    stream.method = call.methodName;
+                                    return stream;
+                                }
+                            case "Distinct":
+                                {
+                                    var combinedStream = AsCombinedStream(source);
+
+                                    combinedStream.distinct = true;
+                                    return combinedStream;
+                                }
+                            case "Select":
+                                {
+                                    ExpressionNode_Lambda resultSelector = call.arguments[1];
+
+                                    switch (source)
+                                    {
+                                        case SourceStream sourceStream:
+                                            {
+                                                var parameterName = resultSelector.parameterNames[0];
+                                                var parameterValue = ExpressionNode_RenameableMember.Member(stream: sourceStream, resultSelector.Lambda_GetParamTypes()[0]);
+                                                var newArg = arg.WithParameter(parameterName, parameterValue);
+                                                var select = ReadFieldSelect(newArg, resultSelector);
+
+                                                return new CombinedStream(NewAliasName()) { source = sourceStream, select = select };
+                                            }
+                                        case CombinedStream groupedStream when groupedStream.isGroupedStream:
+                                            {
+                                                var parameterName = resultSelector.parameterNames[0];
+                                                var groupByFields = groupedStream.groupByFields;
+                                                var memberBind = new MemberBind { name = "Key", value = groupByFields };
+                                                var parameterValue = ExpressionNode.New(constructorArgs: new() { memberBind });
+                                                var noChildParameterValue = ExpressionNode_RenameableMember.Member(stream: groupedStream.source, resultSelector.Lambda_GetParamTypes()[0]);
+                                                var newArg = arg.WithParameter(parameterName, parameterValue, noChildParameterValue: noChildParameterValue);
+
+                                                var select = ReadFieldSelect(newArg, resultSelector);
+                                                groupedStream.select = select;
+                                                return groupedStream;
+                                            }
+                                        case CombinedStream combinedStream:
+                                            {
+                                                var parameterName = resultSelector.parameterNames[0];
+                                                var parameterValue = (ExpressionNode)combinedStream.select.fields;
+                                                var newArg = arg.WithParameter(parameterName, parameterValue);
+                                                var select = ReadFieldSelect(newArg, resultSelector);
+
+                                                combinedStream.select = select;
+                                                return combinedStream;
+                                            }
+                                    }
+                                    break;
+                                }
+                            case nameof(Orm_Extensions.ExecuteUpdate):
+                                {
+                                    ExpressionNode_Lambda resultSelector = call.arguments[1];
+                                    switch (source)
+                                    {
+                                        case SourceStream sourceStream:
+                                            {
+                                                var parameterName = resultSelector.parameterNames[0];
+                                                var parameterValue = ExpressionNode_RenameableMember.Member(stream: sourceStream, resultSelector.Lambda_GetParamTypes()[0]);
+
+                                                var select = ReadFieldSelect(arg.WithParameter(parameterName, parameterValue), resultSelector);
+                                                return new StreamToUpdate(sourceStream) { fieldsToUpdate = select.fields };
+                                            }
+                                        case CombinedStream combinedStream:
+                                            {
+                                                var parameterName = resultSelector.parameterNames[0];
+                                                var parameterValue = combinedStream.select.fields as ExpressionNode;
+                                                var select = ReadFieldSelect(arg.WithParameter(parameterName, parameterValue), resultSelector);
+
+                                                return new StreamToUpdate(source) { fieldsToUpdate = select.fields };
+                                            }
+                                    }
+                                    break;
+                                }
+                            case "Take":
+                            case "Skip":
+                                {
+                                    CombinedStream combinedStream = AsCombinedStream(source);
+
+                                    var value = (call.arguments[1] as ExpressionNode_Constant)?.value as int?;
+
+                                    if (call.methodName == "Skip")
+                                        combinedStream.skip = value;
+                                    else
+                                        combinedStream.take = value;
+                                    return combinedStream;
+                                }
+
+                            case "OrderBy" or "OrderByDescending" or "ThenBy" or "ThenByDescending":
+                                {
+                                    CombinedStream combinedStream = AsCombinedStream(source);
+
+                                    var methodName = call.methodName;
+
+                                    var sortField = ReadSortField(call.arguments[1], combinedStream);
+
+                                    var orderParam = new OrderField { member = sortField, asc = !methodName.EndsWith("Descending") };
+
+                                    if (methodName.StartsWith("OrderBy"))
+                                    {
+                                        combinedStream.orders = new List<OrderField>();
+                                    }
+
+                                    combinedStream.orders ??= new List<OrderField>();
+
+                                    combinedStream.orders.Add(orderParam);
+
+                                    return combinedStream;
+                                }
+                            case "FirstOrDefault" or "First" or "LastOrDefault" or "Last" when call.arguments.Length == 1:
+                            case "Count":
+                            case nameof(Orm_Extensions.ExecuteDelete):
+                            case nameof(Orm_Extensions.ToExecuteString):
+                            case "ToList":
+                                {
+                                    if (call.arguments?.Length != 1) break;
+
+                                    CombinedStream combinedStream = AsCombinedStream(source);
+
+                                    combinedStream.method = call.methodName;
+                                    return combinedStream;
+                                }
+                            case nameof(Queryable.GroupBy):
+                                {
+                                    ExpressionNode_Lambda resultSelector = call.arguments[1];
+                                    return GroupBy(arg, source, resultSelector);
+                                }
+                            case nameof(Queryable.SelectMany):  // LeftJoin InnerJoin
+                                {
+                                    ExpressionNode_Lambda rightSelector = call.arguments[1];
+                                    ExpressionNode_Lambda resultSelector = call.arguments[2];
+                                    return SelectMany(arg, source, rightSelector, resultSelector);
+                                }
+                            case nameof(Queryable.Join):  // InnerJoin (Queryable.Join)
+                            case nameof(Queryable.GroupJoin):  // LeftJoin (Queryable.GroupJoin)
+                                {
+                                    var rightStream = ReadStream(arg, call.arguments[1]);
+                                    ExpressionNode_Lambda leftKeySelector = call.arguments[2];
+                                    ExpressionNode_Lambda rightKeySelector = call.arguments[3];
+                                    ExpressionNode_Lambda resultSelector = call.arguments[4];
+                                    return Join(arg, source, rightStream, leftKeySelector, rightKeySelector, resultSelector);
+                                }
+                        }
+                        throw new NotSupportedException("[StreamReader] unexpected method call : " + call.methodName);
+                    }
+            }
+            throw new NotSupportedException($"[StreamReader] unexpected expression nodeType : {node.nodeType}");
+        }
+
+
+
+        // predicateLambda:          father => (father.id == user.fatherId)
+        ExpressionNode ReadWhere(Argument arg, IStream source, ExpressionNode_Lambda predicateLambda)
+        {
+            var parameterName = predicateLambda.parameterNames[0];
+            var parameterValue = ExpressionNode_RenameableMember.Member(stream: source, predicateLambda.Lambda_GetParamTypes()[0]);
+            arg = arg.WithParameter(parameterName, parameterValue);
+
+            return ReadWhere(arg, predicateLambda.body);
+        }
+
+        // predicate:           (father.id == user.fatherId)
+        ExpressionNode ReadWhere(Argument arg, ExpressionNode predicate)
+        {
+            return arg.DeepClone(predicate);
+        }
+
+
+        ExpressionNode ReadFields(Argument arg, ExpressionNode_Lambda resultSelector)
+        {
+            ExpressionNode node = resultSelector.body;
+            if (node?.nodeType != NodeType.New && node?.nodeType != NodeType.Member && node?.nodeType != NodeType.Convert)
+                throw new NotSupportedException($"[StreamReader] unexpected expression nodeType : {node.nodeType}");
+
+            var fields = arg.DeepClone(node);
+            return fields;
+        }
+        SelectedFields ReadFieldSelect(Argument arg, ExpressionNode_Lambda resultSelector)
+        {
+            ExpressionNode node = resultSelector.body;
+            if (node?.nodeType != NodeType.New && node?.nodeType != NodeType.Member)
+                throw new NotSupportedException($"[StreamReader] unexpected expression nodeType : {node.nodeType}");
+
+
+            bool? existCalculatedField = null;
+
+            var fields = arg.DeepClone(node) as ExpressionNode_New;
+
+            if (existCalculatedField != true)
+                existCalculatedField = fields.constructorArgs?.Exists(m => m?.value?.nodeType != NodeType.Member && m?.value?.nodeType != NodeType.New);
+
+            if (existCalculatedField != true)
+                existCalculatedField = fields.memberArgs?.Exists(m => m?.value?.nodeType != NodeType.Member && m?.value?.nodeType != NodeType.New);
+            var isDefaultSelect = !(existCalculatedField ?? false);
+
+            return new() { fields = fields, isDefaultSelect = isDefaultSelect };
+        }
+
+        ExpressionNode ReadSortField(ExpressionNode_Lambda resultSelector, CombinedStream stream)
+        {
+            ExpressionNode parameterValue;
+            if (stream.isGroupedStream)
+            {
+                var groupByFields = stream.groupByFields;
+                var memberBind = new MemberBind { name = "Key", value = groupByFields };
+                parameterValue = ExpressionNode.New(constructorArgs: new() { memberBind });
+            }
+            else
+            {
+                var entityType = resultSelector.Lambda_GetParamTypes()[0];
+                parameterValue = stream.GetSelectedFields(entityType);
+            }
+
+            var parameterName = resultSelector.parameterNames[0];
+            var arg = new Argument().SetParameter(parameterName, parameterValue);
+
+            ExpressionNode sortField = resultSelector.body;
+
+            //if (sortField?.nodeType != NodeType.Member) throw new NotSupportedException($"[StreamReader] unexpected expression nodeType : {sortField.nodeType}");
+
+            var member = arg.DeepClone(sortField);
+            return member;
+        }
+
+    }
+}

+ 15 - 0
src/Vitorm/StreamQuery/StreamToJoin.cs

@@ -0,0 +1,15 @@
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public class StreamToJoin
+    {
+        // LeftJoin , InnerJoin
+        public EJoinType joinType { get; set; }
+        public IStream right { get; set; }
+
+        //  a1.id==b2.id
+        public ExpressionNode on { get; set; }
+    }
+}

+ 33 - 0
src/Vitorm/StreamQuery/StreamToUpdate.cs

@@ -0,0 +1,33 @@
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vitorm.StreamQuery
+{
+    public partial class StreamToUpdate : CombinedStream
+    {
+        public StreamToUpdate(IStream source) : base(source.alias)
+        {
+            if (source is CombinedStream combinedStream)
+            {
+                this.select = combinedStream.select;
+                this.distinct = combinedStream.distinct;
+                this.source = combinedStream.source;
+                this.joins = combinedStream.joins;
+                this.where = combinedStream.where;
+                this.groupByFields = combinedStream.groupByFields;
+                this.having = combinedStream.having;
+                this.orders = combinedStream.orders;
+                this.skip = combinedStream.skip;
+                this.take = combinedStream.take;
+            }
+            else
+            {
+                base.source = source;
+            }
+        }
+
+        // ExpressionNode_New   new { name = name + "_" }
+        public ExpressionNode_New fieldsToUpdate { get; set; }
+
+    }
+}

+ 6 - 4
src/Vitorm/Vitorm.csproj

@@ -12,14 +12,16 @@
 
     <PropertyGroup>
         <Authors>Lith</Authors>
-        <Description> Vitorm : simple orm</Description>
+        <Description>Vitorm : simple orm</Description>
         <PackageProjectUrl>https://github.com/VitormLib/Vitorm</PackageProjectUrl>
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Dapper" Version="2.1.35" />
-        <PackageReference Include="Dapper.Contrib" Version="2.0.78" />
-        <PackageReference Include="Vit.Linq" Version="2.2.22-temp-ki8" />
+        <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Vit.Linq" Version="2.2.22-temp-ki9" />
     </ItemGroup>
 
 </Project>

+ 58 - 0
test/Vitorm.MySql.MsTest/DataSource.cs

@@ -0,0 +1,58 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+using Vit.Core.Util.ConfigurationManager;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Vitorm.MsTest
+{
+    [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+    public class User
+    {
+        [System.ComponentModel.DataAnnotations.Key]
+        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        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; }
+    }
+
+
+    public class DataSource
+    {
+        static string connectionString = Appsettings.json.GetStringByPath("App.Db.ConnectionString");
+ 
+        public static SqlDbContext CreateDbContext()
+        {
+            var dbContext = new SqlDbContext();
+            dbContext.UseMySql(connectionString);
+
+            dbContext.BeginTransaction();
+
+            var userSet = dbContext.DbSet<User>();
+
+            dbContext.Execute(sql: "DROP TABLE  if exists `User`;");
+
+            userSet.Create();
+
+            var users = new List<User> {
+                    new User {   name="u1", fatherId=4, motherId=6 },
+                    new User {   name="u2", fatherId=4, motherId=6 },
+                    new User {   name="u3", fatherId=5, motherId=6 },
+                    new User {   name="u4" },
+                    new User {   name="u5" },
+                    new User {   name="u6" },
+                };
+
+            dbContext.AddRange(users);
+
+            users.ForEach(user => { user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id); });
+
+            dbContext.UpdateRange(users);
+
+            return dbContext;
+        }
+
+    }
+}

+ 48 - 0
test/Vitorm.MySql.MsTest/DbFunction_Test.cs

@@ -0,0 +1,48 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest
+{
+
+    [TestClass]
+    public class DbFunction_Test
+    {
+        [TestMethod]
+        public void Test_DbFunction()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            // select IF(500<1000,true,false)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<bool>("IF", u.fatherId != null, true, false));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(3, userList.Last().id);
+            }
+
+            {
+                var query = userQuery.Where(u => u.birth < DbFunction.Call<DateTime>("now"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+            }
+
+            // coalesce(parameter1,parameter2, …)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<int?>("coalesce", u.fatherId, u.motherId) != null);
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+            }
+
+
+        }
+
+
+    }
+}

+ 35 - 0
test/Vitorm.MySql.MsTest/Vitorm.MySql.MsTest.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
+
+        <PackageReference Include="Vit.Core" Version="2.1.21" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Update="appsettings.json">
+            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Compile Include="..\Vitorm.Sqlite.MsTest\CommonTest\*.cs" Link="CommonTest\%(RecursiveDir)%(FileName)%(Extension)" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Vitorm.MySql\Vitorm.MySql.csproj" />
+    </ItemGroup>
+
+</Project>

+ 7 - 0
test/Vitorm.MySql.MsTest/appsettings.json

@@ -0,0 +1,7 @@
+{
+  "App": {
+    "Db": {
+      "ConnectionString": "Data Source=localhost;Port=3306;Database=dev-orm;SslMode=none;User Id=root;Password=123456;CharSet=utf8;allowPublicKeyRetrieval=true;"
+    }
+  }
+}

+ 66 - 0
test/Vitorm.SqlServer.MsTest/DataSource.cs

@@ -0,0 +1,66 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+using Vit.Core.Util.ConfigurationManager;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Vitorm.MsTest
+{
+    [System.ComponentModel.DataAnnotations.Schema.Table("User", Schema = "dbo")]
+    public class User
+    {
+        [System.ComponentModel.DataAnnotations.Key]
+        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+        [System.ComponentModel.DataAnnotations.Schema.Column("id", TypeName = "int")]
+        public int id { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.Column("name", TypeName = "varchar(1000)")]
+        [System.ComponentModel.DataAnnotations.Required]
+        public string name { get; set; }
+     
+        public DateTime? birth { get; set; }
+
+        public int? fatherId { get; set; }
+        public int? motherId { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
+        public string test{ get; set; }
+    }
+
+
+    public class DataSource
+    {
+        static string connectionString = Appsettings.json.GetStringByPath("App.Db.ConnectionString");
+
+        public static SqlDbContext CreateDbContext()
+        {
+            var dbContext = new SqlDbContext();
+            dbContext.UseSqlServer(connectionString);
+
+            dbContext.BeginTransaction();
+
+            var userSet = dbContext.DbSet<User>();
+
+            dbContext.Execute(sql: "IF OBJECT_ID(N'User', N'U') IS  NOT  NULL \r\nDROP TABLE [User];");
+
+            userSet.Create();
+
+            var users = new List<User> {
+                    new User {   name="u1", fatherId=4, motherId=6 },
+                    new User {   name="u2", fatherId=4, motherId=6 },
+                    new User {   name="u3", fatherId=5, motherId=6 },
+                    new User {   name="u4" },
+                    new User {   name="u5" },
+                    new User {   name="u6" },
+                };
+
+            dbContext.AddRange(users);
+
+            users.ForEach(user => { user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id); });
+
+            dbContext.UpdateRange(users);
+
+            return dbContext;
+        }
+
+    }
+}

+ 48 - 0
test/Vitorm.SqlServer.MsTest/DbFunction_Test.cs

@@ -0,0 +1,48 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest
+{
+
+    [TestClass]
+    public class DbFunction_Test
+    {
+        [TestMethod]
+        public void Test_DbFunction()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            // select * from `User` as t0  where IIF(`t0`.`fatherId` is not null,true, false)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<int>("IIF", u.fatherId != null, 1, 0)==1);
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(3, userList.Last().id);
+            }
+
+            {
+                var query = userQuery.Where(u => u.birth < DbFunction.Call<DateTime>("GETDATE"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+            }
+
+            // coalesce(parameter1,parameter2, …)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<int?>("coalesce", u.fatherId, u.motherId) != null);
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+            }
+
+
+        }
+
+
+    }
+}

+ 35 - 0
test/Vitorm.SqlServer.MsTest/Vitorm.SqlServer.MsTest.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
+
+        <PackageReference Include="Vit.Core" Version="2.1.21" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Vitorm.SqlServer\Vitorm.SqlServer.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Update="appsettings.json">
+            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+    <ItemGroup>
+        <Compile Include="..\Vitorm.Sqlite.MsTest\CommonTest\*.cs" Link="CommonTest\%(RecursiveDir)%(FileName)%(Extension)" />
+    </ItemGroup>
+
+</Project>

+ 7 - 0
test/Vitorm.SqlServer.MsTest/appsettings.json

@@ -0,0 +1,7 @@
+{
+  "App": {
+    "Db": {
+      "ConnectionString": "Server=localhost;Database=dev-orm;User ID=sa;Password=Admin0123;TrustServerCertificate=true;"
+    }
+  }
+}

+ 281 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/CRUD_Test.cs

@@ -0,0 +1,281 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class CRUD_Test
+    {
+        #region #1 Create
+
+        [TestMethod]
+        public void Test_Create()
+        {
+            var user = new User { id = 7, name = "testUser7", birth = DateTime.Now, fatherId = 1, motherId = 2 };
+            var user2 = new User { id = 8, name = "testUser8", birth = DateTime.Now, fatherId = 3, motherId = 4 };
+
+            // #1 Add
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                dbContext.Add(user);
+
+                Assert.AreEqual(7, userQuery.Count());
+
+                var newUser = userQuery.FirstOrDefault(m => m.id == 7);
+                Assert.AreEqual(user.id, newUser?.id);
+                Assert.AreEqual(user.name, newUser?.name);
+            }
+
+            // #2 AddRange
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                dbContext.AddRange(new[] { user, user2 });
+
+                Assert.AreEqual(8, userQuery.Count());
+
+                var newUsers = userQuery.Where(m => m.id >= 7).ToList();
+                Assert.AreEqual(2, newUsers.Count());
+                Assert.AreEqual(user.id, newUsers[0]?.id);
+                Assert.AreEqual(user2.id, newUsers[1]?.id);
+            }
+
+        }
+        #endregion
+
+
+        #region #3 Update
+
+        [TestMethod]
+        public void Test_Update()
+        {
+            var birth = DateTime.Parse("2021-03-01 00:00:00");
+            var user = new User { id = 4, name = "testUser4", birth = birth, fatherId = 14 };
+            var user2 = new User { id = 5, name = "testUser5", birth = DateTime.Now, fatherId = 15 };
+
+            #region Update
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.Update(user);
+                Assert.AreEqual(1, rowCount);
+
+                var newUser = userQuery.FirstOrDefault(m => m.id == 4);
+                Assert.AreEqual(4, newUser.id);
+                Assert.AreEqual(user.name, newUser.name);
+                Assert.AreEqual(user.birth, newUser.birth);
+                Assert.AreEqual(user.fatherId, newUser.fatherId);
+            }
+            #endregion
+
+            #region UpdateRange
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.UpdateRange(new[] { user, user2 });
+                Assert.AreEqual(2, rowCount);
+
+                var newUsers = userQuery.Where(m => m.id == 4 || m.id == 5).ToList();
+                Assert.AreEqual(user.id, newUsers[0].id);
+                Assert.AreEqual(user.name, newUsers[0].name);
+                Assert.AreEqual(user2.id, newUsers[1].id);
+                Assert.AreEqual(user2.name, newUsers[1].name);
+            }
+            #endregion
+
+        }
+
+
+        [TestMethod]
+        public void Test_ExecuteUpdate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = userQuery.ExecuteUpdate(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? ""),
+                    birth = DateTime.Parse("2021-01-11 00:00:00")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                Assert.AreEqual(DateTime.Parse("2021-01-11 00:00:00"), userList.First().birth);
+                Assert.AreEqual("u_6__", userList.Last().name);
+            }
+
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = query.ExecuteUpdate(row => new User
+                {
+                    name = "u2_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(6, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u2_1_4_6", userList.First().name);
+                Assert.AreEqual("u2_6__", userList.Last().name);
+            }
+
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = query.ExecuteUpdate(row => new User
+                {
+                    name = "u3_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(3, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u3_1_4_6", userList[0].name);
+                Assert.AreEqual("u3_3_5_6", userList[2].name);
+                Assert.AreEqual("u2_4__", userList[3].name);
+            }
+        }
+        #endregion
+
+
+        #region #4 Delete
+
+
+        [TestMethod]
+        public void Test_Delete()
+        {
+
+            #region #1 Delete
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.Delete(new User { id = 5 });
+
+                Assert.AreEqual(1, rowCount);
+                Assert.AreEqual(5, userQuery.Count());
+            }
+            #endregion
+
+            #region #2 DeleteRange
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.DeleteRange(new[] { new User { id = 5 }, new User { id = 6 }, new User { id = 10 } });
+
+                Assert.AreEqual(2, rowCount);
+                Assert.AreEqual(4, userQuery.Count());
+            }
+            #endregion
+
+            #region #3 DeleteByKey
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.DeleteByKey<User>(4);
+                Assert.AreEqual(1, rowCount);
+                Assert.AreEqual(5, userQuery.Count());
+            }
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.DeleteByKey<User>(7);
+                Assert.AreEqual(0, rowCount);
+                Assert.AreEqual(6, userQuery.Count());
+            }
+            #endregion
+
+
+            #region #4 DeleteByKeys
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userSet = dbContext.DbSet<User>();
+
+                var rowCount = userSet.DeleteByKeys(new[] { 5, 6, 10 });
+
+                Assert.AreEqual(2, rowCount);
+                Assert.AreEqual(4, userSet.Query().Count());
+            }
+            #endregion
+        }
+
+
+
+        [TestMethod]
+        public void Test_ExecuteDelete()
+        {
+            if (1 == 1)
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father
+                            };
+
+                var rowCount = query.ExecuteDelete();
+
+                Assert.AreEqual(3, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(3, newUsers.Count());
+                Assert.AreEqual(4, newUsers.First().id);
+                Assert.AreEqual(6, newUsers.Last().id);
+            }
+
+            if (1 == 1)
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = userQuery.Where(m => m.id == 2 || m.id == 4).ExecuteDelete();
+
+                Assert.AreEqual(2, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(4, newUsers.Count());
+                Assert.AreEqual(1, newUsers.First().id);
+                Assert.AreEqual(3, newUsers[1].id);
+                Assert.AreEqual(5, newUsers[2].id);
+            }
+        }
+        #endregion
+
+
+    }
+}

+ 182 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_Group_Test.cs

@@ -0,0 +1,182 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_Group_Test
+    {
+
+        [TestMethod]
+        public void Test_Group_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                        from user in userQuery
+                        group user by new { user.fatherId, user.motherId } into userGroup
+                        select new { userGroup.Key.fatherId, userGroup.Key.motherId };
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(3, rows.Count);
+                Assert.AreEqual(0, rows.Select(u => u.fatherId).Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(0, rows.Select(u => u.motherId).Except(new int?[] { 6, null }).Count());
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                        userQuery
+                        .GroupBy(user => new { user.fatherId, user.motherId })
+                        .Select(userGroup => new
+                        {
+                            userGroup.Key.fatherId,
+                            userGroup.Key.motherId
+                        })
+                        ;
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(3, rows.Count);
+                Assert.AreEqual(0, rows.Select(u => u.fatherId).Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(0, rows.Select(u => u.motherId).Except(new int?[] { 6, null }).Count());
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Group_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                        from user in userQuery.Where(u => u.id > 1)
+                        group user by new { user.fatherId, user.motherId } into userGroup
+                        where userGroup.Key.motherId != null && userGroup.Count() >= 1
+                        orderby userGroup.Key.fatherId descending, userGroup.Count() descending
+                        select new { userGroup.Key.fatherId, userGroup.Key.motherId, rowCount = userGroup.Count(), maxId = userGroup.Max(m => m.id) };
+
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(1, rows.Count);
+                Assert.AreEqual(4, rows[0].fatherId);
+                Assert.AreEqual(6, rows[0].motherId);
+                Assert.AreEqual(1, rows[0].rowCount);
+                Assert.AreEqual(2, rows[0].maxId);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                        userQuery
+                        .Where(u => u.id > 1)
+                        .GroupBy(user => new { user.fatherId, user.motherId })
+                        .Where(userGroup => userGroup.Key.motherId != null)
+                        .OrderByDescending(userGroup => userGroup.Key.fatherId)
+                        .Select(userGroup => new
+                        {
+                            userGroup.Key.fatherId,
+                            userGroup.Key.motherId,
+                            rowCount = userGroup.Count(),
+                            maxId = userGroup.Max(m => m.id)
+                        })
+                        .Skip(1)
+                        .Take(1)
+                        ;
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(1, rows.Count);
+                Assert.AreEqual(4, rows[0].fatherId);
+                Assert.AreEqual(6, rows[0].motherId);
+                Assert.AreEqual(1, rows[0].rowCount);
+                Assert.AreEqual(2, rows[0].maxId);
+            }
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_Others()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query =
+                    userQuery
+                    .GroupBy(user => new { user.fatherId, user.motherId })
+                    .OrderByDescending(group => group.Count())
+                    .Select(userGroup => new
+                    {
+                        userGroup.Key.fatherId,
+                        rowCount = userGroup.Count(),
+                        maxId = userGroup.Max(m => m.id),
+                        minId = userGroup.Min(m => m.id),
+                        sumId = userGroup.Sum(m => m.id),
+                        avgId = userGroup.Average(m => (double)m.id)
+                    })
+                    ;
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(3, rows.Count);
+                var row = rows[1];
+                Assert.AreEqual(2, row.rowCount);
+                Assert.AreEqual(2, row.maxId);
+                Assert.AreEqual(1, row.minId);
+                Assert.AreEqual(3, row.sumId);
+                Assert.AreEqual(1.5, row.avgId);
+            }
+            {
+                var query =
+                    userQuery
+                    .GroupBy(user => new { user.fatherId, user.motherId })
+                    .Where(userGroup => userGroup.Key.motherId != null)
+                    .OrderByDescending(userGroup => userGroup.Key.fatherId)
+                    .Select(userGroup => new { userGroup.Key.fatherId, userGroup.Key.motherId })
+                    ;
+
+                var rows = query.ToList();
+                var sql = query.ToExecuteString();
+
+                Assert.AreEqual(2, rows.Count);
+                Assert.AreEqual(5, rows[0].fatherId);
+            }
+            {
+                var query =
+                    userQuery
+                    .GroupBy(user => user.fatherId)
+                    .Where(userGroup => userGroup.Key != null)
+                    .OrderByDescending(userGroup => userGroup.Key)
+                    .Select(userGroup => new { fatherId = userGroup.Key, rowCount = userGroup.Count() })
+                    ;
+
+                var rows = query.ToList();
+                var sql = query.ToExecuteString();
+
+                Assert.AreEqual(2, rows.Count);
+                Assert.AreEqual(5, rows[0].fatherId);
+            }
+        }
+
+
+    }
+}

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

@@ -0,0 +1,218 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_InnerJoin_ByJoin_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id)
+                    where user.id > 2
+                    select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.SelectMany(
+                        user => userQuery.Where(father => user.fatherId == father.id)
+                        , (user, father) => new { user, father }
+                    )
+                    .Where(row => row.user.id > 2)
+                    .Select(row => new { row.user, row.father });
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+
+        }
+
+
+        [TestMethod]
+        public void Test_InnerJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    join father in userQuery on user.fatherId equals father.id
+                    join mother in userQuery on user.motherId equals mother.id
+                    where user.id > 1
+                    orderby father.id descending
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father != null ? true : false
+                    };
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(2, first.user.id);
+                Assert.AreEqual(4, first.father.id);
+                Assert.AreEqual(6, first.mother.id);
+                Assert.AreEqual(102, first.testId);
+                Assert.AreEqual(true, first.hasFather);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.Join(
+                        userQuery
+                        , user => user.fatherId
+                        , father => father.id
+                        , (user, father) => new { user, father }
+                    ).Join(
+                        userQuery
+                        , row => row.user.motherId
+                        , mother => mother.id
+                        , (row, mother) => new { row, mother }
+                    )
+                    .Where(row2 => row2.row.user.id > 1)
+                    .OrderByDescending(row2 => row2.row.father.id)
+                    .Select(row2 =>
+                        new
+                        {
+                            row2.row.user,
+                            row2.row.father,
+                            row2.mother,
+                            testId = row2.row.user.id + 100,
+                            hasFather = row2.row.father != null ? true : false
+                        }
+                    );
+
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(2, first.user.id);
+                Assert.AreEqual(4, first.father.id);
+                Assert.AreEqual(6, first.mother.id);
+                Assert.AreEqual(102, first.testId);
+                Assert.AreEqual(true, first.hasFather);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_InnerJoin_Others()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // simple
+            {
+                var query =
+                    userQuery.Join(
+                        userQuery
+                        , user => user.fatherId
+                        , father => father.id
+                        , (user, father) => new { user, father }
+                    );
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(1, userList.First().user.id);
+            }
+
+            // where
+            {
+                var query =
+                    userQuery.Join(
+                        userQuery
+                        , user => user.fatherId
+                        , father => father.id
+                        , (user, father) => new { user, father }
+                    ).Where(row => row.user.id > 2);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+            }
+            // select
+            {
+                var query =
+                    userQuery.Join(
+                        userQuery
+                        , user => user.fatherId
+                        , father => father.id
+                        , (user, father) => new { user, father }
+                    ).Where(row => row.user.id > 2)
+                    .Select(row => new { userId = row.user.id, fatherId = row.father.id });
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().userId);
+                Assert.AreEqual(5, userList.First().fatherId);
+            }
+            // full feature
+            {
+                var query =
+                         from user in userQuery
+                         join father in userQuery on user.fatherId equals father.id
+                         where user.id > 1
+                         orderby user.id descending
+                         select new { user, father };
+
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(2, userList.First().user.id);
+            }
+
+        }
+
+
+
+
+    }
+}

+ 135 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_InnerJoin_BySelectMany_Test.cs

@@ -0,0 +1,135 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_InnerJoin_BySelectMany_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id)
+                    where user.id > 2
+                    select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.SelectMany(
+                        user => userQuery.Where(father => user.fatherId == father.id)
+                        , (user, father) => new { user, father }
+                    )
+                    .Where(row => row.user.id > 2)
+                    .Select(row => new { row.user, row.father });
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_InnerJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id)
+                    from mother in userQuery.Where(mother => user.motherId == mother.id)
+                    where user.id > 1
+                    orderby father.id descending
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father != null ? true : false
+                    };
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(2, first.user.id);
+                Assert.AreEqual(4, first.father.id);
+                Assert.AreEqual(6, first.mother.id);
+                Assert.AreEqual(102, first.testId);
+                Assert.AreEqual(true, first.hasFather);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.SelectMany(
+                        user => userQuery.Where(father => user.fatherId == father.id)
+                        , (user, father) => new { user, father }
+                    ).SelectMany(
+                        row => userQuery.Where(mother => row.user.motherId == mother.id)
+                        , (row, mother) => new { row, mother }
+                    )
+                    .Where(row2 => row2.row.user.id > 1)
+                    .OrderByDescending(row2 => row2.row.father.id)
+                    .Select(row2 =>
+                        new
+                        {
+                            row2.row.user,
+                            row2.row.father,
+                            row2.mother,
+                            testId = row2.row.user.id + 100,
+                            hasFather = row2.row.father != null ? true : false
+                        }
+                    );
+
+                query = query.Skip(1).Take(1);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(2, first.user.id);
+                Assert.AreEqual(4, first.father.id);
+                Assert.AreEqual(6, first.mother.id);
+                Assert.AreEqual(102, first.testId);
+                Assert.AreEqual(true, first.hasFather);
+            }
+        }
+
+
+
+
+
+    }
+}

+ 107 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LeftJoin_ByGroupJoin_Test.cs

@@ -0,0 +1,107 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_LeftJoin_ByGroupJoin_Test
+    {
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    join father in userQuery on user.fatherId equals father.id into fathers
+                    from father in fathers.DefaultIfEmpty()
+                    where user.id > 2
+                    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?.id);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.GroupJoin(
+                        userQuery
+                        , user => user.fatherId
+                        , father => father.id
+                        , (user, fathers) => new { user, fathers }
+                    )
+                    .SelectMany(
+                        row => row.fathers.DefaultIfEmpty()
+                        , (row, father) => new { row, father }
+                    )
+                    .Where(row2 => row2.row.user.id > 2)
+                    .Select(row2 => new { row2.row.user, row2.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?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    join father in userQuery on user.fatherId equals father.id into fathers
+                    from father in fathers.DefaultIfEmpty()
+                    join mother in userQuery on user.motherId equals mother.id into mothers
+                    from mother in mothers.DefaultIfEmpty()
+                    where user.id > 2
+                    orderby father.id descending
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father != null ? true : false
+                    };
+
+                query = query.Skip(1).Take(2);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(2, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(4, first.user.id);
+                Assert.AreEqual(null, first.father?.id);
+                Assert.AreEqual(null, first.mother?.id);
+                Assert.AreEqual(104, first.testId);
+                Assert.AreEqual(false, first.hasFather);
+            }
+        }
+
+
+    }
+}

+ 200 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_LeftJoin_BySelectMany_Test.cs

@@ -0,0 +1,200 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_LeftJoin_BySelectMany_Test
+    {
+
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                        from user in userQuery
+                        from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                        where user.id > 2
+                        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?.id);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                        userQuery.SelectMany(
+                            user => userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            , (user, father) => new { user, father }
+                        )
+                        .Where(row => row.user.id > 2)
+                        .Select(row => new { row.user, row.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?.id);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expresssion
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                    from mother in userQuery.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
+                    where user.id > 2
+                    orderby father.id descending
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father != null ? true : false
+                    };
+
+                query = query.Skip(1).Take(2);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(2, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(4, first.user.id);
+                Assert.AreEqual(null, first.father?.id);
+                Assert.AreEqual(null, first.mother?.id);
+                Assert.AreEqual(104, first.testId);
+                Assert.AreEqual(false, first.hasFather);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_MultipleSelect()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            {
+                var query = from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                             where user.id > 2 && father != null
+                             select new
+                             {
+                                 user,
+                                 father
+                             };
+
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+            }
+
+            {
+                var query = from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id)
+                             from mother in userQuery.Where(mother => user.motherId == mother.id)
+                             select new
+                             {
+                                 uniqueId = user.id + "_" + father.id + "_" + mother.id,
+                                 uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                                 user,
+                                 user2 = user,
+                                 user3 = user,
+                                 father,
+                                 hasFather = user.fatherId != null ? true : false,
+                                 fatherName = father.name,
+                                 mother
+                             };
+
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(1, userList.First().user.id);
+                Assert.AreEqual(3, userList.Last().user.id);
+                Assert.AreEqual(5, userList.Last().father?.id);
+            }
+
+            {
+                var query = from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                             from mother in userQuery.Where(mother => user.motherId == mother.id).DefaultIfEmpty()
+                             select new
+                             {
+                                 user,
+                                 father,
+                                 userId = user.id + 100,
+                                 hasFather = user.fatherId != null ? true : false,
+                                 hasFather2 = father != null,
+                                 fatherName = father.name,
+                                 motherName = mother.name,
+                             };
+
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().user.id);
+                Assert.AreEqual(101, userList.First().userId);
+                Assert.AreEqual(6, userList.Last().user.id);
+                Assert.AreEqual(5, userList[2].father.id);
+            }
+
+            {
+                var query = from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id)
+                             from mother in userQuery.Where(mother => user.motherId == mother.id)
+                             where user.id > 1
+                             orderby father.id descending
+                             select new
+                             {
+                                 user,
+                                 father,
+                                 userId = user.id + 100,
+                                 hasFather = user.fatherId != null ? true : false,
+                                 hasFather2 = father != null,
+                                 fatherName = father.name,
+                                 motherName = mother.name,
+                             };
+
+                var userList = query.ToList();
+
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(5, userList.First().father?.id);
+                Assert.AreEqual(4, userList.Last().father?.id);
+            }
+
+        }
+
+
+    }
+}

+ 430 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Query_Test.cs

@@ -0,0 +1,430 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_Test
+    {
+        [TestMethod]
+        public void Test_Get()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var user = dbContext.Get<User>(3);
+                Assert.AreEqual(3, user?.id);
+            }
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var user = dbContext.DbSet<User>().Get(5);
+                Assert.AreEqual(5, user?.id);
+            }
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+
+            {
+                var userList = userQuery.Select(u => u.id).ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_Where()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Where(u => u.id > 2).Where(m => m.id < 4).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+            {
+                var userList = userQuery.Where(u => u.id + 1 == 4).Where(m => m.fatherId == 5).ToList();
+                Assert.AreEqual(3, userList.First().id);
+            }
+            {
+                var userList = userQuery.Where(u => 4 == u.id + 1).Where(m => m.fatherId == 5).ToList();
+                Assert.AreEqual(3, userList.First().id);
+            }
+
+
+            {
+                var userList = userQuery.Where(u => u.birth == new DateTime(2021, 01, 01, 03, 00, 00)).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+            {
+                var userList = userQuery.Where(u => u.birth == DateTime.Parse("2021-01-01 01:00:00").AddHours(2)).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Select()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Select(u => u).Where(user => user.id > 2).Where(user => user.id < 4).Select(u => u).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+
+
+            {
+                var query =
+                    from user in userQuery
+                    select new
+                    {
+                        uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                        uniqueId2 = $"{user.id}_{user.fatherId}_{user.motherId}"
+                    };
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual("1_4_6", userList.First().uniqueId1);
+            }
+
+        }
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = (from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                             where user.id > 2 && father == null
+                             select new
+                             {
+                                 father
+                             }).Count();
+
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_AllFeatures()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            #region SelectMany().Where().OrderBy().Skip().Take().ToExecuteString()
+            /*
+            users.SelectMany(
+                user => users.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                , (user, father) => new {user = user, father = father}
+            ).Where(row => row.user.id > 2)
+            .Select(row => new {row.user })
+            .OrderBy(user=>user.id)
+            .Skip(1).Take(2);
+             */
+            {
+                var query = (from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                             where user.id > 2
+                             orderby father.id, user.id descending
+                             select new
+                             {
+                                 user
+                             })
+                            .Skip(1).Take(2);
+
+                var sql = query.ToExecuteString();
+                var list = query.ToList();
+
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual(5, list[0].user.id);
+                Assert.AreEqual(4, list[1].user.id);
+            }
+            #endregion
+        }
+
+
+
+        [TestMethod]
+        public void Test_FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.FirstOrDefault();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = userQuery.FirstOrDefault(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = userQuery.FirstOrDefault(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = userQuery.OrderByDescending(m => m.id).FirstOrDefault();
+                Assert.AreEqual(6, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_First()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.First();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = userQuery.First(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = userQuery.First(user => user.id == 13);
+                    Assert.Fail("IQueryalbe.First should throw Exception");
+                }
+                catch (Exception ex)
+                {
+                }
+
+            }
+
+        }
+
+
+
+        [TestMethod]
+        public void Test_LastOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.LastOrDefault();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = userQuery.LastOrDefault(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = userQuery.LastOrDefault(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = userQuery.OrderByDescending(m => m.id).LastOrDefault();
+                Assert.AreEqual(1, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Last()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.Last();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = userQuery.Last(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = userQuery.Last(user => user.id == 13);
+                    Assert.Fail("IQueryalbe.First should throw Exception");
+                }
+                catch (Exception ex)
+                {
+                }
+
+            }
+
+        }
+
+        // Enumerable.ToArray
+        [TestMethod]
+        public void Test_Enumerable_ToArray()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.ToArray();
+                Assert.AreEqual(6, userList.Length);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+
+            {
+                var userList = userQuery.Select(u => u.id).ToArray();
+                Assert.AreEqual(6, userList.Length);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+
+        // Enumerable.Contains
+        // Queryable.Contains
+        [TestMethod]
+        public void Test_Contains()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Where(u => new[] { 3, 5 }.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual(5, userList.Last().id);
+            }
+            {
+                var ids = new[] { 3, 5 }.AsEnumerable();
+                var userList = userQuery.Where(u => ids.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual(5, userList.Last().id);
+            }
+            {
+                var ids = new[] { 3, 5 }.AsQueryable();
+                var userList = userQuery.Where(u => ids.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual(5, userList.Last().id);
+            }
+        }
+
+        [TestMethod]
+        public void Test_StringMethods()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            userQuery.ExecuteUpdate(row => new User
+            {
+                name = "u|" + row.id + "|" + (row.fatherId.ToString() ?? "") + "|" + (row.motherId.ToString() ?? "")
+            });
+
+            // StartsWith
+            {
+                var query = userQuery.Where(u => u.name.StartsWith("u|3|5"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u|3|5|6", userList.First().name);
+            }
+            // EndsWith
+            {
+                var query = userQuery.Where(u => u.name.EndsWith("3|5|6"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u|3|5|6", userList.First().name);
+            }
+            // Contains
+            {
+                var query = userQuery.Where(u => u.name.Contains("|3|5|"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u|3|5|6", userList.First().name);
+            }
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_Distinct()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                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();
+
+                Assert.AreEqual(3, ids.Count);
+                Assert.AreEqual(0, ids.Except(new int?[] { 4, 5, null }).Count());
+            }
+            {
+                var query = userQuery.Select(u => u.fatherId).Distinct();
+
+                var sql = query.ToExecuteString();
+                var ids = query.ToList();
+
+                Assert.AreEqual(3, ids.Count);
+                Assert.AreEqual(0, ids.Except(new int?[] { 4, 5, null }).Count());
+            }
+            {
+                var query = userQuery.Distinct();
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+            }
+
+        }
+
+
+
+
+    }
+}

+ 132 - 0
test/Vitorm.Sqlite.MsTest/CommonTest/Transaction_Test.cs

@@ -0,0 +1,132 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Transaction_Test
+    {
+
+        [TestMethod]
+        public void Test_Transaction()
+        {
+            #region Transaction
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userSet = dbContext.DbSet<User>();
+
+                Assert.AreEqual("u4", userSet.Get(4).name);
+
+                dbContext.Update(new User { id = 4, name = "u41" });
+                Assert.AreEqual("u41", userSet.Get(4).name);
+
+                using (var tran = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u42" });
+                    Assert.AreEqual("u42", userSet.Get(4).name);
+                }
+                Assert.AreEqual("u41", userSet.Get(4).name);
+
+                using (var tran = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u42" });
+                    Assert.AreEqual("u42", userSet.Get(4).name);
+                    tran.Rollback();
+                }
+                Assert.AreEqual("u41", userSet.Get(4).name);
+
+                using (var tran = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u43" });
+                    Assert.AreEqual("u43", userSet.Get(4).name);
+                    tran.Commit();
+                }
+                Assert.AreEqual("u43", userSet.Get(4).name);
+
+            }
+            #endregion
+        }
+
+
+        // can not test for db is not durable
+        //[TestMethod]
+        public void Test_Dispose()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userSet = dbContext.DbSet<User>();
+
+                var tran2 = dbContext.BeginTransaction();
+                {
+                    dbContext.Update(new User { id = 4, name = "u42" });
+                    Assert.AreEqual("u42", userSet.Get(4).name);
+                    tran2.Commit();
+                }
+
+                Assert.AreEqual("u42", userSet.Get(4).name);
+
+                var tran3 = dbContext.BeginTransaction();
+                {
+                    dbContext.Update(new User { id = 4, name = "u43" });
+                    Assert.AreEqual("u43", userSet.Get(4).name);
+                }
+                Assert.AreEqual("u43", userSet.Get(4).name);
+            }
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userSet = dbContext.DbSet<User>();
+
+                //Assert.AreEqual("u42", userSet.Get(4).name);
+            }
+
+        }
+
+        [TestMethod]
+        public void Test_NestedTransaction()
+        {
+            #region NestedTransaction
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userSet = dbContext.DbSet<User>();
+
+                Assert.AreEqual("u4", userSet.Get(4).name);
+
+                using (var tran1 = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u41" });
+                    Assert.AreEqual("u41", userSet.Get(4).name);
+
+                    using (var tran2 = dbContext.BeginTransaction())
+                    {
+                        dbContext.Update(new User { id = 4, name = "u42" });
+                        Assert.AreEqual("u42", userSet.Get(4).name);
+                    }
+                    Assert.AreEqual("u41", userSet.Get(4).name);
+
+                    using (var tran2 = dbContext.BeginTransaction())
+                    {
+                        dbContext.Update(new User { id = 4, name = "u42" });
+                        Assert.AreEqual("u42", userSet.Get(4).name);
+                        tran2.Rollback();
+                    }
+                    Assert.AreEqual("u41", userSet.Get(4).name);
+
+                    using (var tran2 = dbContext.BeginTransaction())
+                    {
+                        dbContext.Update(new User { id = 4, name = "u43" });
+                        Assert.AreEqual("u43", userSet.Get(4).name);
+                        tran2.Commit();
+                    }
+                    Assert.AreEqual("u43", userSet.Get(4).name);
+                }
+
+                Assert.AreEqual("u4", userSet.Get(4).name);
+            }
+            #endregion
+        }
+
+
+
+
+    }
+}

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

@@ -0,0 +1,55 @@
+using Vitorm.Sql;
+using Vit.Extensions;
+
+namespace Vitorm.MsTest
+{
+    [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; }
+    }
+
+
+    public class DataSource
+    {
+        public static SqlDbContext CreateDbContext()
+        {
+            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();
+            dbContext.UseSqlite(connectionString);
+
+            dbContext.BeginTransaction();
+
+            var userSet = dbContext.DbSet<User>();
+            userSet.Create();
+
+            var users = new List<User> {
+                    new User { id=1, name="u1", fatherId=4, motherId=6 },
+                    new User { id=2, name="u2", fatherId=4, motherId=6 },
+                    new User { id=3, name="u3", fatherId=5, motherId=6 },
+                    new User { id=4, name="u4" },
+                    new User { id=5, name="u5" },
+                    new User { id=6, name="u6" },
+                };
+            users.ForEach(user => { user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id); });
+
+            dbContext.AddRange(users);
+
+            return dbContext;
+        }
+
+    }
+}

+ 56 - 0
test/Vitorm.Sqlite.MsTest/DbFunction_Test.cs

@@ -0,0 +1,56 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Vitorm_Extensions;
+using System.Data;
+
+namespace Vitorm.MsTest
+{
+
+    [TestClass]
+    public class DbFunction_Test
+    {
+        [TestMethod]
+        public void Test_DbFunction()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            // select * from `User` as t0  where IIF(`t0`.`fatherId` is not null,true, false)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<bool>("IIF", u.fatherId != null, true, false));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(3, userList.Last().id);
+            }
+
+            {
+                var query = userQuery.Where(u => u.birth == DbFunction.Call<DateTime?>("datetime", "2021-01-01 00:00:00", "+2 hours"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(2, userList.First().id);
+            }
+            {
+                var query = userQuery.Where(u => u.birth == DbFunction.Call<DateTime>("datetime", "2021-01-01 00:00:00", "+" + u.id + " hours"));
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+            }
+
+            // coalesce(parameter1,parameter2, …)
+            {
+                var query = userQuery.Where(u => DbFunction.Call<int?>("coalesce", u.fatherId, u.motherId) != null);
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+            }
+
+
+        }
+
+
+    }
+}

+ 25 - 0
test/Vitorm.Sqlite.MsTest/Vitorm.Sqlite.MsTest.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
+
+        <PackageReference Include="Vit.Core" Version="2.1.21" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\src\Vitorm.Sqlite\Vitorm.Sqlite.csproj" />
+    </ItemGroup>
+
+</Project>