Lith há 11 meses atrás
commit
8ce95af3dc
99 ficheiros alterados com 8247 adições e 0 exclusões
  1. 77 0
      .github/workflows/action-ki_multibranch.yml
  2. 201 0
      LICENSE
  3. 41 0
      Publish/DevOps3/build-bash/19.get-app-version.bash
  4. 46 0
      Publish/DevOps3/build-bash/20.change-app-version.bash
  5. 47 0
      Publish/DevOps3/build-bash/21.change-to-next-version.bash
  6. 41 0
      Publish/DevOps3/build-bash/22.add-suffix-to-app-version.bash
  7. 44 0
      Publish/DevOps3/build-bash/30.nuget-pack.sh
  8. 93 0
      Publish/DevOps3/build-bash/40.Station-publish.sh
  9. 23 0
      Publish/DevOps3/build-bash/41.extra-publish.sh
  10. 46 0
      Publish/DevOps3/build-bash/50.docker-image-copy.sh
  11. 27 0
      Publish/DevOps3/build-bash/51.docker-deploy-copy.sh
  12. 23 0
      Publish/DevOps3/build-bash/52.docker-extra-copy.sh
  13. 32 0
      Publish/DevOps3/build-bash/startup.bash
  14. 23 0
      Publish/DevOps3/build-cmd/30.nuget-pack.bat
  15. 31 0
      Publish/DevOps3/build-cmd/40.Station-publish(net5.0).bat
  16. 31 0
      Publish/DevOps3/build-cmd/40.Station-publish(netcoreapp2.1).bat
  17. 57 0
      Publish/DevOps3/build-cmd/40.Station-publish.bat
  18. 97 0
      Publish/DevOps3/build-cmd/41.StressTest-publish.bat
  19. 36 0
      Publish/DevOps3/build-cmd/50.docker-image-create.bat
  20. 36 0
      Publish/DevOps3/build-cmd/51.docker-deploy-copy.bat
  21. 9 0
      Publish/DevOps3/build-cmd/Clean-All.bat
  22. 1 0
      Publish/DevOps3/build-cmd/Clean-ReleaseFiles.bat
  23. 21 0
      Publish/DevOps3/build-cmd/OneKey Release.bat
  24. 46 0
      Publish/DevOps3/build-cmd/Version-To-NextTemp.bat
  25. 46 0
      Publish/DevOps3/build-cmd/Version-To-Preview.bat
  26. 43 0
      Publish/DevOps3/build-cmd/Version-To-Release.bat
  27. 32 0
      Publish/DevOps3/build-cmd/Version-getVersion.bat
  28. BIN
      Publish/DevOps3/build-cmd/VsTool.exe
  29. 1 0
      Publish/DevOps3/environment/env.APPNAME.txt
  30. 1 0
      Publish/DevOps3/environment/env.envName.txt
  31. 13 0
      Publish/DevOps3/environment/readme.md
  32. 30 0
      Publish/DevOps3/github-bash/74.github-push-to-webdav.sh
  33. 60 0
      Publish/DevOps3/github-bash/76.github-push-release.sh
  34. 70 0
      Publish/DevOps3/github-bash/startup.bash
  35. 168 0
      Publish/DevOps3/jenkins-bash/CICD.ki.git_Multibranch.deploy.jenkinsfile
  36. 43 0
      Publish/DevOps3/release-bash/71.file-zip.sh
  37. 38 0
      Publish/DevOps3/release-bash/72.nuget-push.sh
  38. 39 0
      Publish/DevOps3/release-bash/73.docker-image-build-push.sh
  39. 41 0
      Publish/DevOps3/release-bash/74.docker-image-build-push_amd64.bash
  40. 71 0
      Publish/DevOps3/release-bash/75.docker-image-build-push_cross.bash
  41. 47 0
      Publish/DevOps3/release-bash/78.push-releaseFiles-to-webdav.bash
  42. 57 0
      Publish/DevOps3/release-bash/startup.bash
  43. 77 0
      README.md
  44. 70 0
      Vit.Orm.sln
  45. 10 0
      clean-temp.bat
  46. 279 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/CRUD_Test.cs
  47. 58 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/DataSource.cs
  48. 181 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_Group_Test.cs
  49. 218 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_InnerJoin_ByJoin_Test.cs
  50. 135 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_InnerJoin_BySelectMany_Test.cs
  51. 107 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_LeftJoin_ByGroupJoin_Test.cs
  52. 200 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_LeftJoin_BySelectMany_Test.cs
  53. 470 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_Test.cs
  54. 133 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Transaction_Test.cs
  55. 32 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Vit.Orm.Mysql.MsTest.csproj
  56. 9 0
      src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/appsettings.json
  57. 30 0
      src/Vit.Orm.Mysql/Vit.Orm.Mysql/DbContext_Extensions.cs
  58. 32 0
      src/Vit.Orm.Mysql/Vit.Orm.Mysql/SqlTranslator.cs
  59. 66 0
      src/Vit.Orm.Mysql/Vit.Orm.Mysql/Translator/ExecuteDeleteTranslator.cs
  60. 91 0
      src/Vit.Orm.Mysql/Vit.Orm.Mysql/Translator/ExecuteUpdateTranslator.cs
  61. 22 0
      src/Vit.Orm.Mysql/Vit.Orm.Mysql/Vit.Orm.Mysql.csproj
  62. 279 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/CRUD_Test.cs
  63. 66 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/DataSource.cs
  64. 181 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_Group_Test.cs
  65. 218 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_InnerJoin_ByJoin_Test.cs
  66. 135 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_InnerJoin_BySelectMany_Test.cs
  67. 107 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_LeftJoin_ByGroupJoin_Test.cs
  68. 200 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_LeftJoin_BySelectMany_Test.cs
  69. 470 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_Test.cs
  70. 133 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Transaction_Test.cs
  71. 24 0
      src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Vit.Orm.Sqlite.MsTest.csproj
  72. 30 0
      src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/DbContext_Extensions.cs
  73. 32 0
      src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/SqlTranslator.cs
  74. 66 0
      src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Translator/ExecuteDeleteTranslator.cs
  75. 91 0
      src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Translator/ExecuteUpdateTranslator.cs
  76. 21 0
      src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Vit.Orm.Sqlite.csproj
  77. 72 0
      src/Vit.Orm/DbContext.cs
  78. 43 0
      src/Vit.Orm/DbSet.cs
  79. 31 0
      src/Vit.Orm/Entity/ColumnDescriptor.cs
  80. 64 0
      src/Vit.Orm/Entity/Dapper/EntityDescriptor.cs
  81. 13 0
      src/Vit.Orm/Entity/IColumnDescriptor.cs
  82. 19 0
      src/Vit.Orm/Entity/IEntityDescriptor.cs
  83. 191 0
      src/Vit.Orm/Sql/DataReader/EntityReader.cs
  84. 14 0
      src/Vit.Orm/Sql/DataReader/EntityReader/IArgReader.cs
  85. 35 0
      src/Vit.Orm/Sql/DataReader/EntityReader/ModelReader.EntityPropertyReader.cs
  86. 54 0
      src/Vit.Orm/Sql/DataReader/EntityReader/ModelReader.cs
  87. 41 0
      src/Vit.Orm/Sql/DataReader/EntityReader/SqlFieldReader.cs
  88. 51 0
      src/Vit.Orm/Sql/DataReader/EntityReader/TypeUtil.cs
  89. 25 0
      src/Vit.Orm/Sql/DataReader/EntityReader/ValueReader.cs
  90. 32 0
      src/Vit.Orm/Sql/DataReader/FirstEntityReader.cs
  91. 20 0
      src/Vit.Orm/Sql/DataReader/NumScalarReader.cs
  92. 9 0
      src/Vit.Orm/Sql/IDbDataReader.cs
  93. 70 0
      src/Vit.Orm/Sql/ISqlTranslator.cs
  94. 148 0
      src/Vit.Orm/Sql/SqlDbContext.cs
  95. 283 0
      src/Vit.Orm/Sql/SqlDbSet.cs
  96. 354 0
      src/Vit.Orm/Sql/Translator/BaseQueryTranslator.cs
  97. 130 0
      src/Vit.Orm/Sql/Translator/QueryTranslator.cs
  98. 327 0
      src/Vit.Orm/Sql/Translator/SqlTranslator.cs
  99. 19 0
      src/Vit.Orm/Vit.Orm.csproj

+ 77 - 0
.github/workflows/action-ki_multibranch.yml

@@ -0,0 +1,77 @@
+# This is a basic workflow to help you get started with Actions
+
+name: ki_multibranch_3.1
+
+# Controls when the action will run. 
+on:
+  
+  push:
+    # Triggers the workflow on push events for the branches start with release
+    branches:
+      - 'release/**'
+
+    # Triggers the workflow on push events but only for the master branch
+    #branches: [ master ]
+
+    # Triggers the workflow on push tag
+    #tags: ['*']
+    
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  build:
+    # The type of runner that the job will run on
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v2
+
+      # Runs a set of commands using the runners shell
+      - name: Run startup.bash
+        run: |
+           set -e
+           echo start build
+
+           export DOCKER_ImagePrefix="serset/"
+           export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME  }}"
+           export DOCKER_PASSWORD="${{ secrets.DOCKER_PASSWORD }}"
+           export NUGET_SERVER="${{ secrets.NUGET_SERVER  }}"
+           export NUGET_KEY="${{ secrets.NUGET_KEY }}"
+           export WebDav_BaseUrl="${{ secrets.WebDav_BaseUrl }}"
+           export WebDav_User="${{ secrets.WebDav_User }}"
+           cd ./Publish/DevOps3/github-bash
+           bash startup.bash
+           echo build succeed!
+           
+           # echo "appName=${APPNAME}" >> $GITHUB_ENV
+           
+           
+      - name: release_create
+        id: release_create
+        uses: actions/create-release@v1
+        #if: hashFiles(env.release_assetPath)
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          release_name: ${{ env.release_name }}
+          tag_name: ${{ env.release_tag }}
+          draft: ${{ env.release_draft }}
+          prerelease: ${{ env.release_prerelease }}
+          body: ${{ env.release_body }}
+
+
+      # release_upload_asset
+
+      - uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.release_create.outputs.upload_url }}
+          asset_path: ${{ env.release_dirPath }}/${{ env.appName }}-nuget-${{ env.release_version }}.zip
+          asset_name: ${{ env.appName }}-nuget-${{ env.release_version }}.zip
+          asset_content_type: application/zip

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 41 - 0
Publish/DevOps3/build-bash/19.get-app-version.bash

@@ -0,0 +1,41 @@
+set -e
+
+# export versionSuffix='.1234.preview'
+# bash 19.get-app-version.bash
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export versionSuffix='  '
+
+# "
+
+# remove spaces
+versionSuffix=${versionSuffix// /}
+
+#----------------------------------------------
+# basePath
+if [ -z "$basePath" ]; then basePath=$PWD/../../..; fi
+
+
+
+#----------------------------------------------
+echo "#1 get appVersion"
+# get csproj file with appVersion tag, if not exist get file with pack or publish tag
+csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \; | head -n 1);  fi
+if [ -f "$csprojPath" ]; then export appVersion=`grep '<Version>' "$csprojPath" | grep -oE '\>(.*)\<' | tr -d '<>/'`;  fi
+echo "appVersion from csproj: $appVersion"
+
+# get v1 v2 v3
+v1=$(echo $appVersion | tr '.' '\n' | sed -n 1p)
+v2=$(echo $appVersion | tr '.' '\n' | sed -n 2p)
+v3=$(echo $appVersion | tr '.-' '\n' | sed -n 3p)
+
+
+#export nextAppVersion="${appVersion%%-*}$versionSuffix"
+export nextAppVersion="$v1.$v2.$v3$versionSuffix"
+echo "nextAppVersion: $nextAppVersion"
+

+ 46 - 0
Publish/DevOps3/build-bash/20.change-app-version.bash

@@ -0,0 +1,46 @@
+set -e
+
+# export versionSuffix='.1234.preview'
+# bash 20.change-app-version.bash
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export versionSuffix='  '
+
+# "
+
+# remove spaces
+versionSuffix=${versionSuffix// /}
+
+#----------------------------------------------
+# basePath
+if [ -z "$basePath" ]; then basePath=$PWD/../../..; fi
+
+
+
+#----------------------------------------------
+echo "#1 get appVersion"
+# get csproj file with appVersion tag, if not exist get file with pack or publish tag
+csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \; | head -n 1);  fi
+if [ -f "$csprojPath" ]; then export appVersion=`grep '<Version>' "$csprojPath" | grep -oE '\>(.*)\<' | tr -d '<>/'`;  fi
+echo "appVersion from csproj: $appVersion"
+
+# get v1 v2 v3
+v1=$(echo $appVersion | tr '.' '\n' | sed -n 1p)
+v2=$(echo $appVersion | tr '.' '\n' | sed -n 2p)
+v3=$(echo $appVersion | tr '.-' '\n' | sed -n 3p)
+
+
+#export nextAppVersion="${appVersion%%-*}$versionSuffix"
+export nextAppVersion="$v1.$v2.$v3$versionSuffix"
+echo "nextAppVersion: $nextAppVersion"
+
+
+#----------------------------------------------
+echo "#2 change app version from [$appVersion] to [$nextAppVersion]" 
+sed -i 's/'"$appVersion"'/'"$nextAppVersion"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+

+ 47 - 0
Publish/DevOps3/build-bash/21.change-to-next-version.bash

@@ -0,0 +1,47 @@
+set -e
+
+# export versionSuffix='.1234.preview'
+# bash 21.change-to-next-version.bash
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export versionSuffix='  '
+
+# "
+
+# remove spaces
+versionSuffix=${versionSuffix// /}
+
+#----------------------------------------------
+# basePath
+if [ -z "$basePath" ]; then basePath=$PWD/../../..; fi
+
+
+
+#----------------------------------------------
+echo "#1 get appVersion"
+# get csproj file with appVersion tag, if not exist get file with pack or publish tag
+csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \; | head -n 1);  fi
+if [ -f "$csprojPath" ]; then export appVersion=`grep '<Version>' "$csprojPath" | grep -oE '\>(.*)\<' | tr -d '<>/'`;  fi
+echo "appVersion from csproj: $appVersion"
+
+# get v1 v2 v3
+v1=$(echo $appVersion | tr '.' '\n' | sed -n 1p)
+v2=$(echo $appVersion | tr '.' '\n' | sed -n 2p)
+v3=$(echo $appVersion | tr '.-' '\n' | sed -n 3p)
+((v3++));
+
+
+#export nextAppVersion="${appVersion%%-*}$versionSuffix"
+export nextAppVersion="$v1.$v2.$v3$versionSuffix"
+echo "nextAppVersion: $nextAppVersion"
+
+#----------------------------------------------
+echo "#2 change app version from [$appVersion] to [$nextAppVersion]" 
+sed -i 's/'"$appVersion"'/'"$nextAppVersion"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+
+

+ 41 - 0
Publish/DevOps3/build-bash/22.add-suffix-to-app-version.bash

@@ -0,0 +1,41 @@
+set -e
+
+# export versionSuffix='.1234.preview'
+# bash 22.add-suffix-to-app-version.bash
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export versionSuffix='  '
+
+# "
+
+# remove spaces
+versionSuffix=${versionSuffix// /}
+
+#----------------------------------------------
+# basePath
+if [ -z "$basePath" ]; then basePath=$PWD/../../..; fi
+
+
+
+#----------------------------------------------
+echo "#1 get appVersion"
+# get csproj file with appVersion tag, if not exist get file with pack or publish tag
+csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \; | head -n 1);  fi
+if [ -f "$csprojPath" ]; then export appVersion=`grep '<Version>' "$csprojPath" | grep -oE '\>(.*)\<' | tr -d '<>/'`;  fi
+echo "appVersion from csproj: $appVersion"
+
+
+
+export nextAppVersion="${appVersion}${versionSuffix}"
+echo "nextAppVersion: $nextAppVersion"
+
+
+#----------------------------------------------
+echo "#2 change app version from [$appVersion] to [$nextAppVersion]" 
+sed -i 's/'"$appVersion"'/'"$nextAppVersion"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+

+ 44 - 0
Publish/DevOps3/build-bash/30.nuget-pack.sh

@@ -0,0 +1,44 @@
+set -e
+
+# bash 30.nuget-pack.sh
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+export NUGET_PATH=$basePath/Publish/release/.nuget
+
+# "
+
+if [ ! $NUGET_PATH ]; then NUGET_PATH=$basePath/Publish/release/.nuget; fi
+
+
+nugetPath=Publish/release/release/nuget
+mkdir -p $basePath/Publish/release/release
+
+
+
+#----------------------------------------------
+echo "30.nuget-pack.sh"
+docker run -i --rm \
+--env LANG=C.UTF-8 \
+-v $NUGET_PATH:/root/.nuget \
+-v $basePath:/root/code \
+serset/dotnet:sdk-6.0 \
+bash -c "
+cd /root/code
+for file in \$(grep -a '<pack>nuget</pack>' . -rl --include *.csproj)
+do
+	echo pack \$file
+	mkdir -p /root/code/$nugetPath
+	cd /root/code
+	cd \$(dirname \"\$file\")
+	dotnet build --configuration Release
+	dotnet pack --configuration Release --output '/root/code/$nugetPath'
+done
+"
+
+
+ 

+ 93 - 0
Publish/DevOps3/build-bash/40.Station-publish.sh

@@ -0,0 +1,93 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+export NUGET_PATH=$basePath/Publish/release/.nuget
+
+# "
+
+if [ ! $NUGET_PATH ]; then NUGET_PATH=$basePath/Publish/release/.nuget; fi
+
+
+#----------------------------------------------
+echo "#40.Station-publish.sh -> find projects and build"
+
+export devOpsPath="$PWD/.."
+
+docker run -i --rm \
+--env LANG=C.UTF-8 \
+-v $NUGET_PATH:/root/.nuget \
+-v "$basePath":/root/code \
+-v "$basePath":"$basePath" \
+serset/dotnet:sdk-6.0 \
+bash -c "
+set -e
+
+if grep '<publish>' -r --include *.csproj /root/code; then
+	echo '#40.Station-publish.sh -> got projects need to be built'
+else
+	echo '#40.Station-publish.sh -> skip for no project needs to be built'
+	exit 0
+fi
+
+echo '#1 get netVersion'
+export netVersion=\$(grep '<TargetFramework>' \$(grep '<publish>' -rl --include *.csproj /root/code | head -n 1) | grep -oP '>(.*)<' | tr -d '<>')
+echo netVersion: \$netVersion
+
+
+export basePath=/root/code
+export publishPath=\$basePath/Publish/release/release/Station\(\$netVersion\)
+mkdir -p \$publishPath
+
+echo '#2 publish station'
+cd \$basePath
+for file in \$(grep -a '<publish>' . -rl --include *.csproj)
+do
+	cd \$basePath
+	
+	#get publishName
+	publishName=\`grep '<publish>' \$file -r | grep -oP '>(.*)<' | tr -d '<>'\`
+
+	echo publish \$publishName
+
+	#publish
+	cd \$(dirname \"\$file\")
+	dotnet build --configuration Release
+	dotnet publish --configuration Release --output \"\$publishPath/\$publishName\"
+
+	#copy xml
+	for filePath in bin/Release/\$netVersion/*.xml ; do \\cp -rf \$filePath \"\$publishPath/\$publishName\";done
+done
+
+
+#3 copy station release files
+if [ -d \"\$basePath/Publish/ReleaseFile/Station\" ]; then
+	echo '#3 copy station release files'
+	\cp -rf \$basePath/Publish/ReleaseFile/Station/. \"\$publishPath\"
+fi
+
+
+#4 copy extra release files
+bashFile=\"$devOpsPath/environment/build-bash__40.Station-publish__#4_copyExtraReleaseFiles.sh\"
+if [ -f \"\$bashFile\" ]; then
+	echo '#4 copy extra release files'
+	sh \"\$bashFile\"
+fi
+
+
+
+
+"
+
+
+
+echo '#40.Station-publish.sh -> success!'
+
+
+
+
+

+ 23 - 0
Publish/DevOps3/build-bash/41.extra-publish.sh

@@ -0,0 +1,23 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+export NUGET_PATH=$basePath/Publish/release/.nuget
+
+# "
+
+
+#---------------------------------------------------------------------
+#41.extra-publish.sh
+bashFile="$PWD/../environment/build-bash__41.extra-publish.sh"
+if [ -f "$bashFile" ]; then
+	echo '#41.extra-publish.sh'
+	sh "$bashFile"
+fi
+
+
+

+ 46 - 0
Publish/DevOps3/build-bash/50.docker-image-copy.sh

@@ -0,0 +1,46 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+# "
+
+ 
+#---------------------------------------------------------------------
+#1 copy docker image from ReleaseFile
+
+publishPath="$basePath/Publish/release/release/Station(net6.0)"
+dockerPath=$basePath/Publish/release/release/docker-image
+
+
+if [ -d "$basePath/Publish/ReleaseFile/docker-image" ]; then
+	echo "50.docker-image-copy.sh -> #1 copy docker image from ReleaseFile"
+	\cp -rf "$basePath/Publish/ReleaseFile/docker-image/." "$dockerPath"
+fi
+
+
+
+
+#---------------------------------------------------------------------
+echo "50.docker-image-copy.sh -> #2 copy station"
+for file in $(find $basePath -name *.csproj -exec grep '<docker>' -l {} \;)
+do
+	cd $basePath
+	
+	publishName=`grep '<publish>' $file -r | grep -oE '\>(.*)\<' | tr -d '<>/'`
+	
+	dockerName=`grep '<docker>' $file -r | grep -oE '\>(.*)\<' | tr -d '<>/'`
+
+	echo "#2.* copy $dockerName, publishName:$publishName"
+	\cp -rf "$publishPath/$publishName/." "$dockerPath/$dockerName/app"
+done
+
+
+
+
+
+

+ 27 - 0
Publish/DevOps3/build-bash/51.docker-deploy-copy.sh

@@ -0,0 +1,27 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+# "
+
+ 
+#---------------------------------------------------------------------
+#2
+publishPath="$basePath/Publish/release/release/Station(net6.0)"
+deployPath="$basePath/Publish/release/release/docker-deploy"
+
+
+
+#----------------------------------------------
+#3 copy dir
+if [ -d "$basePath/Publish/ReleaseFile/docker-deploy" ]; then
+	echo "51.docker-deploy-copy.sh -> copy dir"
+	\cp -rf "$basePath/Publish/ReleaseFile/docker-deploy/." "$deployPath"
+fi
+
+

+ 23 - 0
Publish/DevOps3/build-bash/52.docker-extra-copy.sh

@@ -0,0 +1,23 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+export NUGET_PATH=$basePath/Publish/release/.nuget
+
+# "
+
+
+#---------------------------------------------------------------------
+#52.docker-extra-copy.sh
+bashFile="$PWD/../environment/build-bash__52.docker-extra-copy.sh"
+if [ -f "$bashFile" ]; then
+	echo '#52.docker-extra-copy.sh'
+	sh "$bashFile"
+fi
+
+
+

+ 32 - 0
Publish/DevOps3/build-bash/startup.bash

@@ -0,0 +1,32 @@
+set -e
+
+# cd build-bash; bash startup.bash;
+
+#----------------------------------------------
+# cur path
+curPath=$PWD
+
+cd $curPath/../../..
+export basePath="$PWD"
+cd $curPath
+
+
+
+
+
+#----------------------------------------------
+echo "build-bash/startup.bash"
+
+for file in *.sh
+do
+    echo "-----------------------------------------------------------------"
+    echo "[$(date "+%H:%M:%S")] sh $file"
+    sh $file
+done
+
+
+
+ 
+#----------------------------------------------
+#
+cd $curPath

+ 23 - 0
Publish/DevOps3/build-cmd/30.nuget-pack.bat

@@ -0,0 +1,23 @@
+@echo off
+
+::1 get basePath
+set curPath=%cd%
+cd /d "%~dp0"
+cd /d ../../..
+set basePath=%cd%
+set nugetPath=%basePath%/Publish/release/release/nuget
+
+::2 find nuget projects and pack
+for /f "delims=" %%f in ('findstr /M /s /i "<pack>nuget</pack>" *.csproj') do (
+	echo pack %basePath%\%%f\..
+	cd /d "%basePath%\%%f\.."
+	dotnet build --configuration Release
+	dotnet pack --configuration Release --output "%nugetPath%"
+	@if errorlevel 1 (echo . & echo .  & echo error & pause) 
+)
+
+
+echo %~n0.bat success
+
+
+cd /d "%curPath%"

+ 31 - 0
Publish/DevOps3/build-cmd/40.Station-publish(net5.0).bat

@@ -0,0 +1,31 @@
+@echo off
+
+
+:: #1 init
+set netVersion=net5.0
+
+
+:: #2 change netcore version
+VsTool.exe replace -r --path "../../.." --file "App.Gateway.csproj|App.Gover.Gateway.csproj|App.ServiceCenter.csproj|Did.SersLoader.Demo.csproj|App.Robot.Station.csproj" --old "<TargetFramework>net6.0</TargetFramework>" --new "<TargetFramework>%netVersion%</TargetFramework>"
+
+
+
+
+
+:: #3 publish
+call "40.Station-publish.bat"
+
+
+
+
+
+
+:: #4 revert netcore version
+VsTool.exe replace -r --path "../../.." --file "App.Gateway.csproj|App.Gover.Gateway.csproj|App.ServiceCenter.csproj|Did.SersLoader.Demo.csproj|App.Robot.Station.csproj" --old "<TargetFramework>%netVersion%</TargetFramework>" --new "<TargetFramework>net6.0</TargetFramework>"
+
+
+
+
+
+echo %~n0.bat success
+cd /d "%curPath%"

+ 31 - 0
Publish/DevOps3/build-cmd/40.Station-publish(netcoreapp2.1).bat

@@ -0,0 +1,31 @@
+@echo off
+
+
+:: #1 init
+set netVersion=netcoreapp2.1
+
+
+:: #2 change netcore version
+VsTool.exe replace -r --path "../../.." --file "App.Gateway.csproj|App.Gover.Gateway.csproj|App.ServiceCenter.csproj|Did.SersLoader.Demo.csproj|App.Robot.Station.csproj" --old "<TargetFramework>net6.0</TargetFramework>" --new "<TargetFramework>%netVersion%</TargetFramework>"
+
+
+
+
+
+:: #3 publish
+call "40.Station-publish.bat"
+
+
+
+
+
+
+:: #4 revert netcore version
+VsTool.exe replace -r --path "../../.." --file "App.Gateway.csproj|App.Gover.Gateway.csproj|App.ServiceCenter.csproj|Did.SersLoader.Demo.csproj|App.Robot.Station.csproj" --old "<TargetFramework>%netVersion%</TargetFramework>" --new "<TargetFramework>net6.0</TargetFramework>"
+
+
+
+
+
+echo %~n0.bat success
+cd /d "%curPath%"

+ 57 - 0
Publish/DevOps3/build-cmd/40.Station-publish.bat

@@ -0,0 +1,57 @@
+@echo off
+
+::enable delayed arguments
+setlocal EnableDelayedExpansion
+
+
+:: #1 get basePath
+set curPath=%cd%
+cd /d "%~dp0"
+cd /d ../../..
+set basePath=%cd%
+
+
+:: #2 get netVersion
+set netVersion=net6.0
+for /f "tokens=3 delims=><" %%a in ('type %basePath%\dotnet\ServiceCenter\App.ServiceCenter\App.ServiceCenter.csproj^|findstr "<TargetFramework>.*TargetFramework"') do set netVersion=%%a
+
+
+set publishPath=%basePath%/Publish/release/release/Station(%netVersion%)
+echo publish Station
+echo dotnet version: %netVersion%
+
+
+
+:: #3 find projects and publish
+for /f "delims=" %%f in ('findstr /M /s /i "<publish>" *.csproj') do (
+	::get name
+	for /f "tokens=3 delims=><" %%a in ('type "%basePath%\%%f"^|findstr "<publish>.*publish"') do set name=%%a
+	echo publish !name!
+
+	::publish
+	cd /d "%basePath%\%%f\.."
+	dotnet build --configuration Release
+	dotnet publish --configuration Release --output "%publishPath%\!name!"
+	@if errorlevel 1 (echo . & echo .  & echo error & pause) 
+
+	::copy xml
+	xcopy  "bin\Release\%netVersion%\*.xml" "%publishPath%\!name!" /i /r /y
+)
+
+
+
+:: #4 copy dir
+xcopy "%basePath%\Publish\ReleaseFile\Station" "%publishPath%" /e /i /r /y
+
+
+
+:: #5 copy ServiceCenter
+xcopy "%publishPath%\ServiceCenter" "%basePath%\Publish\release\release\ServiceCenter(%netVersion%)\ServiceCenter" /e /i /r /y
+xcopy "%publishPath%\01.ServiceCenter.bat" "%basePath%\Publish\release\release\ServiceCenter(%netVersion%)"
+xcopy "%publishPath%\01.Start-4580.bat" "%basePath%\Publish\release\release\ServiceCenter(%netVersion%)"
+
+
+
+
+echo %~n0.bat success
+cd /d "%curPath%"

+ 97 - 0
Publish/DevOps3/build-cmd/41.StressTest-publish.bat

@@ -0,0 +1,97 @@
+@echo off
+
+::enable delayed arguments
+setlocal EnableDelayedExpansion
+
+
+
+:: #1 get 获取basePath
+set curPath=%cd%
+cd /d "%~dp0"
+cd /d ../../..
+set basePath=%cd%
+
+
+
+:: #2
+set publishPath=%basePath%/Publish/release/release/StressTest
+
+
+
+
+
+echo ------------------------------------------------------------------
+echo "#3 publish CL stressTest"
+
+::Client
+cd /d "%basePath%\dotnet\Library\Sers\Sers.CL\Test\CommunicationManage\CmClient"
+dotnet build --configuration Release
+dotnet publish --configuration Release --output "%publishPath%\CL压测net6.0\CmClient"
+@if errorlevel 1 (echo . & echo .  & echo error & pause) 
+
+::Server
+cd /d "%basePath%\dotnet\Library\Sers\Sers.CL\Test\CommunicationManage\CmServer"
+dotnet build --configuration Release
+dotnet publish --configuration Release --output "%publishPath%\CL压测net6.0\CmServer"
+@if errorlevel 1 (echo . & echo .  & echo error & pause) 
+
+
+::copy bat
+xcopy  "%basePath%\Publish\ReleaseFile\StressTest\CL压测" "%publishPath%\CL压测net6.0" /e /i /r /y
+
+
+
+
+echo ------------------------------------------------------------------
+:: #4 publish Sers stressTest
+for %%i in (net6.0) do (  
+	set netVersion=%%i
+	set appPath=%basePath%/Publish/release/release/Station^(!netVersion!^)
+
+	echo 发布 压测-!netVersion!
+
+	::单体压测
+	set targetPath=%publishPath%/单体压测!netVersion!
+
+	::(x.x.1)copy ServiceCenter
+	xcopy "!appPath!\ServiceCenter" "!targetPath!\ServiceCenter" /e /i /r /y
+
+	::(x.x.2)copy demo
+	xcopy "!appPath!\Demo\wwwroot" "!targetPath!\ServiceCenter\wwwroot" /e /i /r /y
+	xcopy "!appPath!\Demo\Did.SersLoader.Demo.dll" "!targetPath!\ServiceCenter" /i /r /y
+	xcopy "!appPath!\Demo\Did.SersLoader.Demo.pdb" "!targetPath!\ServiceCenter" /i /r /y
+	xcopy "!appPath!\Demo\Did.SersLoader.Demo.xml" "!targetPath!\ServiceCenter" /i /r /y
+
+	::(x.x.3)copy Robot
+	xcopy "!appPath!\Robot\wwwroot" "!targetPath!\ServiceCenter\wwwroot" /e /i /r /y
+	xcopy "!appPath!\Robot\App.Robot.Station.dll" "!targetPath!\ServiceCenter" /i /r /y
+	xcopy "!appPath!\Robot\App.Robot.Station.pdb" "!targetPath!\ServiceCenter" /i /r /y
+	xcopy "!appPath!\Robot\App.Robot.Station.xml" "!targetPath!\ServiceCenter" /i /r /y
+
+	::(x.x.4)copy ReleaseFile
+	xcopy "%basePath%\Publish\ReleaseFile\StressTest\单体压测" "!targetPath!" /e /i /r /y
+
+
+
+	::分布式压测
+	set targetPath=%publishPath%/分布式压测!netVersion!
+
+	::(x.x.1)copy  station
+	xcopy "!appPath!\ServiceCenter" "!targetPath!\ServiceCenter" /e /i /r /y
+	xcopy "!appPath!\Demo" "!targetPath!\Demo" /e /i /r /y
+	xcopy "!appPath!\Robot" "!targetPath!\Robot" /e /i /r /y
+
+	::(x.x.2)copy ReleaseFile
+	xcopy  "%basePath%\Publish\ReleaseFile\StressTest\分布式压测" "!targetPath!" /e /i /r /y
+)
+
+
+
+ 
+
+
+echo %~n0.bat success
+cd /d "%curPath%"
+
+
+

+ 36 - 0
Publish/DevOps3/build-cmd/50.docker-image-create.bat

@@ -0,0 +1,36 @@
+@echo off
+
+
+echo %~n0.bat start...
+
+
+:: #1 get basePath
+set curPath=%cd%
+cd /d "%~dp0"
+cd /d ../../..
+set basePath=%cd%
+
+:: #2
+set publishPath=%basePath%/Publish/release/release/Station(net6.0)
+set dockerPath=%basePath%/Publish/release/release/docker-image
+rd /s /q "%dockerPath%"
+
+
+:: #3 copy dir
+xcopy "%basePath%/Publish/ReleaseFile/docker-image" "%dockerPath%" /e /i /r /y
+
+
+:: #4 copy station 
+xcopy  "%publishPath%/ServiceCenter" "%dockerPath%/sers/app" /e /i /r /y
+xcopy  "%publishPath%/Gateway" "%dockerPath%/sers-gateway/app" /e /i /r /y
+xcopy  "%publishPath%/Gover" "%dockerPath%/sers-gover/app" /e /i /r /y
+xcopy  "%publishPath%/Demo" "%dockerPath%/sers-demo/app" /e /i /r /y
+xcopy  "%publishPath%/Robot" "%dockerPath%/sers-demo-robot/app" /e /i /r /y
+xcopy  "%basePath%/Publish/release/release/压测/单体压测net6.0/ServiceCenter" "%dockerPath%/sers-demo-sersall/app" /e /i /r /y
+ 
+
+
+
+
+echo %~n0.bat success
+cd /d "%curPath%"

+ 36 - 0
Publish/DevOps3/build-cmd/51.docker-deploy-copy.bat

@@ -0,0 +1,36 @@
+@echo off
+
+
+echo %~n0.bat start...
+
+
+::#1 get basePath
+set curPath=%cd%
+cd /d "%~dp0"
+cd /d ..\..\..
+set basePath=%cd%
+
+
+:: #2
+set publishPath=%basePath%\Publish\release\release\Station(net6.0)
+set dockerPath=%basePath%\Publish\release\release\docker-deploy
+rd /s /q "%dockerPath%"
+
+
+:: #3 copy dir
+xcopy "%basePath%\Publish\ReleaseFile\docker-deploy" "%dockerPath%" /e /i /r /y
+
+
+:: #4 copy station 
+xcopy "%publishPath%\ServiceCenter\appsettings.json" "%dockerPath%\sers"
+xcopy "%publishPath%\Gateway\appsettings.json" "%dockerPath%\sers-gateway"
+xcopy "%publishPath%\Gover\appsettings.json" "%dockerPath%\sers-gover"
+xcopy "%publishPath%\Demo\appsettings.json" "%dockerPath%\sers-demo"
+xcopy "%publishPath%\Robot\appsettings.json" "%dockerPath%\sers-demo-robot"
+xcopy "%basePath%\Publish\release\release\压测\单体压测net6.0\ServiceCenter\appsettings.json" "%dockerPath%\sers-demo-sersall"
+
+
+
+
+echo %~n0.bat success
+cd /d "%curPath%"

+ 9 - 0
Publish/DevOps3/build-cmd/Clean-All.bat

@@ -0,0 +1,9 @@
+VsTool.exe delete --path "..\..\.." --file "*.suo|*.user" --directory "obj|bin|.vs|packages"
+
+
+rd /s/q ..\..\release
+
+
+echo %~n0.bat success
+
+pause

+ 1 - 0
Publish/DevOps3/build-cmd/Clean-ReleaseFiles.bat

@@ -0,0 +1 @@
+rd /s/q ..\..\release

+ 21 - 0
Publish/DevOps3/build-cmd/OneKey Release.bat

@@ -0,0 +1,21 @@
+
+echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    
+call "30.nuget-pack.bat"
+
+echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    
+call "40.Station-publish.bat"
+call "40.Station-publish(netcoreapp2.1).bat"
+call "40.Station-publish(net6.0).bat"
+
+call "41.StressTest-publish.bat"
+
+echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    
+call "50.docker-image-create.bat"
+call "51.docker-deploy-copy.bat"
+
+echo %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%    
+
+
+echo %~n0.bat success
+
+pause

+ 46 - 0
Publish/DevOps3/build-cmd/Version-To-NextTemp.bat

@@ -0,0 +1,46 @@
+@echo off
+
+:: #1 get csproj
+for /f "delims=" %%a in ('findstr /M /s /i /r "<pack> <publish>" "..\..\..\*.csproj"') do set "csproj=%%~a"
+::echo %csproj%
+
+:: #2 get version
+for /f "tokens=3 delims=><" %%a in ('type %csproj%^|findstr "<Version>.*Version"') do set version=%%a
+
+:: set version=2.1.3
+:: echo  %version%
+
+
+:: #3 get v1 v2 v3
+for /f "tokens=1 delims=-" %%i in ("%version%") do set numVersion=%%i
+
+:: v1 v2 v3
+for /f "tokens=1 delims=." %%i in ("%numVersion%") do set v1=%%i
+for /f "tokens=2 delims=." %%i in ("%numVersion%") do set v2=%%i
+for /f "tokens=3 delims=." %%i in ("%numVersion%") do set v3=%%i
+
+
+:: #4 newVersion
+set /a v3=1+%v3%
+set newVersion=%v1%.%v2%.%v3%-temp
+:: echo %newVersion%
+
+
+ 
+:: #5 replace version in csproj
+echo replace verion [%version%]-^>[%newVersion%]
+echo.
+
+VsTool.exe replace -r --path "..\..\.." --file "*.csproj" --old "<Version>%version%</Version>" --new "<Version>%newVersion%</Version>"
+VsTool.exe replace -r --path "..\..\.." --file "packages.config" --old "%version%" --new "%newVersion%"
+
+
+:: #6 replace version in docker image file
+VsTool.exe replace -r --path "..\..\..\Publish\ReleaseFile\docker-image" --file "*.md" --old "%version%" --new "%newVersion%"
+
+
+echo.
+echo.
+echo.
+echo replace version success [%version%]-^>[%newVersion%]
+pause

+ 46 - 0
Publish/DevOps3/build-cmd/Version-To-Preview.bat

@@ -0,0 +1,46 @@
+@echo off
+
+:: #1 get csproj
+for /f "delims=" %%a in ('findstr /M /s /i /r "<pack> <publish>" "..\..\..\*.csproj"') do set "csproj=%%~a"
+::echo %csproj%
+
+:: #2 get version
+for /f "tokens=3 delims=><" %%a in ('type %csproj%^|findstr "<Version>.*Version"') do set version=%%a
+
+:: set version=2.1.3
+:: echo  %version%
+
+
+:: #3 get v1 v2 v3
+for /f "tokens=1 delims=-" %%i in ("%version%") do set numVersion=%%i
+
+:: v1 v2 v3
+for /f "tokens=1 delims=." %%i in ("%numVersion%") do set v1=%%i
+for /f "tokens=2 delims=." %%i in ("%numVersion%") do set v2=%%i
+for /f "tokens=3 delims=." %%i in ("%numVersion%") do set v3=%%i
+
+
+:: #4 newVersion
+:: set /a v3=1+%v3%
+set newVersion=%v1%.%v2%.%v3%-preview
+:: echo %newVersion%
+
+
+ 
+:: #5 replace version in csproj
+echo ×Ô¶¯Ð޸İ汾ºÅ [%version%]-^>[%newVersion%]
+echo.
+
+VsTool.exe replace -r --path "..\..\.." --file "*.csproj" --old "<Version>%version%</Version>" --new "<Version>%newVersion%</Version>"
+VsTool.exe replace -r --path "..\..\.." --file "packages.config" --old "%version%" --new "%newVersion%"
+
+
+:: #6 replace version in docker image file
+VsTool.exe replace -r --path "..\..\..\Publish\ReleaseFile\docker-image" --file "*.md" --old "%version%" --new "%newVersion%"
+
+
+echo.
+echo.
+echo.
+echo replace version success [%version%]-^>[%newVersion%]
+pause

+ 43 - 0
Publish/DevOps3/build-cmd/Version-To-Release.bat

@@ -0,0 +1,43 @@
+@echo off
+
+:: #1 get csproj
+for /f "delims=" %%a in ('findstr /M /s /i /r "<pack> <publish>" "..\..\..\*.csproj"') do set "csproj=%%~a"
+::echo %csproj%
+
+:: #2 get version
+for /f "tokens=3 delims=><" %%a in ('type %csproj%^|findstr "<Version>.*Version"') do set version=%%a
+
+:: set version=2.1.3
+:: echo  %version%
+
+
+:: #3 get v1 v2 v3
+for /f "tokens=1 delims=-" %%i in ("%version%") do set numVersion=%%i
+
+:: v1 v2 v3
+for /f "tokens=1 delims=." %%i in ("%numVersion%") do set v1=%%i
+for /f "tokens=2 delims=." %%i in ("%numVersion%") do set v2=%%i
+for /f "tokens=3 delims=." %%i in ("%numVersion%") do set v3=%%i
+
+
+:: #4 newVersion
+:: set /a v3=1+%v3%
+set newVersion=%v1%.%v2%.%v3%
+:: echo %newVersion%
+echo [%version%]-^>[%newVersion%]
+
+ 
+:: #5 replace version in csproj
+VsTool.exe replace -r --path "..\..\.." --file "*.csproj" --old "<Version>%version%</Version>" --new "<Version>%newVersion%</Version>"
+VsTool.exe replace -r --path "..\..\.." --file "packages.config" --old "%version%" --new "%newVersion%"
+
+
+:: #6 replace version in docker image file
+VsTool.exe replace -r --path "..\..\..\Publish\ReleaseFile\docker-image" --file "*.md" --old "%version%" --new "%newVersion%"
+
+
+echo.
+echo.
+echo.
+echo replace version success [%version%]-^>[%newVersion%]
+pause

+ 32 - 0
Publish/DevOps3/build-cmd/Version-getVersion.bat

@@ -0,0 +1,32 @@
+@echo off
+
+:: get current version
+
+
+:: #1 get csproj
+for /f "delims=" %%a in ('findstr /M /s /i /r "<pack> <publish>" "..\..\..\*.csproj"') do set "csproj=%%~a"
+echo %csproj%
+
+:: #2 get version
+for /f "tokens=3 delims=><" %%a in ('type %csproj%^|findstr "<Version>.*Version"') do set version=%%a
+
+:: set version=2.1.3
+:: echo  %version%
+
+
+:: #3 get v1 v2 v3
+for /f "tokens=1 delims=-" %%i in ("%version%") do set numVersion=%%i
+
+:: v1 v2 v3
+for /f "tokens=1 delims=." %%i in ("%numVersion%") do set v1=%%i
+for /f "tokens=2 delims=." %%i in ("%numVersion%") do set v2=%%i
+for /f "tokens=3 delims=." %%i in ("%numVersion%") do set v3=%%i
+
+
+:: #4 newVersion
+::set /a v3=1+%v3%
+set  newVersion=%v1%.%v2%.%v3%-preview
+echo [%version%]-^>[%newVersion%]
+
+
+pause

BIN
Publish/DevOps3/build-cmd/VsTool.exe


+ 1 - 0
Publish/DevOps3/environment/env.APPNAME.txt

@@ -0,0 +1 @@
+Vit.Linq

+ 1 - 0
Publish/DevOps3/environment/env.envName.txt

@@ -0,0 +1 @@
+ki

+ 13 - 0
Publish/DevOps3/environment/readme.md

@@ -0,0 +1,13 @@
+
+# DevOps 3.1
+> 2024-04-27
+
+# build-bash
+extra steps when building, all sh files could be remove if not needed.
+
+
+
+
+
+
+

+ 30 - 0
Publish/DevOps3/github-bash/74.github-push-to-webdav.sh

@@ -0,0 +1,30 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export APPNAME=xxxx
+export appVersion=xxxx
+
+export WebDav_BaseUrl="https://nextcloud.xxx.com/remote.php/dav/files/release/releaseFiles/ki_jenkins"
+export WebDav_User="username:pwd"
+
+# "
+
+
+
+
+
+#----------------------------------------------
+if [ -z "$WebDav_BaseUrl" ]; then
+	echo "github skip pushing release file to WebDav because invalid WebDav endpoint"
+else
+	echo "github push release file to WebDav"
+	bash "$PWD/../release-bash/78.push-releaseFiles-to-webdav.bash";
+fi
+
+

+ 60 - 0
Publish/DevOps3/github-bash/76.github-push-release.sh

@@ -0,0 +1,60 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export appVersion=1.0-preview
+
+export APPNAME=xxxxxx
+
+# "
+
+
+ 
+
+
+#---------------------------------------------------------------------
+#2 init environment for github release
+
+
+
+echo "appName=${APPNAME}" >> $GITHUB_ENV
+
+echo "release_name=${APPNAME}-${appVersion}" >> $GITHUB_ENV
+echo "release_tag=${appVersion}" >> $GITHUB_ENV
+
+echo "release_draft=false" >> $GITHUB_ENV
+echo "release_prerelease=false" >> $GITHUB_ENV
+
+echo "release_body=${APPNAME} ${appVersion}" >> $GITHUB_ENV
+
+
+echo "release_dirPath=${basePath}/Publish/release/release-zip" >> $GITHUB_ENV
+echo "release_version=${appVersion}" >> $GITHUB_ENV
+
+#filePath=$basePath/Publish/release/release-zip/Sers-ServiceCenter(net6.0)-${appVersion}.zip
+#fileType="${filePath##*.}"
+#echo "release_assetPath=${filePath}" >> $GITHUB_ENV
+#echo "release_assetName=${APPNAME}-${appVersion}.${fileType}" >> $GITHUB_ENV
+#echo "release_contentType=application/zip" >> $GITHUB_ENV
+
+
+# draft or preivew
+if [ "preview" = "$(echo $appVersion | tr -d \"0-9\-\\.\")" ]
+then
+  echo preivew
+  echo "release_prerelease=true" >> $GITHUB_ENV
+else
+  if  [ "" = "$(echo $appVersion | tr -d \"0-9\-\\.\")" ]
+  then
+    echo release
+  else
+    echo draft
+    echo "release_draft=true" >> $GITHUB_ENV
+  fi
+fi
+

+ 70 - 0
Publish/DevOps3/github-bash/startup.bash

@@ -0,0 +1,70 @@
+set -e
+
+# cd github-bash; bash startup.bash;
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+#export APPNAME=xxxxxx
+
+export DOCKER_USERNAME=serset
+export DOCKER_PASSWORD=xxxxxx
+
+export NUGET_SERVER=https://api.nuget.org/v3/index.json
+export NUGET_KEY=xxxxxx
+
+export WebDav_BaseUrl="https://nextcloud.xxx.com/remote.php/dav/files/release/releaseFiles/ki_jenkins"
+export WebDav_User="username:pwd"
+
+# "
+
+#----------------------------------------------
+# cur path
+curPath=$PWD
+
+cd $curPath/../../..
+export basePath="$PWD"
+cd $curPath
+
+export devOpsPath="$PWD/.."
+
+# export basePath=/root/temp/svn
+
+if [ ! $APPNAME ]; then 
+	export APPNAME=$(cat "$devOpsPath/environment/env.APPNAME.txt" | tr -d '\n')
+	echo "APPNAME: [${APPNAME}]" 
+fi
+
+#---------------------------------------------- 
+echo '#1 build'
+cd "$devOpsPath/build-bash"; bash startup.bash;
+
+#---------------------------------------------- 
+echo '#2 release-bash'
+cd "$devOpsPath/release-bash"; bash startup.bash;
+ 
+
+
+#----------------------------------------------
+echo "#3 get appVersion" 
+cd "$devOpsPath/build-bash"; source 19.get-app-version.bash;
+
+
+
+#----------------------------------------------
+echo "#4 bash"
+cd $curPath
+for file in *.sh
+do
+    echo "-----------------------------------------------------------------"
+    echo "[$(date "+%H:%M:%S")]" sh $file
+    sh $file
+done
+
+
+
+
+
+
+

+ 168 - 0
Publish/DevOps3/jenkins-bash/CICD.ki.git_Multibranch.deploy.jenkinsfile

@@ -0,0 +1,168 @@
+def remote = [:]
+remote.name = "dind-ssh"
+remote.host = "dind"
+remote.port = 22
+remote.user = "  "
+remote.password = "  "
+remote.allowAnyHosts = true
+
+pipeline {
+    agent any
+
+    environment {
+        // get APPNAME
+        // APPNAME = "Sers"
+        APPNAME = readFile("Publish/DevOps3/environment/env.APPNAME.txt")
+
+        envName = readFile("Publish/DevOps3/environment/env.envName.txt")
+        versionSuffix = "-${envName}${env.build_number}"
+
+        //basePath = "/root/docker-cache/jenkins/jenkins_home/workspace/${APPNAME}/${envName}/${env.BRANCH_NAME}/${env.build_number}"
+        basePath = "${env.WORKSPACE}"
+
+        NUGET_PATH = "/root/docker-cache/jenkins/jenkins_home/workspace/.nuget"
+
+        NUGET_SERVER = "http://nuget.lith.cloud:8"
+        //NUGET_KEY = " "
+        NUGET_KEY = credentials("nuget_key")
+
+        DOCKER_ImagePrefix = "docker.lith.cloud:8/${envName}/"
+        DOCKER_Buildx = false
+        DOCKER_USERNAME = " "
+        DOCKER_PASSWORD = " "
+
+        // set to "  "  if want to skip save releaseFiles to WebDav
+        WebDav_BaseUrl = "https://pan.lith.cloud:4/remote.php/dav/files/release/releaseFiles/ki_jenkins"
+        // "username:pwd"
+        WebDav_User = credentials("WebDav_User")
+
+        build_crossPlatform = "no"
+
+        dind_ssh_account = credentials("dind_ssh_account")
+    }
+
+    stages {
+        stage('#1 deploy ?') {
+            steps {
+                timeout(time:600,unit:'SECONDS') {
+                    script {
+                        remote.user = "${dind_ssh_account_USR}"
+                        remote.password = "${dind_ssh_account_PSW}"
+
+                        env.codePath = "/root/docker-cache/jenkins/" + basePath.substring(5, basePath.length()) 
+
+                        echo "-------- APPNAME: [$APPNAME]"
+                        echo "-------- basePath: [$basePath]"
+                        echo "-------- DOCKER_ImagePrefix: [$DOCKER_ImagePrefix]"
+                        echo "-------- codePath: [$codePath]"
+
+                        env.inputChoice = "no"
+                        env.inputChoice = input message: "deploy ?", 
+                            ok: 'Proceed?', 
+                            parameters: [choice(choices:["yes","no"], description: 'if not please select no', name: 'choice')]
+                    }
+                }
+            }
+        }
+
+        stage('#2 change version') {
+            when { expression { env.inputChoice == "yes" } }
+            steps {
+                script {
+                    echo "#2.1 change-app-version"
+                    sshCommand remote: remote, command:  "sh -c 'set -e; export versionSuffix=$versionSuffix;    cd $codePath/Publish/DevOps3/build-bash; source 22.add-suffix-to-app-version.bash;    echo -n \"\$nextAppVersion\" > $codePath/Publish/DevOps3/environment/env.appVersion.txt '"
+
+                    echo "#2.2 get app version"
+                    env.appVersion = readFile("Publish/DevOps3/environment/env.appVersion.txt")
+                    echo "-------- appVersion: [${env.appVersion}]"
+                }
+            }
+        }
+
+        stage('#3.1 build - single platflorm') {
+            when { expression { env.inputChoice == "yes" } }
+            steps {
+                script {
+                    sshCommand remote: remote, command:  "sh -c 'set -e; export APPNAME=$APPNAME; export NUGET_PATH=$NUGET_PATH;    cd $codePath/Publish/DevOps3/build-bash; sh startup.bash;  '"
+                }
+            }
+        }
+
+        stage('#3.2 build - cross platform') {
+            when { expression { env.inputChoice == "yes" && env.build_crossPlatform == "yes" } }
+            steps {
+                script {
+                    sshCommand remote: remote, command:  "sh -c 'set -e; export APPNAME=$APPNAME; export NUGET_PATH=$NUGET_PATH;    cd $codePath/Publish/DevOps3/build-bash; sh 40.Station-publish-multiple.bash;  '"
+                }
+            }
+        }
+
+        stage('#4 publish') {
+            when { expression { env.inputChoice == "yes" } }
+            steps {
+                script {
+                    sshCommand remote: remote, command:  "sh -c 'set -e; export APPNAME=$APPNAME;export NUGET_PATH=$NUGET_PATH;    export NUGET_SERVER=$NUGET_SERVER;export NUGET_KEY=$NUGET_KEY;  export DOCKER_Buildx=${env.DOCKER_Buildx};export DOCKER_ImagePrefix=${env.DOCKER_ImagePrefix};export DOCKER_USERNAME=${env.DOCKER_USERNAME};export DOCKER_PASSWORD=${env.DOCKER_PASSWORD};export DOCKER_BuildxExtArgs=\"--output=type=registry,registry.insecure=true\";    cd $codePath/Publish/DevOps3/release-bash; sh startup.bash;  '"
+                }
+            }
+        }
+
+        stage('#5 save releaseFiles') {
+            when { expression { env.inputChoice == "yes" && env.WebDav_BaseUrl != "  " } }
+            steps {
+                script {
+                    sshCommand remote: remote, command:  "sh -c 'set -e; export basePath=\"$codePath\"; export APPNAME=$APPNAME; export appVersion=\"$appVersion\";   export WebDav_BaseUrl=\"$WebDav_BaseUrl\"; export WebDav_User=\"$WebDav_User\";    cd $codePath/Publish/DevOps3/release-bash; sh 78.push-releaseFiles-to-webdav.bash;  '"
+                }
+            }
+        }
+
+        stage('#6 deploy') {
+            when { expression { env.inputChoice == "yes" && env.build_deploy == "yes" } }
+            steps {
+                script {
+
+                    remote.name = "k8s-ssh"
+                    remote.host = "k8s.lith.cloud"
+
+                    remote.user = "${k8s_ssh_account_USR}"
+                    remote.password = "${k8s_ssh_account_PSW}"
+
+                    sshCommand remote: remote, command:  "sh -c 'set -e;  cd /home/DataStore/Local/Data/2000/200.flowtea-prod/helm;    helm upgrade flowtea --set appNum=200 --set debug=true --set resources.limits=false --set image.host=\"\" --set image.tag=\"${env.appVersion}\" --set storageClass.ssd=nfs-prod-ssd --set storageClass.hdd=nfs-prod-hdd ./flowtea -n flowtea;  '"
+                }
+            }
+        }
+
+    }
+
+    post {
+        always {
+            timeout(time:3600,unit:'SECONDS') {
+                script {
+                    env.inputChoice = "yes"
+                    env.inputChoice = input message: "Clean temp files, \n will wait for 3600 seconds. \n click abort to skip clean.", 
+                            ok: 'Proceed', 
+                            parameters: [choice(choices:["yes","no"], name: 'choice')]
+               }
+            }
+            script {
+                if ( inputChoice == "yes" ) {
+                    echo "clean up workspace directory"
+                    cleanWs()
+
+                    // clean up tmp directory
+                    dir("${workspace}@tmp") {
+                        deleteDir()
+                    }
+                }
+            }
+        }
+        success {
+            echo "build success !"
+        }
+        failure {
+            echo "build failure !"
+        }
+        aborted {
+            echo "build aborted !"
+        }
+    }
+}

+ 43 - 0
Publish/DevOps3/release-bash/71.file-zip.sh

@@ -0,0 +1,43 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export appVersion=1.0
+
+export APPNAME=xxxxxx
+
+# "
+
+#----------------------------------------------
+echo "71.file-zip.sh"
+
+if [ ! -d "$basePath/Publish/release/release" ]; then
+    echo '71.file-zip.sh -> skip for no files exist'
+    exit 0
+fi
+
+docker run --rm -i \
+-v $basePath:/root/code \
+serset/filezip bash -c "
+set -e
+
+releasePath=/root/code/Publish/release
+rm -rf \$releasePath/release-zip
+
+for dirname in \`ls /root/code/Publish/release/release\`
+do
+  if [ -d \$releasePath/release/\$dirname ]
+  then
+    filezip zip -p -i \$releasePath/release/\$dirname -o \$releasePath/release-zip/${APPNAME}-\${dirname}-${appVersion}.zip 
+  fi
+done
+
+echo zip files:
+ls /root/code/Publish/release/release-zip
+
+"

+ 38 - 0
Publish/DevOps3/release-bash/72.nuget-push.sh

@@ -0,0 +1,38 @@
+set -e
+
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export NUGET_SERVER=https://api.nuget.org/v3/index.json
+export NUGET_KEY=xxxxxxxxxx
+
+# "
+
+#----------------------------------------------
+echo "72.nuget-push.sh"
+
+if [ ! -d "$basePath/Publish/release/release/nuget" ]; then
+    echo '71.file-zip.sh -> skip for no nuget files exist'
+    exit 0
+fi
+
+
+docker run -i --rm \
+--env LANG=C.UTF-8 \
+-v $basePath:/root/code \
+serset/dotnet:sdk-6.0 \
+bash -c "
+for file in /root/code/Publish/release/release/nuget/*.nupkg
+do
+    echo nuget push \$file
+    dotnet nuget push \$file -k ${NUGET_KEY} -s ${NUGET_SERVER} --skip-duplicate
+done
+" || true
+
+
+ 

+ 39 - 0
Publish/DevOps3/release-bash/73.docker-image-build-push.sh

@@ -0,0 +1,39 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export appVersion=1.0
+
+export DOCKER_ImagePrefix=serset/
+export DOCKER_USERNAME=serset
+export DOCKER_PASSWORD=xxx
+
+export DOCKER_Buildx=false #default: true
+export DOCKER_BuildxExtArgs=
+
+# "
+
+
+
+
+#---------------------------------------------------------------------
+echo "73.docker-image-build-push.sh"
+
+if [ ! -d "$basePath/Publish/release/release/docker-image" ]; then
+    echo '73.docker-image-build-push.sh -> skip for no docker image files exist'
+    exit 0
+fi
+
+if [ "$DOCKER_Buildx" != "false" ]; then
+    sh 75.docker-image-build-push_cross.bash
+else
+    sh 74.docker-image-build-push_amd64.bash
+fi
+
+
+

+ 41 - 0
Publish/DevOps3/release-bash/74.docker-image-build-push_amd64.bash

@@ -0,0 +1,41 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export appVersion=1.0
+
+export DOCKER_ImagePrefix=serset/
+export DOCKER_USERNAME=serset
+export DOCKER_PASSWORD=xxx
+export DOCKER_BuildxExtArgs=
+
+# "
+
+
+#---------------------------------------------------------------------
+echo "74.docker-image-build-push_amd64.bash"
+
+echo "#1 login if UserName is not empty"
+if [ -n "$DOCKER_USERNAME" ]; then docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD; fi
+
+dockerPath=$basePath/Publish/release/release/docker-image
+
+for dockerName in `ls $dockerPath`
+do
+  if [ -d $dockerPath/$dockerName ]
+  then
+    echo "#2.* docker build $dockerName"
+    echo "docker build $dockerPath/$dockerName -t ${DOCKER_ImagePrefix}$dockerName:$appVersion -t ${DOCKER_ImagePrefix}$dockerName"
+    docker build $dockerPath/$dockerName -t ${DOCKER_ImagePrefix}$dockerName:$appVersion -t ${DOCKER_ImagePrefix}$dockerName
+    docker push ${DOCKER_ImagePrefix}$dockerName:$appVersion
+    docker push ${DOCKER_ImagePrefix}$dockerName
+    docker rmi -f ${DOCKER_ImagePrefix}$dockerName:$appVersion ${DOCKER_ImagePrefix}$dockerName
+  fi
+done
+
+

+ 71 - 0
Publish/DevOps3/release-bash/75.docker-image-build-push_cross.bash

@@ -0,0 +1,71 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export appVersion=1.0
+
+export DOCKER_ImagePrefix=serset/
+export DOCKER_USERNAME=serset
+export DOCKER_PASSWORD=xxx
+export DOCKER_BuildxExtArgs=
+
+# "
+
+
+
+
+#---------------------------------------------------------------------
+echo "75.docker-image-build-push_cross.bash -> #1 docker - init buildx"
+
+
+export builderName="mybuilder__${appVersion}__"
+echo "builderName: $builderName"
+
+
+echo "#1.1 docker buildx version"
+docker buildx version
+
+echo "#1.2 install binfmt_misc"
+docker run --privileged --rm tonistiigi/binfmt --install all
+
+echo "#1.3 create builder"
+if [ ! "$(docker buildx ls | grep $builderName)" ]; then docker buildx create --use --name $builderName --buildkitd-flags '--allow-insecure-entitlement security.insecure'; fi
+
+echo "#1.4 start builder"
+docker buildx inspect $builderName --bootstrap
+
+echo "#1.5 show builders and supported CPU platform"
+docker buildx ls
+
+
+
+#---------------------------------------------------------------------
+echo "75.docker-image-build-push_cross.bash -> #2 docker - build and push"
+
+echo "#2.1 login if UserName is not empty"
+if [ -n "$DOCKER_USERNAME" ]; then docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD; fi
+
+dockerPath=$basePath/Publish/release/release/docker-image
+
+for dockerName in `ls $dockerPath`
+do
+  if [ -d $dockerPath/$dockerName ]
+  then
+    platform="linux/amd64,linux/arm64,linux/arm/v7"
+    if [ -f "$dockerPath/$dockerName/Dockerfile.platform" ]; then platform=`cat "$dockerPath/$dockerName/Dockerfile.platform"`; fi
+
+    echo "#2.* docker build $dockerName, platform: $platform"
+    echo "docker buildx build --allow security.insecure $dockerPath/$dockerName -t ${DOCKER_ImagePrefix}$dockerName:$appVersion -t ${DOCKER_ImagePrefix}$dockerName --platform=$platform --push $DOCKER_BuildxExtArgs --builder $builderName"
+    docker buildx build --allow security.insecure $dockerPath/$dockerName -t ${DOCKER_ImagePrefix}$dockerName:$appVersion -t ${DOCKER_ImagePrefix}$dockerName --platform=$platform --push $DOCKER_BuildxExtArgs --builder $builderName
+  fi
+done
+
+
+#---------------------------------------------------------------------
+echo "75.docker-image-build-push_cross.bash -> #3 remove builder"
+if [ "$(docker buildx ls | grep $builderName)" ]; then docker buildx rm $builderName; fi

+ 47 - 0
Publish/DevOps3/release-bash/78.push-releaseFiles-to-webdav.bash

@@ -0,0 +1,47 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+
+export APPNAME=Vit.Library
+export appVersion=2.2.14
+
+export WebDav_BaseUrl="https://nextcloud.xxx.com/remote.php/dav/files/release/releaseFiles/ki_jenkins"
+export WebDav_User="username:pwd"
+
+# "
+
+
+# will not throw errors if WebDav service is not available
+
+
+#---------------------------------------------------------------------
+echo "78.push-releaseFiles-to-webdav.bash -> #1 create dir"
+
+docker run -i --rm curlimages/curl sh -c "curl -X MKCOL -u \"$WebDav_User\" \"$WebDav_BaseUrl/$APPNAME\" " || true
+docker run -i --rm curlimages/curl sh -c "curl -X MKCOL -u \"$WebDav_User\" \"$WebDav_BaseUrl/$APPNAME/$appVersion\" " || true
+
+#---------------------------------------------------------------------
+echo "78.push-releaseFiles-to-webdav.bash -> #2 push release files"
+
+docker run -i --rm \
+-v "$basePath/Publish/release/release-zip":/releaseFiles \
+curlimages/curl \
+sh -c "
+cd /releaseFiles
+for file in /releaseFiles/*
+do
+    echo ''
+    echo '----------------------------'
+    fileName=\"\${file##*/}\"
+    echo push file: \$fileName
+    curl -X PUT -u \"$WebDav_User\" -T "/releaseFiles/\$fileName" \"$WebDav_BaseUrl/$APPNAME/$appVersion/\$fileName\"
+done
+" || true
+
+#---------------------------------------------------------------------
+echo "78.push-releaseFiles-to-webdav.bash -> #3 success"

+ 57 - 0
Publish/DevOps3/release-bash/startup.bash

@@ -0,0 +1,57 @@
+set -e
+
+# cd /root/temp/svn/Publish/DevOps/release-bash;bash startup.bash;
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export APPNAME=xxxxxx
+
+export DOCKER_ImagePrefix=serset/
+export DOCKER_USERNAME=serset
+export DOCKER_PASSWORD=xxx
+
+export NUGET_SERVER=https://api.nuget.org/v3/index.json
+export NUGET_KEY=xxxxxxxxxx
+# "
+
+
+
+#----------------------------------------------
+# cur path
+curPath="$PWD"
+
+cd "$curPath/../../.."
+export basePath="$PWD"
+cd "$curPath"
+
+# export basePath=/root/temp/svn
+
+
+
+#----------------------------------------------
+echo "#1 get appVersion"
+cd "$curPath/../build-bash"; source 19.get-app-version.bash;
+
+
+
+
+
+
+#----------------------------------------------
+echo "#2 bash"
+cd $curPath
+for file in *.sh
+do
+    echo "-----------------------------------------------------------------"
+    echo "[$(date "+%H:%M:%S")] sh $file"
+    sh "$file"
+done
+
+
+cd "$curPath"
+
+
+
+

+ 77 - 0
README.md

@@ -0,0 +1,77 @@
+
+# Vit.Orm
+Compatible with QueryBuilder ( ref to DynamicQueryable )
+>source address: [https://github.com/serset/Vit.Orm](https://github.com/serset/Vit.Orm "https://github.com/serset/Vit.Orm")    
+
+![](https://img.shields.io/github/license/Serset/Vit.Orm.svg)  
+![](https://img.shields.io/github/repo-size/Serset/Vit.Orm.svg)  ![](https://img.shields.io/github/last-commit/Serset/Vit.Orm.svg)  
+ 
+
+| Build | NuGet |
+| -------- | -------- |
+|![](https://github.com/serset/Vit.Orm/workflows/ki_multibranch/badge.svg) | [![](https://img.shields.io/nuget/v/Vit.Orm.svg)](https://www.nuget.org/packages/Vit.Orm/) ![](https://img.shields.io/nuget/dt/Vit.Orm.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
+
+--------------
+# cur
+
+# support Mysql
+
+
+
+# sqlite upgrade to latest version
+# remove depency of Dapper
+# try to make it clean
+
+
+# support SqlServer
+# support ElasticSearch
+# support ClickHouse
+
+
+# DbContext.QueryProcedure<Entity>(arg)
+
+--------------
+# TODO
+
+# sqlite nested transaction
+# Save SaveRange
+
+
+
+#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`
+  

+ 70 - 0
Vit.Orm.sln

@@ -0,0 +1,70 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31606.5
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{0062F400-558C-4084-8004-3C8D4CBBFDE4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{75C25D0B-8529-4E3F-AF43-6EC1E9E40828}"
+	ProjectSection(SolutionItems) = preProject
+		README.md = README.md
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vit.Orm", "src\Vit.Orm\Vit.Orm.csproj", "{952A36C2-CE2C-416F-BFD0-EBDCA8A3056E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vit.Orm.Mysql", "Vit.Orm.Mysql", "{896DAB83-6E35-4A7D-9F9C-F244289BCB3F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vit.Orm.Mysql", "src\Vit.Orm.Mysql\Vit.Orm.Mysql\Vit.Orm.Mysql.csproj", "{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{ABD9C43D-DEE7-4750-9F86-9B6EB4FD93C3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vit.Orm.Mysql.MsTest", "src\Vit.Orm.Mysql\Test\Vit.Orm.Mysql.MsTest\Vit.Orm.Mysql.MsTest.csproj", "{E708C091-5F15-418A-A4A2-5F45701E5599}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vit.Orm.Sqlite", "Vit.Orm.Sqlite", "{B0686EB6-C433-4AF8-99DC-D0316C4FBF80}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vit.Orm.Sqlite", "src\Vit.Orm.Sqlite\Vit.Orm.Sqlite\Vit.Orm.Sqlite.csproj", "{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vit.Orm.Sqlite.MsTest", "src\Vit.Orm.Sqlite\Test\Vit.Orm.Sqlite.MsTest\Vit.Orm.Sqlite.MsTest.csproj", "{C91B8EF2-5BCC-4D94-A408-C5FB9E348408}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{952A36C2-CE2C-416F-BFD0-EBDCA8A3056E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{952A36C2-CE2C-416F-BFD0-EBDCA8A3056E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{952A36C2-CE2C-416F-BFD0-EBDCA8A3056E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{952A36C2-CE2C-416F-BFD0-EBDCA8A3056E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E708C091-5F15-418A-A4A2-5F45701E5599}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E708C091-5F15-418A-A4A2-5F45701E5599}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E708C091-5F15-418A-A4A2-5F45701E5599}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E708C091-5F15-418A-A4A2-5F45701E5599}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C91B8EF2-5BCC-4D94-A408-C5FB9E348408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C91B8EF2-5BCC-4D94-A408-C5FB9E348408}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C91B8EF2-5BCC-4D94-A408-C5FB9E348408}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C91B8EF2-5BCC-4D94-A408-C5FB9E348408}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{0062F400-558C-4084-8004-3C8D4CBBFDE4} = {B0686EB6-C433-4AF8-99DC-D0316C4FBF80}
+		{0B489440-EC3C-44C6-BF36-F0D3BA66C5F9} = {896DAB83-6E35-4A7D-9F9C-F244289BCB3F}
+		{ABD9C43D-DEE7-4750-9F86-9B6EB4FD93C3} = {896DAB83-6E35-4A7D-9F9C-F244289BCB3F}
+		{E708C091-5F15-418A-A4A2-5F45701E5599} = {ABD9C43D-DEE7-4750-9F86-9B6EB4FD93C3}
+		{E8AE0F59-7E5B-4C24-AF93-5AC8958CC5F0} = {B0686EB6-C433-4AF8-99DC-D0316C4FBF80}
+		{C91B8EF2-5BCC-4D94-A408-C5FB9E348408} = {0062F400-558C-4084-8004-3C8D4CBBFDE4}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {C7DA16E3-9949-49FA-B0B4-F830636DE60F}
+	EndGlobalSection
+EndGlobal

+ 10 - 0
clean-temp.bat

@@ -0,0 +1,10 @@
+cd Publish\DevOps3\build-cmd
+VsTool.exe delete --path "..\..\.." --file "*.suo|*.user" --directory "obj|bin|.vs|packages|TestResults"
+
+
+rd /s/q ..\..\release
+
+
+echo %~n0.bat success£¡
+
+:: pause

+ 279 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/CRUD_Test.cs

@@ -0,0 +1,279 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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 user = new User { id = 4, name = "testUser4", birth = DateTime.Now, fatherId = 14 };
+            var user2 = new User { id = 5, name = "testUser5", birth = DateTime.Now, fatherId = 15 };
+
+            #region Update
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = userQuery.ExecuteUpdate(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                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,
+                                 motherId = 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,
+                                 motherId = 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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.DeleteByKey<User>(4);
+                Assert.AreEqual(1, rowCount);
+                Assert.AreEqual(5, userQuery.Count());
+            }
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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
+
+
+    }
+}

+ 58 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/DataSource.cs

@@ -0,0 +1,58 @@
+using Dapper.Contrib.Extensions;
+
+using Vit.Orm.Sql;
+using Vit.Extensions;
+using Vit.Core.Module.Serialization;
+using Vit.Core.Util.ConfigurationManager;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [Table("User")]
+    public class User
+    {
+        [Key]
+        //[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 DbContext CreateDbContext(string dbName = "DataSource")
+        { 
+            var dbContext = new SqlDbContext();
+            dbContext.UseMysql(connectionString);
+            return dbContext; 
+        }
+ 
+        public static DbContext CreateFormatedDbContext(string dbName = "DataSource")
+        {
+            var dbContext = new SqlDbContext();
+            dbContext.UseMysql(connectionString);
+
+            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;
+        }
+
+    }
+}

+ 181 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_Group_Test.cs

@@ -0,0 +1,181 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_Group_Test
+    {
+
+        [TestMethod]
+        public void Test_Group_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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(4, rows[1].fatherId);
+                Assert.AreEqual(6, rows[1].motherId);
+                Assert.AreEqual(5, rows[2].fatherId);
+                Assert.AreEqual(6, rows[2].motherId);
+            }
+
+            // 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(4, rows[1].fatherId);
+                Assert.AreEqual(6, rows[1].motherId);
+                Assert.AreEqual(5, rows[2].fatherId);
+                Assert.AreEqual(6, rows[2].motherId);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Group_Complex()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
+                        orderby userGroup.Key.fatherId 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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query =
+                    userQuery
+                    .GroupBy(user => new { user.fatherId, user.motherId })
+                    .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 => m.id)
+                    })
+                    ;
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(3, rows.Count);
+                Assert.AreEqual(2, rows[1].rowCount);
+                Assert.AreEqual(1.5, rows[1].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
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_InnerJoin_ByJoin_Test.cs

@@ -0,0 +1,218 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_InnerJoin_ByJoin_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_InnerJoin_BySelectMany_Test.cs

@@ -0,0 +1,135 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_InnerJoin_BySelectMany_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_LeftJoin_ByGroupJoin_Test.cs

@@ -0,0 +1,107 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_LeftJoin_ByGroupJoin_Test
+    {
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_LeftJoin_BySelectMany_Test.cs

@@ -0,0 +1,200 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_LeftJoin_BySelectMany_Test
+    {
+
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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);
+            }
+
+        }
+
+
+    }
+}

+ 470 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Query_Test.cs

@@ -0,0 +1,470 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+using static Vit.Core.Util.XmlComment.MethodComment;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [TestClass]
+    public class Query_Test
+    {
+        [TestMethod]
+        public void Test_Get()
+        {
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var user = dbContext.Get<User>(3);
+                Assert.AreEqual(3, user?.id);
+            }
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var user = dbContext.DbSet<User>().Get(5);
+                Assert.AreEqual(5, user?.id);
+            }
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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 userList = (from user in userQuery
+                                select new
+                                {
+                                    uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                                    uniqueId2 = $"{user.id}_{user.fatherId}_{user.motherId}"
+                                }).ToList();
+
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual("1_4_6", userList.First().uniqueId1);
+            }
+
+        }
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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_DbFunction()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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);
+            }
+
+
+        }
+
+
+
+        [TestMethod]
+        public void Test_Distinct()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = userQuery.Select(u => new { u.fatherId }).Distinct();
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(4, userList.First().fatherId);
+                Assert.AreEqual(null, userList.Last().fatherId);
+            }
+            {
+                var query = userQuery.Select(u => u.fatherId).Distinct();
+
+                var sql = query.ToExecuteString();
+                var fatherId = query.FirstOrDefault();
+                Assert.AreEqual(4, fatherId);
+            }
+            {
+                var query = userQuery.Distinct();
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+            }
+
+        }
+
+
+
+
+    }
+}

+ 133 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Transaction_Test.cs

@@ -0,0 +1,133 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [TestClass]
+    public class Transaction_Test
+    {
+
+        [TestMethod]
+        public void Test_Transaction()
+        {
+            var dbName = System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_";
+            DataSource.CreateFormatedDbContext(dbName).Dispose();
+
+
+            #region Transaction
+            {
+                using var dbContext = DataSource.CreateDbContext(dbName);
+                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 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 tran3 = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u43" });
+                    Assert.AreEqual("u43", userSet.Get(4).name);
+
+                    tran3.Commit();
+                }
+
+                Assert.AreEqual("u43", userSet.Get(4).name);
+            }
+            #endregion
+
+
+            #region Transaction Dispose
+            {
+                {
+                    using var dbContext = DataSource.CreateDbContext(dbName);
+                    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(dbName);
+                    var userSet = dbContext.DbSet<User>();
+
+                    Assert.AreEqual("u42", userSet.Get(4).name);
+                }
+            }
+            #endregion
+
+
+
+        }
+
+
+
+
+        //[TestMethod]
+        public void Test_NestedTransaction()
+        {
+            var dbName = System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_";
+
+            #region
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(dbName);
+                var userSet = dbContext.DbSet<User>();
+
+                using (var tran1 = dbContext.BeginTransaction())
+                {
+                    Assert.AreEqual("u4", userSet.Get(4).name);
+
+                    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);
+                        tran2.Rollback();
+                    }
+
+                    Assert.AreEqual("u41", userSet.Get(4).name);
+                    using (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);
+                }
+
+                Assert.AreEqual("u43", userSet.Get(4).name);
+            }
+            #endregion
+
+
+
+        }
+
+
+
+
+    }
+}

+ 32 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/Vit.Orm.Mysql.MsTest.csproj

@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+    </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="..\..\Vit.Orm.Mysql\Vit.Orm.Mysql.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <None Update="appsettings.json">
+            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+
+
+</Project>

+ 9 - 0
src/Vit.Orm.Mysql/Test/Vit.Orm.Mysql.MsTest/appsettings.json

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

+ 30 - 0
src/Vit.Orm.Mysql/Vit.Orm.Mysql/DbContext_Extensions.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Data;
+
+using Vit.Orm.Entity;
+using Vit.Orm.Entity.Dapper;
+using Vit.Orm.Sql;
+using Vit.Orm.Mysql;
+
+namespace Vit.Extensions
+{
+    public static class DbContext_Extensions
+    {
+        public static SqlDbContext UseMysql(this SqlDbContext dbContext, string ConnectionString)
+        {
+            ISqlTranslator sqlTranslator = new SqlTranslator(dbContext);
+
+            Func<IDbConnection> createDbConnection = () => new MySql.Data.MySqlClient.MySqlConnection(ConnectionString);
+
+            Func<Type, IEntityDescriptor> getEntityDescriptor = (type) => EntityDescriptor.GetEntityDescriptor(type);
+
+
+            dbContext.Init(sqlTranslator: sqlTranslator, createDbConnection: createDbConnection, getEntityDescriptor: getEntityDescriptor);
+
+            return dbContext;
+        }
+
+
+
+    }
+}

+ 32 - 0
src/Vit.Orm.Mysql/Vit.Orm.Mysql/SqlTranslator.cs

@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Mysql.Translator;
+
+
+namespace Vit.Orm.Mysql
+{
+    public class SqlTranslator : Vit.Orm.Sql.Translator.SqlTranslator
+    {
+        public SqlTranslator(DbContext dbContext):base(dbContext) 
+        {
+        }
+
+        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(CombinedStream combinedStream)
+        {
+            var query = new ExecuteUpdateTranslator(this);
+            string sql = query.BuildQuery(combinedStream);
+            return (sql, query.sqlParam);
+        }
+
+        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(CombinedStream combinedStream)
+        {
+            var query = new ExecuteDeleteTranslator(this);
+            string sql = query.BuildQuery(combinedStream);
+            return (sql, query.sqlParam);
+        }
+
+
+
+    }
+}

+ 66 - 0
src/Vit.Orm.Mysql/Vit.Orm.Mysql/Translator/ExecuteDeleteTranslator.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Entity;
+using Vit.Orm.Sql.Translator;
+
+namespace Vit.Orm.Mysql.Translator
+{
+    public class ExecuteDeleteTranslator : BaseQueryTranslator
+    {
+        /*
+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(CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(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;
+        }
+
+
+
+        IEntityDescriptor entityDescriptor;
+
+        public ExecuteDeleteTranslator(SqlTranslator sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(CombinedStream stream)
+        {
+            var entityType = (stream.source as SourceStream)?.GetEntityType();
+            entityDescriptor = sqlTranslator.GetEntityDescriptor(entityType);
+            if (entityDescriptor == null) throw new ArgumentException("Entity can not be deleted");
+
+            var sqlFields = new List<string>();
+
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as `{entityDescriptor.keyName}`");
+            return String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 91 - 0
src/Vit.Orm.Mysql/Vit.Orm.Mysql/Translator/ExecuteUpdateTranslator.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Orm.Entity;
+using Vit.Orm.Sql.Translator;
+
+namespace Vit.Orm.Mysql.Translator
+{
+    public class ExecuteUpdateTranslator : BaseQueryTranslator
+    {
+        /*
+
+-- 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(CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(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 += $"UPDATE {sqlTranslator.DelimitIdentifier(tableName)} ";
+
+            var sqlToUpdateCols = columnsToUpdate.Select(m => m.name).Select(name => $"{NewLine}  SET {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;
+        }
+
+
+        List<MemberBind> columnsToUpdate;
+        IEntityDescriptor entityDescriptor;
+
+        public ExecuteUpdateTranslator(SqlTranslator sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(CombinedStream stream)
+        {
+            var fieldsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate;
+
+            columnsToUpdate = (fieldsToUpdate?.constructorArgs ?? new()).AsQueryable().Concat(fieldsToUpdate?.memberArgs ?? new()).ToList();
+            if (columnsToUpdate?.Any() != true) throw new ArgumentException("can not get columns to update");
+
+
+            var entityType = fieldsToUpdate.New_GetType();
+            entityDescriptor = sqlTranslator.GetEntityDescriptor(entityType);
+            if (entityDescriptor == null) throw new ArgumentException("Entity can not be updated");
+
+
+            var sqlFields = new List<string>();
+
+            foreach (var column in columnsToUpdate)
+            {
+                sqlFields.Add($"({ReadEval(column.value)}) as {sqlTranslator.DelimitIdentifier("_" + column.name)}");
+            }
+
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}");
+            return String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 22 - 0
src/Vit.Orm.Mysql/Vit.Orm.Mysql/Vit.Orm.Mysql.csproj

@@ -0,0 +1,22 @@
+<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>
+
+    <ItemGroup>
+        <!--mysql-->
+        <PackageReference Include="MySql.Data" Version="8.4.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\Vit.Orm\Vit.Orm.csproj" />
+    </ItemGroup>
+ 
+</Project>

+ 279 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/CRUD_Test.cs

@@ -0,0 +1,279 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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 user = new User { id = 4, name = "testUser4", birth = DateTime.Now, fatherId = 14 };
+            var user2 = new User { id = 5, name = "testUser5", birth = DateTime.Now, fatherId = 15 };
+
+            #region Update
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = userQuery.ExecuteUpdate(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                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,
+                                 motherId = 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,
+                                 motherId = 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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = dbContext.DeleteByKey<User>(4);
+                Assert.AreEqual(1, rowCount);
+                Assert.AreEqual(5, userQuery.Count());
+            }
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                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
+
+
+    }
+}

+ 66 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/DataSource.cs

@@ -0,0 +1,66 @@
+using Dapper.Contrib.Extensions;
+
+using Vit.Orm.Sql;
+using Vit.Extensions;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [Table("User")]
+    public class User
+    {
+        [Key]
+        //[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
+    {
+        public static DbContext CreateDbContext(string dbName = "DataSource")
+        {
+            var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"{dbName}.db");
+            var dbContext = new SqlDbContext();
+            dbContext.UseSqlite($"data source={filePath}");
+            return dbContext;
+            //return CreateFormatedDbContext(dbName);
+        }
+
+        public static DbContext CreateFormatedDbContext(string dbName = "DataSource")
+        {
+            var filePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"{dbName}.db");
+            if (File.Exists(filePath)) File.Delete(filePath);
+            File.WriteAllBytes(filePath, new byte[0]);
+
+
+            var connectionString = $"data source={filePath}";
+            //connectionString = $"Data Source={dbName};Mode=Memory;Cache=Shared";
+            //connectionString = $"Data Source=:memory:";
+
+            var dbContext = new SqlDbContext();
+            dbContext.UseSqlite(connectionString);
+
+            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;
+        }
+
+    }
+}

+ 181 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_Group_Test.cs

@@ -0,0 +1,181 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_Group_Test
+    {
+
+        [TestMethod]
+        public void Test_Group_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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(4, rows[1].fatherId);
+                Assert.AreEqual(6, rows[1].motherId);
+                Assert.AreEqual(5, rows[2].fatherId);
+                Assert.AreEqual(6, rows[2].motherId);
+            }
+
+            // 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(4, rows[1].fatherId);
+                Assert.AreEqual(6, rows[1].motherId);
+                Assert.AreEqual(5, rows[2].fatherId);
+                Assert.AreEqual(6, rows[2].motherId);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Group_Complex()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
+                        orderby userGroup.Key.fatherId 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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query =
+                    userQuery
+                    .GroupBy(user => new { user.fatherId, user.motherId })
+                    .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 => m.id)
+                    })
+                    ;
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+
+                Assert.AreEqual(3, rows.Count);
+                Assert.AreEqual(2, rows[1].rowCount);
+                Assert.AreEqual(1.5, rows[1].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
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_InnerJoin_ByJoin_Test.cs

@@ -0,0 +1,218 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_InnerJoin_ByJoin_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_InnerJoin_BySelectMany_Test.cs

@@ -0,0 +1,135 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_InnerJoin_BySelectMany_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_LeftJoin_ByGroupJoin_Test.cs

@@ -0,0 +1,107 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_LeftJoin_ByGroupJoin_Test
+    {
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_LeftJoin_BySelectMany_Test.cs

@@ -0,0 +1,200 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+    [TestClass]
+    public class Query_LeftJoin_BySelectMany_Test
+    {
+
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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);
+            }
+
+        }
+
+
+    }
+}

+ 470 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Query_Test.cs

@@ -0,0 +1,470 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Vit.Extensions.Linq_Extensions;
+using System.Data;
+using static Vit.Core.Util.XmlComment.MethodComment;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [TestClass]
+    public class Query_Test
+    {
+        [TestMethod]
+        public void Test_Get()
+        {
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var user = dbContext.Get<User>(3);
+                Assert.AreEqual(3, user?.id);
+            }
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+                var user = dbContext.DbSet<User>().Get(5);
+                Assert.AreEqual(5, user?.id);
+            }
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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 userList = (from user in userQuery
+                                select new
+                                {
+                                    uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                                    uniqueId2 = $"{user.id}_{user.fatherId}_{user.motherId}"
+                                }).ToList();
+
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual("1_4_6", userList.First().uniqueId1);
+            }
+
+        }
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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_DbFunction()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            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);
+            }
+
+
+        }
+
+
+
+        [TestMethod]
+        public void Test_Distinct()
+        {
+            using var dbContext = DataSource.CreateFormatedDbContext(System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_");
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = userQuery.Select(u => new { u.fatherId }).Distinct();
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(3, userList.Count);
+                Assert.AreEqual(4, userList.First().fatherId);
+                Assert.AreEqual(null, userList.Last().fatherId);
+            }
+            {
+                var query = userQuery.Select(u => u.fatherId).Distinct();
+
+                var sql = query.ToExecuteString();
+                var fatherId = query.FirstOrDefault();
+                Assert.AreEqual(4, fatherId);
+            }
+            {
+                var query = userQuery.Distinct();
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+            }
+
+        }
+
+
+
+
+    }
+}

+ 133 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Transaction_Test.cs

@@ -0,0 +1,133 @@
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vit.Orm.Sqlite.MsTest
+{
+
+    [TestClass]
+    public class Transaction_Test
+    {
+
+        [TestMethod]
+        public void Test_Transaction()
+        {
+            var dbName = System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_";
+            DataSource.CreateFormatedDbContext(dbName).Dispose();
+
+
+            #region Transaction
+            {
+                using var dbContext = DataSource.CreateDbContext(dbName);
+                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 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 tran3 = dbContext.BeginTransaction())
+                {
+                    dbContext.Update(new User { id = 4, name = "u43" });
+                    Assert.AreEqual("u43", userSet.Get(4).name);
+
+                    tran3.Commit();
+                }
+
+                Assert.AreEqual("u43", userSet.Get(4).name);
+            }
+            #endregion
+
+
+            #region Transaction Dispose
+            {
+                {
+                    using var dbContext = DataSource.CreateDbContext(dbName);
+                    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(dbName);
+                    var userSet = dbContext.DbSet<User>();
+
+                    Assert.AreEqual("u42", userSet.Get(4).name);
+                }
+            }
+            #endregion
+
+
+
+        }
+
+
+
+
+        //[TestMethod]
+        public void Test_NestedTransaction()
+        {
+            var dbName = System.Reflection.MethodBase.GetCurrentMethod()?.Name ?? "_";
+
+            #region
+            {
+                using var dbContext = DataSource.CreateFormatedDbContext(dbName);
+                var userSet = dbContext.DbSet<User>();
+
+                using (var tran1 = dbContext.BeginTransaction())
+                {
+                    Assert.AreEqual("u4", userSet.Get(4).name);
+
+                    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);
+                        tran2.Rollback();
+                    }
+
+                    Assert.AreEqual("u41", userSet.Get(4).name);
+                    using (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);
+                }
+
+                Assert.AreEqual("u43", userSet.Get(4).name);
+            }
+            #endregion
+
+
+
+        }
+
+
+
+
+    }
+}

+ 24 - 0
src/Vit.Orm.Sqlite/Test/Vit.Orm.Sqlite.MsTest/Vit.Orm.Sqlite.MsTest.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+    </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="..\..\Vit.Orm.Sqlite\Vit.Orm.Sqlite.csproj" />
+    </ItemGroup>
+
+</Project>

+ 30 - 0
src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/DbContext_Extensions.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Data;
+
+using Vit.Orm.Entity;
+using Vit.Orm.Entity.Dapper;
+using Vit.Orm.Sql;
+using Vit.Orm.Sqlite;
+
+namespace Vit.Extensions
+{
+    public static class DbContext_Extensions
+    {
+        public static SqlDbContext UseSqlite(this SqlDbContext dbContext, string ConnectionString)
+        {
+            ISqlTranslator sqlTranslator = new SqlTranslator(dbContext);
+
+            Func<IDbConnection> createDbConnection = () => new Microsoft.Data.Sqlite.SqliteConnection(ConnectionString);
+
+            Func<Type, IEntityDescriptor> getEntityDescriptor = (type) => EntityDescriptor.GetEntityDescriptor(type);
+
+
+            dbContext.Init(sqlTranslator: sqlTranslator, createDbConnection: createDbConnection, getEntityDescriptor: getEntityDescriptor);
+
+            return dbContext;
+        }
+
+
+
+    }
+}

+ 32 - 0
src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/SqlTranslator.cs

@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Sqlite.Translator;
+
+
+namespace Vit.Orm.Sqlite
+{
+    public class SqlTranslator : Vit.Orm.Sql.Translator.SqlTranslator
+    {
+        public SqlTranslator(DbContext dbContext):base(dbContext) 
+        {
+        }
+
+        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(CombinedStream combinedStream)
+        {
+            var query = new ExecuteUpdateTranslator(this);
+            string sql = query.BuildQuery(combinedStream);
+            return (sql, query.sqlParam);
+        }
+
+        public override (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(CombinedStream combinedStream)
+        {
+            var query = new ExecuteDeleteTranslator(this);
+            string sql = query.BuildQuery(combinedStream);
+            return (sql, query.sqlParam);
+        }
+
+
+
+    }
+}

+ 66 - 0
src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Translator/ExecuteDeleteTranslator.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Entity;
+using Vit.Orm.Sql.Translator;
+
+namespace Vit.Orm.Sqlite.Translator
+{
+    public class ExecuteDeleteTranslator : BaseQueryTranslator
+    {
+        /*
+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(CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(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;
+        }
+
+
+
+        IEntityDescriptor entityDescriptor;
+
+        public ExecuteDeleteTranslator(SqlTranslator sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(CombinedStream stream)
+        {
+            var entityType = (stream.source as SourceStream)?.GetEntityType();
+            entityDescriptor = sqlTranslator.GetEntityDescriptor(entityType);
+            if (entityDescriptor == null) throw new ArgumentException("Entity can not be deleted");
+
+            var sqlFields = new List<string>();
+
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as `{entityDescriptor.keyName}`");
+            return String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 91 - 0
src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Translator/ExecuteUpdateTranslator.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Orm.Entity;
+using Vit.Orm.Sql.Translator;
+
+namespace Vit.Orm.Sqlite.Translator
+{
+    public class ExecuteUpdateTranslator : BaseQueryTranslator
+    {
+        /*
+
+-- 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(CombinedStream stream)
+        {
+            var sqlInner = base.BuildQuery(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 += $"UPDATE {sqlTranslator.DelimitIdentifier(tableName)} ";
+
+            var sqlToUpdateCols = columnsToUpdate.Select(m => m.name).Select(name => $"{NewLine}  SET {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;
+        }
+
+
+        List<MemberBind> columnsToUpdate;
+        IEntityDescriptor entityDescriptor;
+
+        public ExecuteUpdateTranslator(SqlTranslator sqlTranslator) : base(sqlTranslator)
+        {
+        }
+
+        protected override string ReadSelect(CombinedStream stream)
+        {
+            var fieldsToUpdate = (stream as StreamToUpdate)?.fieldsToUpdate;
+
+            columnsToUpdate = (fieldsToUpdate?.constructorArgs ?? new()).AsQueryable().Concat(fieldsToUpdate?.memberArgs ?? new()).ToList();
+            if (columnsToUpdate?.Any() != true) throw new ArgumentException("can not get columns to update");
+
+
+            var entityType = fieldsToUpdate.New_GetType();
+            entityDescriptor = sqlTranslator.GetEntityDescriptor(entityType);
+            if (entityDescriptor == null) throw new ArgumentException("Entity can not be updated");
+
+
+            var sqlFields = new List<string>();
+
+            foreach (var column in columnsToUpdate)
+            {
+                sqlFields.Add($"({ReadEval(column.value)}) as {sqlTranslator.DelimitIdentifier("_" + column.name)}");
+            }
+
+            // primary key
+            sqlFields.Add($"{sqlTranslator.GetSqlField(stream.source.alias, entityDescriptor.keyName)} as {sqlTranslator.DelimitIdentifier(entityDescriptor.keyName)}");
+            return String.Join(",", sqlFields);
+        }
+
+
+
+    }
+}

+ 21 - 0
src/Vit.Orm.Sqlite/Vit.Orm.Sqlite/Vit.Orm.Sqlite.csproj

@@ -0,0 +1,21 @@
+<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>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.17" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\Vit.Orm\Vit.Orm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 72 - 0
src/Vit.Orm/DbContext.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+using Vit.Linq.ExpressionTree;
+using Vit.Orm.Entity;
+
+namespace Vit.Orm
+{
+    public class DbContext : IDisposable
+    {
+        public DbContext() { }
+
+        public virtual ExpressionConvertService convertService => ExpressionConvertService.Instance;
+
+        public Func<Type, IDbSet> dbSetCreator { set; protected get; }
+
+        Dictionary<Type, IDbSet> dbSetMap = new();
+
+        public virtual IDbSet DbSet(Type entityType)
+        {
+            if (dbSetMap.TryGetValue(entityType, out var dbSet)) return dbSet;
+
+            dbSet = dbSetCreator(entityType);
+            dbSetMap[entityType] = dbSet;
+            return dbSet;
+
+            //return dbSetMap.GetOrAdd(entityType, dbSetCreator);
+        }
+        public virtual DbSet<Entity> DbSet<Entity>()
+        {
+            return DbSet(typeof(Entity)) as DbSet<Entity>;
+        }
+
+
+        public virtual IEntityDescriptor GetEntityDescriptor(Type entityType) => DbSet(entityType)?.entityDescriptor;
+
+
+
+        public virtual void Create<Entity>() => DbSet<Entity>().Create();
+
+        public virtual Entity Add<Entity>(Entity entity) => DbSet<Entity>().Add(entity);
+        public virtual void AddRange<Entity>(IEnumerable<Entity> entitys) => DbSet<Entity>().AddRange(entitys);
+
+
+        public virtual Entity Get<Entity>(object keyValue) => DbSet<Entity>().Get(keyValue);
+        public virtual IQueryable<Entity> Query<Entity>() => DbSet<Entity>().Query();
+
+
+
+        public virtual int Update<Entity>(Entity entity) => DbSet<Entity>().Update(entity);
+        public virtual int UpdateRange<Entity>(IEnumerable<Entity> entitys) => DbSet<Entity>().UpdateRange(entitys);
+
+
+
+        public virtual int Delete<Entity>(Entity entity) => DbSet<Entity>().Delete(entity);
+        public virtual int DeleteRange<Entity>(IEnumerable<Entity> entitys) => DbSet<Entity>().DeleteRange(entitys);
+
+
+        public virtual int DeleteByKey<Entity>(object keyValue) => DbSet<Entity>().DeleteByKey(keyValue);
+        public virtual int DeleteByKeys<Entity, Key>(IEnumerable<Key> keys) => DbSet<Entity>().DeleteByKeys(keys);
+
+        public virtual IDbTransaction BeginTransaction()
+        {
+            throw new NotImplementedException();
+        }
+        public virtual void Dispose()
+        {
+        }
+    }
+}

+ 43 - 0
src/Vit.Orm/DbSet.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using Vit.Orm.Entity;
+
+namespace Vit.Orm
+{
+    public interface IDbSet
+    {
+        IEntityDescriptor entityDescriptor { get; }
+    }
+
+    public abstract class DbSet<Entity> : IDbSet
+    {
+        public abstract IEntityDescriptor entityDescriptor { get; }
+
+
+        public abstract void Create();
+
+
+        public abstract Entity Add(Entity entity);
+        public abstract void AddRange(IEnumerable<Entity> entitys);
+
+
+        public abstract Entity Get(object keyValue);
+        public abstract IQueryable<Entity> Query();
+
+
+
+        public abstract int Update(Entity entity);
+        public abstract int UpdateRange(IEnumerable<Entity> entitys);
+
+
+        public abstract int Delete(Entity entity);
+        public abstract int DeleteRange(IEnumerable<Entity> entitys);
+
+        public abstract int DeleteByKey(object keyValue);
+        public abstract int DeleteByKeys<Key>(IEnumerable<Key> keys);
+
+
+
+    }
+}

+ 31 - 0
src/Vit.Orm/Entity/ColumnDescriptor.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Reflection;
+
+namespace Vit.Orm.Entity
+{
+    public class ColumnDescriptor : IColumnDescriptor
+    {
+        public ColumnDescriptor(PropertyInfo propertyInfo, bool isPrimaryKey)
+        {
+            this.propertyInfo = propertyInfo;
+            this.isPrimaryKey = isPrimaryKey;
+        }
+
+        PropertyInfo propertyInfo;
+        public bool isPrimaryKey { get; private set; }
+        public string name => propertyInfo?.Name;
+
+        public Type type => propertyInfo?.PropertyType;
+
+        public void Set(object entity, object value)
+        {
+            propertyInfo?.SetValue(entity, value);
+        }
+        public object Get(object entity)
+        {
+            return propertyInfo?.GetValue(entity, null);
+        }
+    }
+
+
+}

+ 64 - 0
src/Vit.Orm/Entity/Dapper/EntityDescriptor.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Vit.Orm.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)
+        {
+            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 string tableName { get; private set; }
+
+        /// <summary>
+        /// primary key name
+        /// </summary>
+        public string keyName => key?.name;
+
+        /// <summary>
+        /// primary key
+        /// </summary>
+        public IColumnDescriptor key { get; private set; }
+
+        /// <summary>
+        /// not include primary key
+        /// </summary>
+        public IColumnDescriptor[] columns { get; private set; }
+
+
+        public IColumnDescriptor[] allColumns { get; private set; }
+
+
+    }
+}

+ 13 - 0
src/Vit.Orm/Entity/IColumnDescriptor.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace Vit.Orm.Entity
+{
+    public interface IColumnDescriptor
+    {
+        bool isPrimaryKey { get; }
+        string name { get; }
+        Type type { get; }
+        void Set(object entity, object value);
+        object Get(object entity);
+    }
+}

+ 19 - 0
src/Vit.Orm/Entity/IEntityDescriptor.cs

@@ -0,0 +1,19 @@
+namespace Vit.Orm.Entity
+{
+    public interface IEntityDescriptor
+    {
+        string tableName { get; }
+        string keyName { get; }
+        /// <summary>
+        /// primary key
+        /// </summary>
+        public IColumnDescriptor key { get; }
+
+        /// <summary>
+        /// not include primary key
+        /// </summary>
+        public IColumnDescriptor[] columns { get; }
+
+        public IColumnDescriptor[] allColumns { get; }
+    }
+}

+ 191 - 0
src/Vit.Orm/Sql/DataReader/EntityReader.cs

@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+
+using Vit.Linq.ExpressionTree;
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    public class EntityReader : IDbDataReader
+    {
+        public List<string> sqlFields { get; private set; } = new List<string>();
+
+        protected Type entityType;
+        protected List<IArgReader> entityArgReaders = new List<IArgReader>();
+        protected Delegate lambdaCreateEntity;
+
+        public string BuildSelect(Type entityType, ISqlTranslator sqlTranslator, ExpressionConvertService convertService, ExpressionNode selectedFields)
+        {
+            this.entityType = entityType;
+
+            var cloner = new ExpressionNodeCloner();
+            cloner.clone = (node) =>
+            {
+                if (node?.nodeType == NodeType.Member)
+                {
+                    ExpressionNode_Member member = node;
+
+                    var argName = GetArgument(sqlTranslator, member);
+
+                    if (argName != null)
+                    {
+                        return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
+                    }
+                }
+                else if (node?.nodeType == NodeType.MethodCall)
+                {
+                    ExpressionNode_MethodCall methodCall = node;
+
+                    var argName = GetArgument(sqlTranslator, methodCall);
+
+                    if (argName != null)
+                    {
+                        return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
+                    }
+                }
+                return default;
+            };
+            ExpressionNode_New newExp = cloner.Clone(selectedFields);
+
+
+            #region Compile Lambda
+            var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), (ExpressionNode)newExp);
+            //var strNode = Json.Serialize(lambdaNode);
+
+            var lambdaExp = convertService.ToLambdaExpression(lambdaNode, entityArgReaders.Select(m => m.argType).ToArray());
+
+            lambdaCreateEntity = lambdaExp.Compile();
+            #endregion
+
+            // sqlFields
+            return String.Join(", ", sqlFields);
+        }
+
+
+        public virtual object ReadData(IDataReader reader)
+        {
+            return new Func<IDataReader, object>(ReadEntity<string>)
+              .GetMethodInfo().GetGenericMethodDefinition().MakeGenericMethod(entityType)
+              .Invoke(this, new object[] { reader });
+        }
+
+        object ReadEntity<Entity>(IDataReader reader)
+        {
+            var list = new List<Entity>();
+
+            while (reader.Read())
+            {
+                var lambdaArgs = entityArgReaders.Select(m => m.Read(reader)).ToArray();
+                var obj = (Entity)lambdaCreateEntity.DynamicInvoke(lambdaArgs);
+                list.Add(obj);
+            }
+
+            return list;
+        }
+
+        protected string GetArgument(ISqlTranslator sqlTranslator, ExpressionNode_Member member)
+        {
+            // tableName_fieldName   tableName_
+            var argUniqueKey = $"arg_{member.objectValue?.parameterName ?? member.parameterName}_{member.memberName}";
+
+            IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.argUniqueKey == argUniqueKey);
+
+            if (argReader == null)
+            {
+                var argName = "arg_" + entityArgReaders.Count;
+
+                var argType = member.Member_GetType();
+
+                bool isValueType = TypeUtil.IsValueType(argType);
+                if (isValueType)
+                {
+                    // Value arg
+                    string sqlFieldName = sqlTranslator.GetSqlField(member);
+                    argReader = new ValueReader(this, argType, argUniqueKey, argName, sqlFieldName);
+                }
+                else
+                {
+                    // Entity arg
+                    argReader = new ModelReader(this, sqlTranslator, member, argUniqueKey, argName, argType);
+                }
+                entityArgReaders.Add(argReader);
+            }
+            return argReader.argName;
+        }
+        protected string GetArgument(ISqlTranslator sqlTranslator, ExpressionNode_MethodCall methodCall)
+        {
+            var functionName = methodCall.methodName;
+            switch (methodCall.methodName)
+            {
+                case nameof(Enumerable.Count):
+                    {
+                        var stream = methodCall.arguments[0] as ExpressionNode_Member;
+                        if (stream?.nodeType == NodeType.Member && stream.parameterName != null && stream.memberName == null)
+                        {
+                            var tableName = stream.parameterName;
+                            var columnName = stream.memberName;
+
+                            var argUniqueKey = $"argFunc_{functionName}_{tableName}_{columnName}";
+
+                            IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.argUniqueKey == argUniqueKey);
+
+                            if (argReader == null)
+                            {
+                                var argName = "arg_" + entityArgReaders.Count;
+
+                                var argType = typeof(int);
+
+                                // Value arg
+                                string sqlFieldName = sqlTranslator.GetSqlField_Aggregate(functionName,tableName, columnName: columnName);
+                                argReader = new ValueReader(this, argType, argUniqueKey, argName, sqlFieldName);
+
+                                entityArgReaders.Add(argReader);
+                            }
+                            return argReader.argName;
+                        }
+                    }
+                    break;
+                case nameof(Enumerable.Max) or nameof(Enumerable.Min) or nameof(Enumerable.Sum) or nameof(Enumerable.Average) when methodCall.arguments.Length == 2:
+                    {
+                        var stream = methodCall.arguments[0] as ExpressionNode_Member;
+                        if (stream?.nodeType == NodeType.Member && stream.parameterName != null && stream.memberName == null)
+                        {
+                            var lambdaFieldSelect = methodCall.arguments[1] as ExpressionNode_Lambda;
+                            if (lambdaFieldSelect?.body?.nodeType == NodeType.Member)
+                            {
+                                var tableName = stream.parameterName;
+                                string columnName = lambdaFieldSelect.body.memberName;
+
+                                var argUniqueKey = $"argFunc_{functionName}_{tableName}_{columnName}";
+
+                                IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.argUniqueKey == argUniqueKey);
+
+                                if (argReader == null)
+                                {
+                                    var argName = "arg_" + entityArgReaders.Count;
+
+                                    var argType = methodCall.MethodCall_GetReturnType();
+
+                                    // Value arg
+                                    string sqlFieldName = sqlTranslator.GetSqlField_Aggregate(functionName,tableName, columnName: columnName);
+                                    argReader = new ValueReader(this, argType, argUniqueKey, argName, sqlFieldName);
+
+                                    entityArgReaders.Add(argReader);
+                                }
+                                return argReader.argName;
+                            }
+                        }
+                    }
+                    break;
+
+            }
+            //throw new NotSupportedException("[CollectionStream] unexpected method call : " + methodCall.methodName);
+            return default;
+        }
+
+
+    }
+}

+ 14 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/IArgReader.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Data;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    public interface IArgReader
+    {
+        string argUniqueKey { get; }
+        Type argType { get; }
+        string argName { get; }
+        object Read(IDataReader reader);
+    }
+
+}

+ 35 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/ModelReader.EntityPropertyReader.cs

@@ -0,0 +1,35 @@
+using System.Data;
+
+using Vit.Orm.Entity;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    partial class ModelReader
+    {
+        class EntityPropertyReader : SqlFieldReader
+        {
+            IColumnDescriptor column;
+
+            public EntityPropertyReader(EntityReader entityReader, IColumnDescriptor column, bool isPrimaryKey, string sqlFieldName)
+                : base(entityReader.sqlFields, column.type, sqlFieldName)
+            {
+                this.column = column;
+            }
+            public bool Read(IDataReader reader, object entity)
+            {
+                var value = Read(reader);
+                if (value != null)
+                {
+                    column.Set(entity, value);
+                    return true;
+                }
+
+                if (column.isPrimaryKey) return false;
+                return true;
+            }
+        }
+    }
+
+
+
+}

+ 54 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/ModelReader.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    partial class ModelReader : IArgReader
+    {
+        public string argName { get; set; }
+        public string argUniqueKey { get; set; }
+        public Type argType { get; set; }
+
+        List<EntityPropertyReader> proppertyReaders = new();
+
+        public ModelReader(EntityReader entityReader, ISqlTranslator sqlTranslator, ExpressionNode_Member member, string argUniqueKey, string argName, Type argType)
+        {
+            this.argUniqueKey = argUniqueKey;
+            this.argName = argName;
+            this.argType = argType;
+
+            var entityDescriptor = sqlTranslator.GetEntityDescriptor(argType);
+
+            // 1: {"nodeType":"Member","parameterName":"a0","memberName":"id"}
+            // 2: {"nodeType":"Member","objectValue":{"parameterName":"a0","nodeType":"Member"},"memberName":"id"}
+            var tableName = member.objectValue?.parameterName ?? member.parameterName;
+
+            // ##1 key
+            string sqlFieldName = sqlTranslator.GetSqlField(tableName, entityDescriptor.keyName);
+            proppertyReaders.Add(new EntityPropertyReader(entityReader, entityDescriptor.key, true, sqlFieldName));
+
+            // ##2 properties
+            foreach (var column in entityDescriptor.columns)
+            {
+                sqlFieldName = sqlTranslator.GetSqlField(tableName, column.name);
+                proppertyReaders.Add(new EntityPropertyReader(entityReader, column, false, sqlFieldName));
+            }
+        }
+        public object Read(IDataReader reader)
+        {
+            var entity = Activator.CreateInstance(argType);
+            foreach (var perpertyReader in proppertyReaders)
+            {
+                if (!perpertyReader.Read(reader, entity))
+                    return null;
+            }
+            return entity;
+        }
+    }
+
+
+
+}

+ 41 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/SqlFieldReader.cs

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

+ 51 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/TypeUtil.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    public class TypeUtil
+    {
+        public static Type GetUnderlyingType(Type type)
+        {
+            if (type.IsGenericType && typeof(Nullable<>) == type.GetGenericTypeDefinition())
+            {
+                return type.GetGenericArguments()[0];
+            }
+            return type;
+        }
+
+        /// <summary>
+        /// is ValueType or string or Nullable
+        /// </summary>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        public static bool IsValueType(Type type)
+        {
+            if (type.IsValueType || type == typeof(string)) return true;
+            if (type.IsGenericType && typeof(Nullable<>) == type.GetGenericTypeDefinition())
+            {
+                return true;
+            }
+            return false;
+        }
+
+
+
+        public static object ConvertToType(object value, Type type)
+        {
+            return ConvertToUnderlyingType(value, GetUnderlyingType(type));
+        }
+
+        public static object ConvertToUnderlyingType(object value, Type underlyingType)
+        {
+            if (value == null || value == DBNull.Value) return null;
+
+            if (!underlyingType.IsInstanceOfType(value))
+                value = Convert.ChangeType(value, underlyingType);
+            return value;
+        }
+
+
+    }
+}

+ 25 - 0
src/Vit.Orm/Sql/DataReader/EntityReader/ValueReader.cs

@@ -0,0 +1,25 @@
+using System;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+
+namespace Vit.Orm.Sql.DataReader
+{
+
+    class ValueReader : SqlFieldReader, IArgReader
+    {
+        public string argName { get; set; }
+
+        public string argUniqueKey { get; set; }
+
+        public Type argType { get => valueType; }
+
+        public ValueReader(EntityReader entityReader, Type valueType, string argUniqueKey, string argName, string sqlFieldName)
+                     : base(entityReader.sqlFields, valueType, sqlFieldName)
+        {
+            this.argUniqueKey = argUniqueKey;
+            this.argName = argName;
+        }
+    }
+
+
+}

+ 32 - 0
src/Vit.Orm/Sql/DataReader/FirstEntityReader.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Vit.Orm.Sql.DataReader
+{
+    public class FirstEntityReader: EntityReader
+    {
+        public bool nullable = true;
+        public override object ReadData(IDataReader reader)
+        {
+            return new Func<IDataReader, string>(ReadEntity<string>)
+              .GetMethodInfo().GetGenericMethodDefinition().MakeGenericMethod(entityType)
+              .Invoke(this, new object[] { reader });
+        }
+
+        Entity ReadEntity<Entity>(IDataReader reader)
+        {
+            if (reader.Read())
+            {
+                var lambdaArgs = entityArgReaders.Select(m => m.Read(reader)).ToArray();
+                var obj = (Entity)lambdaCreateEntity.DynamicInvoke(lambdaArgs);
+                return obj;
+            }
+            if (!nullable) throw new InvalidOperationException("Sequence contains no elements");
+            return default;
+        }
+    }
+}

+ 20 - 0
src/Vit.Orm/Sql/DataReader/NumScalarReader.cs

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

+ 9 - 0
src/Vit.Orm/Sql/IDbDataReader.cs

@@ -0,0 +1,9 @@
+ 
+
+namespace Vit.Orm.Sql
+{
+    public interface IDbDataReader
+    {
+        object ReadData(System.Data.IDataReader reader);
+    }
+}

+ 70 - 0
src/Vit.Orm/Sql/ISqlTranslator.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Entity;
+
+namespace Vit.Orm.Sql
+{
+    public interface ISqlTranslator
+    {
+        IEntityDescriptor GetEntityDescriptor(Type entityType);
+
+        /// <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.
+        string DelimitIdentifier(string identifier);
+
+        /// <summary>
+        ///     Generates a valid parameter name for the given candidate name.
+        /// </summary>
+        /// <param name="name">The candidate name for the parameter.</param>
+        /// <returns>
+        ///     A valid name based on the candidate name.
+        /// </returns>
+        string GenerateParameterName(string name);
+        string GetTableName(Type entityType);
+        string GetSqlField(string tableName, string columnName);
+        string GetSqlField(ExpressionNode_Member member);
+
+
+        /// <summary>
+        /// functionName example:  Count, Max, Min, Sum, Average
+        /// </summary>
+        /// <param name="functionName"></param>
+        /// <param name="tableName"></param>
+        /// <param name="columnName"></param>
+        /// <returns></returns>
+        string GetSqlField_Aggregate(string functionName, string tableName, string columnName);
+
+
+
+        string PrepareCreate(IEntityDescriptor entityDescriptor);
+
+        string PrepareGet<Entity>(DbSet<Entity> dbSet);
+
+        (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(CombinedStream combinedStream, Type entityType);
+
+
+        (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) PrepareAdd<Entity>(DbSet<Entity> dbSet);
+
+
+        (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) PrepareUpdate<Entity>(DbSet<Entity> dbSet);
+        (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(CombinedStream combinedStream);
+
+        string PrepareDelete<Entity>(DbSet<Entity> dbSet);
+
+        string PrepareDeleteRange<Entity>(DbSet<Entity> dbSet);
+
+        (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(CombinedStream combinedStream);
+
+
+
+
+
+    }
+}

+ 148 - 0
src/Vit.Orm/Sql/SqlDbContext.cs

@@ -0,0 +1,148 @@
+using Dapper;
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+
+using Vit.Orm.Entity;
+
+namespace Vit.Orm.Sql
+{
+    public class SqlDbContext : DbContext
+    {
+        protected Func<IDbConnection> createDbConnection { get; set; }
+        protected IDbConnection _dbConnection;
+        public override void Dispose()
+        {
+            base.Dispose();
+
+            // dispose transactions
+            DisposeTransactions();
+
+            _dbConnection?.Dispose();
+            _dbConnection = null;
+        }
+        public virtual IDbConnection dbConnection => _dbConnection ??= createDbConnection();
+
+
+        public ISqlTranslator sqlTranslator { get; private set; }
+
+
+        public void Init(ISqlTranslator sqlTranslator, Func<IDbConnection> createDbConnection, Func<Type, IEntityDescriptor> getEntityDescriptor)
+        {
+            this.sqlTranslator = sqlTranslator;
+            this.createDbConnection = createDbConnection;
+
+            this.dbSetCreator = (entityType) =>
+            {
+                var entityDescriptor = getEntityDescriptor(entityType);
+                return SqlDbSetConstructor.CreateDbSet(this, entityType, entityDescriptor);
+            };
+        }
+
+        protected Stack<TransactionWrap> transactions;
+        protected IDbTransaction GetCurrentTransaction()
+        {
+            if (transactions == null) return null;
+
+            while (transactions.Count > 0)
+            {
+                var tran = transactions.Peek();
+                if (tran?.TransactionState == TransactionWrap.ETransactionState.Active) return tran.originalTransaction;
+                transactions.Pop();
+            }
+            return null;
+        }
+        protected void DisposeTransactions()
+        {
+            if (transactions == null) return;
+
+            while (transactions.Count > 0)
+            {
+                var transaction = transactions.Pop();
+                if (transaction?.TransactionState != TransactionWrap.ETransactionState.Disposed)
+                {
+                    transaction?.Dispose();
+                }
+            }
+            transactions = null;
+        }
+        public override IDbTransaction BeginTransaction()
+        {
+            if (dbConnection.State != ConnectionState.Open) dbConnection.Open();
+
+            var transaction = dbConnection.BeginTransaction();
+
+            transactions ??= new();
+            var wrap = new TransactionWrap(transaction);
+            transactions.Push(wrap);
+            return wrap;
+        }
+
+        public class TransactionWrap : IDbTransaction
+        {
+            public enum ETransactionState
+            {
+                Active, Committed, RolledBack, Disposed
+            }
+            public ETransactionState TransactionState { get; private set; } = ETransactionState.Active;
+            public TransactionWrap(IDbTransaction transaction)
+            {
+                originalTransaction = transaction;
+            }
+            public IDbTransaction originalTransaction;
+
+            public IDbConnection Connection => originalTransaction.Connection;
+
+            public System.Data.IsolationLevel IsolationLevel => originalTransaction.IsolationLevel;
+
+            public void Commit()
+            {
+                originalTransaction.Commit();
+                TransactionState = ETransactionState.Committed;
+            }
+
+            public void Dispose()
+            {
+                originalTransaction.Dispose();
+                TransactionState = ETransactionState.Disposed;
+            }
+
+            public void Rollback()
+            {
+                originalTransaction.Rollback();
+                TransactionState = ETransactionState.RolledBack;
+            }
+        }
+
+
+
+        #region Execute
+
+        public int commandTimeout = 0;
+
+        public virtual int Execute(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        {
+            var transaction = GetCurrentTransaction();
+            commandTimeout ??= this.commandTimeout;
+            return dbConnection.Execute(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+        }
+
+        public virtual IDataReader ExecuteReader(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        {
+            var transaction = GetCurrentTransaction();
+            commandTimeout ??= this.commandTimeout;
+            return dbConnection.ExecuteReader(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+        }
+
+        public virtual object ExecuteScalar(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
+        {
+            var transaction = GetCurrentTransaction();
+            commandTimeout ??= this.commandTimeout;
+            return dbConnection.ExecuteScalar(sql, param: param, transaction: transaction, commandTimeout: commandTimeout, commandType: commandType);
+        }
+        #endregion
+
+    }
+}

+ 283 - 0
src/Vit.Orm/Sql/SqlDbSet.cs

@@ -0,0 +1,283 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+using System.Linq.Expressions;
+using Vit.Orm.Entity;
+using System.Reflection;
+using Vit.Linq;
+using Vit.Orm.Sql.DataReader;
+using Vit.Extensions.Linq_Extensions;
+
+namespace Vit.Orm.Sql
+{
+    public class SqlDbSetConstructor
+    {
+        public static IDbSet CreateDbSet( SqlDbContext dbContext, Type entityType, IEntityDescriptor entityDescriptor)
+        {
+            return _CreateDbSet.MakeGenericMethod(entityType)
+                     .Invoke(null, new object[] { dbContext, entityDescriptor }) as IDbSet;
+        }
+       
+        static MethodInfo _CreateDbSet = new Func<SqlDbContext, IEntityDescriptor,IDbSet>(CreateDbSet<object>)
+                   .Method.GetGenericMethodDefinition();
+        public static IDbSet CreateDbSet<Entity>(SqlDbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            return new SqlDbSet<Entity>(dbContext, entityDescriptor);
+        }
+
+    }
+
+    public class SqlDbSet<Entity> : Vit.Orm.DbSet<Entity>
+    {
+        protected SqlDbContext dbContext;
+
+        protected IEntityDescriptor _entityDescriptor;
+        public override IEntityDescriptor entityDescriptor => _entityDescriptor;
+
+
+        public virtual ISqlTranslator sqlTranslator => dbContext.sqlTranslator;
+
+        public SqlDbSet(SqlDbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            this.dbContext = dbContext;
+            this._entityDescriptor = entityDescriptor;
+        }
+
+        public override void Create()
+        {
+            string sql = sqlTranslator.PrepareCreate(entityDescriptor);
+
+            dbContext.Execute(sql: sql);
+        }
+
+
+
+
+        public override Entity Add(Entity entity)
+        {
+            // #1 prepare sql
+            (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) = sqlTranslator.PrepareAdd(this);
+
+            // #2 get sql params
+            var sqlParam = GetSqlParams(entity);
+
+            // #3 execute
+            var affectedRowCount = dbContext.Execute(sql: sql, param: (object)sqlParam);
+
+            return affectedRowCount == 1 ? entity : default;
+        }
+
+        public override void AddRange(IEnumerable<Entity> entitys)
+        {
+            // #1 prepare sql
+            (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) = sqlTranslator.PrepareAdd(this);
+
+            // #2 execute
+            var affectedRowCount = 0;
+
+            foreach (var entity in entitys)
+            {
+                var sqlParam = GetSqlParams(entity);
+                if (dbContext.Execute(sql: sql, param: (object)sqlParam) == 1)
+                    affectedRowCount++;
+            }
+        }
+
+
+        public override Entity Get(object keyValue)
+        {
+            // #1 prepare sql
+            string sql = sqlTranslator.PrepareGet(this);
+
+            // #2 get sql params
+            var sqlParam = new Dictionary<string, object>();
+            sqlParam[entityDescriptor.keyName] = keyValue;
+
+            // #3 execute
+            using var reader = dbContext.ExecuteReader(sql: sql, param: (object)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));
+                }
+                return entity;
+            }
+            return default;
+
+
+        }
+
+
+        public override IQueryable<Entity> Query()
+        {
+            var dbContextId = "SqlDbSet_" + dbContext.GetHashCode();
+
+            Func<Expression, Type, object> QueryExecutor = (expression, type) =>
+            {
+                // #1 convert to ExpressionNode
+                // (query) => query.Where().OrderBy().Skip().Take().Select().ToList();
+                // (users) => users.SelectMany(
+                //      user => users.Where(father => (father.id == user.fatherId)).DefaultIfEmpty(),
+                //      (user, father) => new <>f__AnonymousType4`2(user = user, father = father)
+                //  ).Where().Select();
+                var isArgument = QueryableBuilder.QueryTypeNameCompare(dbContextId);
+                ExpressionNode node = dbContext.convertService.ConvertToData(expression, autoReduce: true, isArgument: isArgument);
+                //var strNode = Json.Serialize(node);
+
+
+                // #2 convert to Streams
+                // {select,left,joins,where,order,skip,take}
+                var stream = StreamReader.ReadNode(node);
+                //var strStream = Json.Serialize(stream);
+
+
+                // #3.1 ExecuteUpdate
+                if (stream is StreamToUpdate streamToUpdate)
+                {
+                    (string sql, Dictionary<string, object> sqlParam) = sqlTranslator.PrepareExecuteUpdate(streamToUpdate);
+
+                    return dbContext.Execute(sql: sql, param: (object)sqlParam);
+                }
+
+
+                // #3.3 Query
+                // #3.3.1
+                var combinedStream = stream as CombinedStream;
+                if (combinedStream == null) combinedStream = new CombinedStream("tmp") { source = stream };
+
+                // #3.3.2 execute and read result
+                switch (combinedStream.method)
+                {
+                    case nameof(Queryable_Extensions.ToExecuteString):
+                        {
+                            // ToExecuteString
+                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslator.PrepareQuery(combinedStream, entityType: null);
+                            return sql;
+                        }
+                    case "Count":
+                        {
+                            // Count
+                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslator.PrepareQuery(combinedStream, entityType: null);
+
+                            var count = dbContext.ExecuteScalar(sql: sql, param: (object)sqlParam);
+                            return Convert.ToInt32(count);
+                        }
+                    case nameof(Queryable_Extensions.ExecuteDelete):
+                        {
+                            // ExecuteDelete
+                            (string sql, Dictionary<string, object> sqlParam) = sqlTranslator.PrepareExecuteDelete(combinedStream);
+
+                            var count = dbContext.Execute(sql: sql, param: (object)sqlParam);
+                            return count;
+                        }
+                    case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                        {
+                            var entityType = expression.Type;
+                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslator.PrepareQuery(combinedStream, entityType);
+
+                            using var reader = dbContext.ExecuteReader(sql: sql, param: (object)sqlParam);
+                            return dataReader.ReadData(reader);
+                        }
+                    case "ToList":
+                    case "":
+                    case null:
+                        {
+                            // ToList
+                            var entityType = expression.Type.GetGenericArguments()?.FirstOrDefault();
+                            (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) = sqlTranslator.PrepareQuery(combinedStream, entityType);
+
+                            using var reader = dbContext.ExecuteReader(sql: sql, param: (object)sqlParam);
+                            return dataReader.ReadData(reader);
+                        }
+                }
+                throw new NotSupportedException("not supported query type: " + combinedStream.method);
+            };
+            return QueryableBuilder.Build<Entity>(QueryExecutor, dbContextId);
+        }
+
+
+
+
+
+        public override int Update(Entity entity)
+        {
+            // #1 prepare sql
+            (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) = sqlTranslator.PrepareUpdate(this);
+
+            // #2 get sql params
+            var sqlParam = GetSqlParams(entity);
+
+            // #3 execute
+            var affectedRowCount = dbContext.Execute(sql: sql, param: (object)sqlParam);
+
+            return affectedRowCount;
+        }
+
+        public override int UpdateRange(IEnumerable<Entity> entitys)
+        {
+            // #1 prepare sql
+            (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) = sqlTranslator.PrepareUpdate(this);
+
+            // #2 execute
+            var affectedRowCount = 0;
+
+            foreach (var entity in entitys)
+            {
+                var sqlParam = GetSqlParams(entity);
+                affectedRowCount += dbContext.Execute(sql: sql, param: (object)sqlParam);
+            }
+            return affectedRowCount;
+        }
+
+
+
+        public override int Delete(Entity entity)
+        {
+            var key = entityDescriptor.key.Get(entity);
+            return DeleteByKey(key);
+        }
+
+        public override int DeleteRange(IEnumerable<Entity> entitys)
+        {
+            var keys = entitys.Select(entity => entityDescriptor.key.Get(entity)).ToList();
+            return DeleteByKeys(keys);
+        }
+
+
+        public override int DeleteByKey(object keyValue)
+        {
+            // #1 prepare sql
+            string sql = sqlTranslator.PrepareDelete(this);
+
+            // #2 get sql params
+            var sqlParam = new Dictionary<string, object>();
+            sqlParam[entityDescriptor.keyName] = keyValue;
+
+            // #3 execute
+            var affectedRowCount = dbContext.Execute(sql: sql, param: (object)sqlParam);
+
+            return affectedRowCount;
+        }
+
+        public override int DeleteByKeys<Key>(IEnumerable<Key> keys)
+        {
+            // #1 prepare sql
+            string sql = sqlTranslator.PrepareDeleteRange(this);
+
+            // #2 get sql params
+            var sqlParam = new Dictionary<string, object>();
+            sqlParam["keys"] = keys;
+
+            // #3 execute
+            var affectedRowCount = dbContext.Execute(sql: sql, param: (object)sqlParam);
+
+            return affectedRowCount;
+        }
+
+    }
+}

+ 354 - 0
src/Vit.Orm/Sql/Translator/BaseQueryTranslator.cs

@@ -0,0 +1,354 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Orm.Sql;
+using System.Linq.Expressions;
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+
+namespace Vit.Orm.Sql.Translator
+{
+    public abstract class BaseQueryTranslator
+    {
+        public SqlTranslator sqlTranslator { get; protected set; }
+
+
+        public BaseQueryTranslator(SqlTranslator sqlTranslator)
+        {
+            this.sqlTranslator = sqlTranslator;
+        }
+
+
+
+        public IDbDataReader dataReader;
+        public Dictionary<string, object> sqlParam { get; protected set; } = new Dictionary<string, object>();
+
+        protected int paramIndex = 0;
+        protected string NewParamName() => "param" + (paramIndex++);
+
+
+        /// <summary>
+        /// return "*";
+        /// </summary>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        protected virtual string ReadSelect(CombinedStream stream)
+        {
+            return "*";
+        }
+
+        public virtual string BuildQuery(CombinedStream stream)
+        {
+
+            string sql = "";
+
+            // #0  select
+            sql += "select " + ReadSelect(stream);
+
+
+            #region #1 source
+            // from User u
+            sql += "\r\n from " + ReadInnerTable(stream.source);
+            #endregion
+
+            #region #2 join
+            {
+                stream.joins?.ForEach(streamToJoin =>
+                {
+                    sql += "\r\n " + (streamToJoin.joinType == EJoinType.InnerJoin ? "inner join" : "left join");
+                    sql += " " + ReadInnerTable(streamToJoin.right);
+
+                    var where = ReadEval(streamToJoin.on);
+                    if (!string.IsNullOrWhiteSpace(where)) sql += " on " + where;
+                });
+            }
+            #endregion
+
+            // #3 where 1=1
+            if (stream.where != null)
+            {
+                var where = ReadEval(stream.where);
+                if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n where " + where;
+            }
+
+            // #4 GROUP BY xxxx  Having XXX
+            if (stream.isGroupedStream)
+            {
+                #region ##1 group by
+                var node = stream.groupByFields;
+                List<string> fields = new();
+                if (node?.nodeType == NodeType.New)
+                {
+                    ExpressionNode_New newNode = node;
+                    newNode.constructorArgs.ForEach((Action<MemberBind>)(arg =>
+                    {
+                        fields.Add(this.ReadEval((ExpressionNode)arg.value));
+                    }));
+                }
+                else if (node?.nodeType == NodeType.Member)
+                {
+                    fields.Add(ReadEval(node));
+                }
+                else
+                {
+                    throw new NotSupportedException("[QueryTranslator] groupByFields is not valid: must be New or Member");
+                }
+                sql += "\r\n group by " + String.Join(", ", fields);
+                #endregion
+
+                #region ##2 having
+                if (stream.having != null)
+                {
+                    var where = ReadEval(stream.having);
+                    if (!string.IsNullOrWhiteSpace(where)) sql += "\r\n having " + where;
+                }
+                #endregion
+
+            }
+
+            // #5 OrderBy
+            if (stream.orders?.Any() == true)
+            {
+                var fields = stream.orders.Select(field => (sqlTranslator.GetSqlField(field.member) + " " + (field.asc ? "asc" : "desc"))).ToList();
+                sql += "\r\n order by " + String.Join(", ", fields);
+            }
+
+            // #6 limit 1000,10       limit {skip},{take}   |     limit {take}
+            if (stream.take != null || stream.skip != null)
+            {
+                if (stream.skip == null)
+                {
+                    sql += "\r\n limit " + stream.take;
+                }
+                else
+                {
+                    sql += "\r\n limit " + stream.skip + "," + (stream.take ?? 100000000);
+                }
+            }
+
+            return sql;
+        }
+
+        protected string ReadInnerTable(IStream stream)
+        {
+            if (stream is SourceStream sourceStream)
+            {
+                IQueryable query = sourceStream.GetSource() as IQueryable;
+                var tableName = sqlTranslator.GetTableName(query.ElementType);
+                return $"{sqlTranslator.DelimitIdentifier(tableName)} as " + stream.alias;
+            }
+            if (stream is CombinedStream baseStream)
+            {
+                var innerQuery = BuildQuery(baseStream);
+                return $"({innerQuery}) as " + stream.alias;
+            }
+            throw new NotSupportedException();
+        }
+
+
+
+        /// <summary>
+        /// read where or value or on
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        /// <exception cref="NotSupportedException"></exception>
+        protected string ReadEval(ExpressionNode data)
+        {
+            switch (data.nodeType)
+            {
+
+                case NodeType.And:
+                    ExpressionNode_And and = data;
+                    return $"({ReadEval(and.left)}) and ({ReadEval(and.right)})";
+
+                case NodeType.Or:
+                    ExpressionNode_Or or = data;
+                    return $"({ReadEval(or.left)}) or ({ReadEval(or.right)})";
+
+                case NodeType.Not:
+                    ExpressionNode_Not not = data;
+                    return $"not ({ReadEval(not.body)})";
+
+                case NodeType.ArrayIndex:
+                    throw new NotSupportedException(data.nodeType);
+                //ExpressionNode_ArrayIndex arrayIndex = data;
+                //return Expression.ArrayIndex(ToExpression(arg, arrayIndex.left), ToExpression(arg, arrayIndex.right));
+                case NodeType.Equal:
+                case NodeType.NotEqual:
+                    {
+                        ExpressionNode_Binary binary = data;
+
+                        //   "= null"  ->   "is null" ,    "!=null" -> "is not null"   
+                        if (binary.right.nodeType == NodeType.Constant && binary.right.value == null)
+                        {
+                            var opera = data.nodeType == NodeType.Equal ? "is null" : "is not null";
+                            return $"{ReadEval(binary.left)} " + opera;
+                        }
+                        else if (binary.left.nodeType == NodeType.Constant && binary.left.value == null)
+                        {
+                            var opera = data.nodeType == NodeType.Equal ? "is null" : "is not null";
+                            return $"{ReadEval(binary.right)} " + opera;
+                        }
+
+                        var @operator = operatorMap[data.nodeType];
+                        return $"{ReadEval(binary.left)} {@operator} {ReadEval(binary.right)}";
+                    }
+                case NodeType.LessThan:
+                case NodeType.LessThanOrEqual:
+                case NodeType.GreaterThan:
+                case NodeType.GreaterThanOrEqual:
+                    {
+                        ExpressionNode_Binary binary = data;
+                        var @operator = operatorMap[data.nodeType];
+                        return $"{ReadEval(binary.left)} {@operator} {ReadEval(binary.right)}";
+                    }
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall call = data;
+
+                        switch (call.methodName)
+                        {
+                            case nameof(object.ToString):
+                                {
+                                    return $"cast({ReadEval(call.@object)} as text)";
+                                }
+
+                            #region String method:  StartsWith EndsWith Contains
+                            case nameof(string.StartsWith): // String.StartsWith
+                                {
+                                    var str = call.@object;
+                                    var value = call.arguments[0];
+                                    return $"{ReadEval(str)} like {ReadEval(value)}||'%'";
+                                }
+                            case nameof(string.EndsWith): // String.EndsWith
+                                {
+                                    var str = call.@object;
+                                    var value = call.arguments[0];
+                                    return $"{ReadEval(str)} like '%'||{ReadEval(value)}";
+                                }
+                            case nameof(string.Contains) when call.methodCall_typeName == "String": // String.Contains
+                                {
+                                    var str = call.@object;
+                                    var value = call.arguments[0];
+                                    return $"{ReadEval(str)} like '%'||{ReadEval(value)}||'%'";
+                                }
+                            #endregion
+
+                            case nameof(Enumerable.Contains):
+                                {
+                                    var values = call.arguments[0];
+                                    var member = call.arguments[1];
+                                    return $"{ReadEval(member)} in {ReadEval(values)}";
+                                }
+
+                            case nameof(DbFunction.Call):
+                                {
+                                    var functionName = call.arguments[0].value as string;
+                                    var argList = call.arguments.AsQueryable().Skip(1).Select(arg => ReadEval(arg)).ToList();
+                                    var arg = string.Join(",", argList);
+                                    return $"{functionName}({arg})";
+                                }
+                        }
+                        throw new NotSupportedException("[QueryTranslator] not suported MethodCall: " + call.methodName);
+                    }
+
+
+                #region Read Value
+
+                case NodeType.Member:
+                    return sqlTranslator.GetSqlField(data);
+
+                case NodeType.Constant:
+                    ExpressionNode_Constant constant = data;
+                    var paramName = NewParamName();
+                    sqlParam[paramName] = constant.value;
+                    return "@" + paramName;
+
+                case NodeType.Convert:
+                    {
+                        // cast( 4.1 as signed)
+
+                        ExpressionNode_Convert convert = data;
+
+                        Type targetType = convert.valueType?.ToType();
+
+                        if (targetType == typeof(object)) return ReadEval(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 ReadEval(convert.body);
+                        }
+
+                        if (targetDbType == "datetime")
+                        {
+                            return $"DATETIME({ReadEval(convert.body)})";
+                        }
+                        return $"cast({ReadEval(convert.body)} as {targetDbType})";
+
+                        #region GetDbType
+                        string GetDbType(Type type)
+                        {
+                            if (type == typeof(DateTime))
+                                return "datetime";
+
+                            if (type == typeof(string))
+                                return "text";
+
+                            if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
+                                return "numeric";
+
+                            if (type == typeof(bool) || type.Name.ToLower().Contains("int")) return "integer";
+
+                            throw new NotSupportedException("[QueryTranslator] unsupported column type:" + type.Name);
+                        }
+                        #endregion
+                    }
+                case nameof(ExpressionType.Add):
+                    {
+                        ExpressionNode_Binary binary = data;
+
+                        // ##1 String Add
+                        if (data.valueType?.ToType() == typeof(string))
+                        {
+                            return $"{ReadEval(binary.left)} || {ReadEval(binary.right)}";
+                        }
+
+                        // ##2 Numberic Add
+                        return $"{ReadEval(binary.left)} + {ReadEval(binary.right)}";
+                    }
+                case nameof(ExpressionType.Coalesce):
+                    {
+                        ExpressionNode_Binary binary = data;
+                        return $"COALESCE({ReadEval(binary.left)},{ReadEval(binary.right)})";
+                    }
+                    #endregion
+            }
+            throw new NotSupportedException("[QueryTranslator] not suported nodeType: " + data.nodeType);
+        }
+
+
+
+
+
+        protected readonly static Dictionary<string, string> operatorMap = new Dictionary<string, string>
+        {
+            [NodeType.Equal] = "=",
+            [NodeType.NotEqual] = "!=",
+            [NodeType.LessThan] = "<",
+            [NodeType.LessThanOrEqual] = "<=",
+            [NodeType.GreaterThan] = ">",
+            [NodeType.GreaterThanOrEqual] = ">=",
+        };
+
+    }
+
+}

+ 130 - 0
src/Vit.Orm/Sql/Translator/QueryTranslator.cs

@@ -0,0 +1,130 @@
+using System;
+using System.Linq;
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Orm.DataReader;
+using Vit.Orm.Sql.DataReader;
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Extensions.Linq_Extensions;
+
+
+namespace Vit.Orm.Sql.Translator
+{
+    public class QueryTranslator : BaseQueryTranslator
+    {
+        /* //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;
+         */
+
+
+        /// <summary>
+        /// only used for Method ReadSelect
+        /// </summary>
+        public Type entityType { get; private set; }
+
+        public QueryTranslator(SqlTranslator sqlTranslator, Type entityType) : base(sqlTranslator)
+        {
+            this.entityType = entityType;
+        }
+
+
+        protected override string ReadSelect(CombinedStream stream)
+        {
+            switch (stream.method)
+            {
+                case "Count":
+                    {
+                        var reader = new NumScalarReader();
+                        if (this.dataReader == null) this.dataReader = reader;
+                        return "count(*)";
+                    }
+                case "" or null or "ToList" or nameof(Queryable_Extensions.ToExecuteString):
+                    {
+                        var reader = new EntityReader();
+                        return BuildReader(reader);
+                    }
+                case "FirstOrDefault" or "First" or "LastOrDefault" or "Last":
+                    {
+                        stream.take = 1;
+                        stream.skip = null;
+
+                        if (stream.method.Contains("Last"))
+                            ReverseOrder(stream);
+
+                        var nullable = stream.method.Contains("OrDefault");
+                        var reader = new FirstEntityReader { nullable = nullable };
+                        return BuildReader(reader);
+                    }
+            }
+            throw new NotSupportedException("not supported method: " + stream.method);
+
+
+            #region BuildReader
+            string BuildReader(EntityReader reader)
+            {
+                var resultEntityType = entityType;
+                ExpressionNode selectedFields = stream.select?.fields as ExpressionNode;
+                if (selectedFields == null)
+                {
+                    if (stream.joins?.Any() != true)
+                    {
+                        selectedFields = ExpressionNode.Member(parameterName: stream.source.alias, memberName: null).Member_SetType(resultEntityType);
+                    }
+                }
+
+                if (selectedFields == null)
+                    throw new NotSupportedException("select could not be null");
+
+                if (resultEntityType == null && selectedFields.nodeType == NodeType.New)
+                {
+                    resultEntityType = selectedFields.New_GetType();
+                }
+
+                //if (resultEntityType == null)
+                //    throw new NotSupportedException("resultEntityType could not be null");
+
+                var sqlFields = reader.BuildSelect(resultEntityType, sqlTranslator, sqlTranslator.dbContext.convertService, selectedFields);
+                if (dataReader == null) dataReader = reader;
+                return (stream.distinct == true ? "distinct " : "") + sqlFields;
+            }
+            #endregion
+        }
+        void ReverseOrder(CombinedStream stream)
+        {
+            stream.orders ??= new();
+            var orders = stream.orders;
+            // make sure orders exist
+            if (!orders.Any())
+            {
+                AddOrder(stream.source);
+                stream.joins?.ForEach(right => AddOrder(right.right));
+
+                #region AddOrder
+                void AddOrder(IStream source)
+                {
+                    if (source is SourceStream sourceStream)
+                    {
+                        var entityType = sourceStream.GetEntityType();
+                        var entityDescriptor = sqlTranslator.GetEntityDescriptor(entityType);
+                        if (entityDescriptor != null)
+                        {
+                            var member = ExpressionNode_RenameableMember.Member(stream: source, entityType);
+                            member.memberName = entityDescriptor.keyName;
+                            orders.Add(new OrderField { member = member, asc = true });
+                        }
+                    }
+                }
+                #endregion
+            }
+
+            // reverse order
+            orders?.ForEach(order => order.asc = !order.asc);
+        }
+
+
+    }
+}

+ 327 - 0
src/Vit.Orm/Sql/Translator/SqlTranslator.cs

@@ -0,0 +1,327 @@
+using System;
+using System.Collections.Generic;
+
+using Vit.Linq.ExpressionTree.ComponentModel;
+using Vit.Linq.ExpressionTree.CollectionsQuery;
+using Vit.Orm.Entity;
+using System.Linq;
+
+namespace Vit.Orm.Sql.Translator
+{
+    public class SqlTranslator : ISqlTranslator
+    {
+
+        public DbContext dbContext { get; private set; }
+
+        public SqlTranslator(DbContext dbContext)
+        {
+            this.dbContext = dbContext;
+        }
+
+
+        public virtual IEntityDescriptor GetEntityDescriptor(Type entityType) => dbContext.GetEntityDescriptor(entityType);
+
+
+
+
+
+        #region DelimitIdentifier
+        /// <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 virtual 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 virtual string EscapeIdentifier(string identifier) => identifier.Replace("\"", "\"\"");
+
+        /// <summary>
+        ///     Generates a valid parameter name for the given candidate name.
+        /// </summary>
+        /// <param name="name">The candidate name for the parameter.</param>
+        /// <returns>
+        ///     A valid name based on the candidate name.
+        /// </returns>
+        public virtual string GenerateParameterName(string name) => name.StartsWith("@", StringComparison.Ordinal) ? name : "@" + name;
+        #endregion
+
+
+
+
+
+        public virtual string GetTableName(Type entityType)
+        {
+            return dbContext.GetEntityDescriptor(entityType)?.tableName;
+        }
+
+        public virtual string GetSqlField(string tableName, string columnName)
+        {
+            return $"{DelimitIdentifier(tableName)}.{DelimitIdentifier(columnName)}";
+        }
+
+
+        public virtual string GetSqlField(ExpressionNode_Member member)
+        {
+            var memberName = member.memberName;
+            if (string.IsNullOrWhiteSpace(memberName))
+            {
+                memberName = dbContext.GetEntityDescriptor(member.Member_GetType())?.keyName;
+            }
+
+            // 1: {"nodeType":"Member","parameterName":"a0","memberName":"id"}
+            // 2: {"nodeType":"Member","objectValue":{"parameterName":"a0","nodeType":"Member"},"memberName":"id"}
+            return GetSqlField(member.objectValue?.parameterName ?? member.parameterName, memberName);
+        }
+
+
+        /// <summary>
+        /// functionName example:  Count, Max, Min, Sum, Average
+        /// </summary>
+        /// <param name="functionName"></param>
+        /// <param name="tableName"></param>
+        /// <param name="columnName"></param>
+        /// <returns></returns>
+        public virtual string GetSqlField_Aggregate(string functionName, string tableName, string columnName)
+        {
+            switch (functionName)
+            {
+                case nameof(Enumerable.Count):
+                    {
+                        if (columnName == null) return $"{functionName}(*)";
+                        return $"{functionName}({GetSqlField(tableName, columnName)})";
+                    }
+                case nameof(Enumerable.Max) or nameof(Enumerable.Min) or nameof(Enumerable.Sum):
+                    {
+                        return $"{functionName}({GetSqlField(tableName, columnName)})";
+                    }
+                case nameof(Enumerable.Average):
+                    {
+                        return $"AVG({GetSqlField(tableName, columnName)})";
+                    }
+            }
+            throw new NotSupportedException("[SqlTranslator] unsupported aggregate function : " + functionName);
+        }
+
+
+
+
+
+
+
+
+        public virtual 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
+            if (entityDescriptor.columns != null)
+            {
+                foreach (var column in entityDescriptor.columns)
+                {
+                    sqlFields.Add(GetColumnSql(column));
+                }
+            }
+
+            return $@"
+CREATE TABLE {DelimitIdentifier(entityDescriptor.tableName)} (
+{string.Join(",\r\n  ", sqlFields)}
+)";
+
+
+            #region GetColumnSql
+            string GetColumnSql(IColumnDescriptor column)
+            {
+                bool nullable = false;
+
+                var type = column.type;
+                if (type.IsGenericType)
+                {
+                    nullable = true;
+                    type = type.GetGenericArguments()[0];
+                }
+                // name varchar(100) DEFAULT NULL
+                return $"  {DelimitIdentifier(column.name)} {GetDbType(type)} {(nullable ? "DEFAULT NULL" : "NOT NULL")}";
+            }
+            
+            #endregion
+
+        }
+       protected virtual string GetDbType(Type 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);
+        }
+
+
+        public virtual string PrepareGet<Entity>(DbSet<Entity> dbSet)
+        {
+            /* //sql
+            delete from user where id = 7;
+            */
+            var entityDescriptor = dbSet.entityDescriptor;
+
+            // #2 build sql
+            string sql = $@"select * from {DelimitIdentifier(entityDescriptor.tableName)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)};";
+
+            return sql;
+        }
+
+        public virtual (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(CombinedStream combinedStream, Type entityType)
+        {
+            var query = new QueryTranslator(this, entityType: entityType);
+            string sql = query.BuildQuery(combinedStream);
+            return (sql, query.sqlParam, query.dataReader);
+        }
+
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(CombinedStream combinedStream)
+        {
+            throw new NotImplementedException();
+        }
+
+        public virtual (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(CombinedStream combinedStream)
+        {
+            throw new NotImplementedException();
+        }
+
+        public virtual (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) PrepareAdd<Entity>(DbSet<Entity> dbSet)
+        {
+            /* //sql
+             insert into user(name,birth,fatherId,motherId) values('','','');
+             select seq from sqlite_sequence where name='user';
+              */
+            var entityDescriptor = dbSet.entityDescriptor;
+
+            // #1 GetSqlParams 
+            Func<Entity, Dictionary<string, object>> GetSqlParams = (entity) =>
+                {
+                    var sqlParam = new Dictionary<string, object>();
+                    foreach (var column in entityDescriptor.allColumns)
+                    {
+                        var columnName = column.name;
+                        var value = column.Get(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 entityDescriptor.allColumns)
+            {
+                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}'; ";
+
+            return (sql, GetSqlParams);
+        }
+
+        public virtual (string sql, Func<Entity, Dictionary<string, object>> GetSqlParams) PrepareUpdate<Entity>(DbSet<Entity> dbSet)
+        {
+            /* //sql
+                update user set name='' where id=7;
+            */
+
+            var entityDescriptor = dbSet.entityDescriptor;
+            var sqlParam = new Dictionary<string, object>();
+
+            // #1 GetSqlParams
+            Func<Entity, Dictionary<string, object>> GetSqlParams = (entity) =>
+            {
+                var sqlParam = new Dictionary<string, object>();
+                foreach (var column in entityDescriptor.allColumns)
+                {
+                    var columnName = column.name;
+                    var value = column.Get(entity);
+
+                    sqlParam[columnName] = value;
+                }
+                //sqlParam[entityDescriptor.keyName] = entityDescriptor.key.Get(entity);
+                return sqlParam;
+            };
+
+            // #2 columns
+            List<string> columnsToUpdate = new List<string>();
+            string columnName;
+            foreach (var column in entityDescriptor.columns)
+            {
+                columnName = column.name;
+                columnsToUpdate.Add($"{DelimitIdentifier(columnName)}={GenerateParameterName(columnName)}");
+            }
+
+            // #3 build sql
+            string sql = $@"update {DelimitIdentifier(entityDescriptor.tableName)} set {string.Join(",", columnsToUpdate)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)};";
+
+            return (sql, GetSqlParams);
+        }
+
+
+        public virtual string PrepareDelete<Entity>(DbSet<Entity> dbSet)
+        {
+            /* //sql
+            delete from user where id = 7;
+            */
+            var entityDescriptor = dbSet.entityDescriptor;
+
+            // #2 build sql
+            string sql = $@"delete from {DelimitIdentifier(entityDescriptor.tableName)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)};";
+
+            return sql;
+        }
+
+        public virtual string PrepareDeleteRange<Entity>(DbSet<Entity> dbSet)
+        {
+            /* //sql
+            delete from user where id in ( 7 ) ;
+            */
+            var entityDescriptor = dbSet.entityDescriptor;
+
+            // #2 build sql
+            string sql = $@"delete from {DelimitIdentifier(entityDescriptor.tableName)} where {DelimitIdentifier(entityDescriptor.keyName)} in {GenerateParameterName("keys")};";
+
+            return sql;
+        }
+
+    }
+}

+ 19 - 0
src/Vit.Orm/Vit.Orm.csproj

@@ -0,0 +1,19 @@
+<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>
+
+    <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-ki1" />
+    </ItemGroup>
+
+</Project>