Lith 8 months ago
parent
commit
8a0f0ab9f9
88 changed files with 6706 additions and 1 deletions
  1. 101 0
      .github/workflows/ki_devops3_build.yml
  2. 45 0
      .github/workflows/ki_devops3_test.yml
  3. 201 0
      LICENSE
  4. 86 0
      Publish/DevOps3/build-bash/10.Test.bash
  5. 42 0
      Publish/DevOps3/build-bash/19.get-app-version.bash
  6. 47 0
      Publish/DevOps3/build-bash/20.change-app-version.bash
  7. 48 0
      Publish/DevOps3/build-bash/21.change-to-next-version.bash
  8. 42 0
      Publish/DevOps3/build-bash/22.add-suffix-to-app-version.bash
  9. 43 0
      Publish/DevOps3/build-bash/30.nuget-pack.sh
  10. 92 0
      Publish/DevOps3/build-bash/40.Station-publish.sh
  11. 23 0
      Publish/DevOps3/build-bash/41.extra-publish.sh
  12. 46 0
      Publish/DevOps3/build-bash/50.docker-image-copy.sh
  13. 27 0
      Publish/DevOps3/build-bash/51.docker-deploy-copy.sh
  14. 23 0
      Publish/DevOps3/build-bash/52.docker-extra-copy.sh
  15. 32 0
      Publish/DevOps3/build-bash/startup.bash
  16. 23 0
      Publish/DevOps3/build-cmd/30.nuget-pack.bat
  17. 9 0
      Publish/DevOps3/build-cmd/Clean-All.bat
  18. 1 0
      Publish/DevOps3/build-cmd/Clean-ReleaseFiles.bat
  19. BIN
      Publish/DevOps3/build-cmd/VsTool.exe
  20. 21 0
      Publish/DevOps3/environment/build-bash__40.Station-publish__#4_copyExtraReleaseFiles.sh.bak
  21. 20 0
      Publish/DevOps3/environment/build-bash__41.extra-publish.sh.bak
  22. 17 0
      Publish/DevOps3/environment/build-bash__52.docker-extra-copy.sh.bak
  23. 1 0
      Publish/DevOps3/environment/env.APPNAME.txt
  24. 1 0
      Publish/DevOps3/environment/env.envName.txt
  25. 2 0
      Publish/DevOps3/environment/jenkins_NoNeedApprovalForBuild.txt
  26. 30 0
      Publish/DevOps3/github-bash/74.github-push-to-webdav.sh
  27. 60 0
      Publish/DevOps3/github-bash/76.github-push-release.sh
  28. 71 0
      Publish/DevOps3/github-bash/startup.bash
  29. 169 0
      Publish/DevOps3/jenkins-bash/CICD.ki.git_Multibranch.deploy.jenkinsfile
  30. 43 0
      Publish/DevOps3/release-bash/71.file-zip.sh
  31. 38 0
      Publish/DevOps3/release-bash/72.nuget-push.sh
  32. 39 0
      Publish/DevOps3/release-bash/73.docker-image-build-push.sh
  33. 41 0
      Publish/DevOps3/release-bash/74.docker-image-build-push_amd64.bash
  34. 71 0
      Publish/DevOps3/release-bash/75.docker-image-build-push_cross.bash
  35. 47 0
      Publish/DevOps3/release-bash/78.push-releaseFiles-to-webdav.bash
  36. 57 0
      Publish/DevOps3/release-bash/startup.bash
  37. 306 1
      README.md
  38. 63 0
      Vitorm.MongoDB.sln
  39. 10 0
      clean-temp.bat
  40. BIN
      doc/vitorm_logo_v1.png
  41. 6 0
      src/Versions.props
  42. 20 0
      src/Vitorm.MongoDB/DataProvider.cs
  43. 58 0
      src/Vitorm.MongoDB/DbConfig.cs
  44. 13 0
      src/Vitorm.MongoDB/DbContext.cs
  45. 248 0
      src/Vitorm.MongoDB/DbSet.cs
  46. 42 0
      src/Vitorm.MongoDB/Vitorm.MongoDB.csproj
  47. 20 0
      test/Vitorm.Data.MsTest/UserBase.cs
  48. 32 0
      test/Vitorm.Data.MsTest/Vitorm.Data.MsTest.csproj
  49. 70 0
      test/Vitorm.MongoDB.Console/Program.cs
  50. 33 0
      test/Vitorm.MongoDB.Console/Program_Min.cs
  51. 14 0
      test/Vitorm.MongoDB.Console/Vitorm.MongoDB.Console.csproj
  52. 170 0
      test/Vitorm.MongoDB.MsTest/CommonTest/CRUDAsync_Test.cs
  53. 168 0
      test/Vitorm.MongoDB.MsTest/CommonTest/CRUD_Test.cs
  54. 170 0
      test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_CustomLoader_Test.cs
  55. 56 0
      test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_Test.cs
  56. 92 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Event_Test.cs
  57. 54 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDeleteAsync_Test.cs
  58. 54 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDelete_Test.cs
  59. 78 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdateAsync_Test.cs
  60. 80 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdate_Test.cs
  61. 45 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ToExecuteString_Test.cs
  62. 90 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Bool_Test.cs
  63. 58 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_DateTime_Test.cs
  64. 37 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Numeric_Calculate_Test.cs
  65. 129 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Numeric_Test.cs
  66. 39 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Calculate_Test.cs
  67. 50 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Like_Test.cs
  68. 94 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Test.cs
  69. 57 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Test.cs
  70. 237 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Count_Test.cs
  71. 73 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Distinct_Test.cs
  72. 42 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_FilterRule_Test.cs
  73. 40 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_FilterRule_WithJoin_Test.cs
  74. 183 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Group_Test.cs
  75. 218 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_InnerJoin_ByJoin_Test.cs
  76. 192 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_InnerJoin_BySelectMany_Test.cs
  77. 108 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_ByGroupJoin_Test.cs
  78. 188 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_BySelectMany_Test.cs
  79. 251 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_MethodAsync_Test.cs
  80. 393 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Method_Test.cs
  81. 89 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ScopeParam_LeftJoin_Test.cs
  82. 80 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ScopeParam_Test.cs
  83. 173 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Select_Test.cs
  84. 79 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ToListAndTotalCount_Test.cs
  85. 24 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Schema_Test.cs
  86. 57 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Truncate_Test.cs
  87. 128 0
      test/Vitorm.MongoDB.MsTest/DataSource.cs
  88. 36 0
      test/Vitorm.MongoDB.MsTest/Vitorm.MongoDB.MsTest.csproj

+ 101 - 0
.github/workflows/ki_devops3_build.yml

@@ -0,0 +1,101 @@
+# This is a basic workflow to help you get started with Actions
+
+name: ki_devops3_build
+
+# 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:
+  merge_group:
+
+# 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 "devops3_test"
+  devops3_test:
+    # 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@v4
+
+      # Runs a set of commands using the runners shell
+      - name: Run test
+        run: |
+           set -e
+           echo call ./Publish/DevOps3/build-bash/10.Test.bash to test
+
+           cd ./Publish/DevOps3/build-bash
+           bash 10.Test.bash;
+           echo run test succeed!
+
+  # This workflow contains a single job called "devops3_build"
+  devops3_build:
+    # Requiring successful dependent jobs
+    needs: devops3_test
+
+    # 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@v4
+
+      # Runs a set of commands using the runners shell
+      - name: Run build
+        run: |
+           set -e
+           echo call ./Publish/DevOps3/github-bash/startup.bash to 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

+ 45 - 0
.github/workflows/ki_devops3_test.yml

@@ -0,0 +1,45 @@
+# This is a basic workflow to help you get started with Actions
+
+name: ki_devops3_test
+
+# 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:
+
+  pull_request:
+    types: [opened, synchronize, reopened]
+  merge_group:
+
+# 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 "devops3_test"
+  devops3_test:
+    # 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@v4
+
+      # Runs a set of commands using the runners shell
+      - name: Run test
+        run: |
+           set -e
+           echo call ./Publish/DevOps3/build-bash/10.Test.bash to test
+
+           cd ./Publish/DevOps3/build-bash
+           bash 10.Test.bash;
+           echo run test succeed!

+ 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.

+ 86 - 0
Publish/DevOps3/build-bash/10.Test.bash

@@ -0,0 +1,86 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp/svn
+export NUGET_PATH=$basePath/Publish/release/.nuget
+
+# "
+
+
+#----------------------------------------------
+# init args
+if [ -z "$basePath" ]; then export basePath=$PWD/../../..; fi
+export devOpsPath="$PWD/.."
+
+if [ ! $NUGET_PATH ]; then NUGET_PATH=$basePath/Publish/release/.nuget; fi
+
+
+#----------------------------------------------
+echo "#10.Test.bash -> #1 init test environment"
+bashFile="$devOpsPath/environment/build-bash__10.Test__#1.InitEnv.sh"
+if [ -f "$bashFile" ]; then
+	echo "#10.Test.bash -> #1 init test environment - Run bash"
+	sh "$bashFile"
+fi
+
+
+#----------------------------------------------
+echo "#10.Test.bash -> #2 find test projects and run test"
+
+docker run -i --rm \
+--net=host \
+--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
+
+cd /root/code
+
+#2.1 skip if no test projects
+if grep '<test>' -r --include *.csproj; then
+	echo '#10.Test.bash -> got projects need to test'
+else
+	echo '#10.Test.bash -> skip for no project needs to test'
+	exit 0
+fi
+
+#2.2 run test
+echo '#10.Test.bash -> #2.2 run test...'
+for file in \$(grep -a '<test>' . -rl --include *.csproj)
+do
+	echo '#10.Test.bash -> #2.2.1 run test:'
+	echo run test for project \"\$file\"
+
+	# run test
+	cd /root/code
+	cd \$(dirname \"\$file\")
+	dotnet test
+done
+
+
+"
+#----------------------------------------------
+echo "#10.Test.bash -> #3 clean test environment"
+bashFile="$devOpsPath/environment/build-bash__10.Test__#3.CleanEnv.sh"
+if [ -f "$bashFile" ]; then
+	echo "#10.Test.bash -> #1 Clean test environment - Run bash"
+	sh "$bashFile"
+fi
+
+
+
+#----------------------------------------------
+
+echo '#10.Test.bash -> success!'
+
+
+
+
+

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

@@ -0,0 +1,42 @@
+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 *.props -exec grep '<Version>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);  fi
+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"
+

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

@@ -0,0 +1,47 @@
+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 *.props -exec grep '<Version>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);  fi
+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/'"<Version>$appVersion<\/Version>"'/'"<Version>$nextAppVersion<\/Version>"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+

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

@@ -0,0 +1,48 @@
+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 *.props -exec grep '<Version>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);  fi
+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/'"<Version>$appVersion<\/Version>"'/'"<Version>$nextAppVersion<\/Version>"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+
+

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

@@ -0,0 +1,42 @@
+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 *.props -exec grep '<Version>' -l {} \; | head -n 1);
+if [ ! -f "$csprojPath" ]; then csprojPath=$(find ${basePath} -name *.csproj -exec grep '<appVersion>' -l {} \; | head -n 1);  fi
+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/'"<Version>$appVersion<\/Version>"'/'"<Version>$nextAppVersion<\/Version>"'/g'  `find ${basePath} -name *.csproj -exec grep '<pack>\|<publish>' -l {} \;`
+

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

@@ -0,0 +1,43 @@
+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
+
+
+
+#----------------------------------------------
+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 "
+
+publishPath=/root/code/Publish/release/release/nuget
+
+cd /root/code
+for file in \$(grep -a '<pack>nuget</pack>' . -rl --include *.csproj)
+do
+	echo pack \$file
+	mkdir -p \$publishPath
+	cd /root/code
+	cd \$(dirname \"\$file\")
+	dotnet build --configuration Release
+	dotnet pack --configuration Release --output \"\$publishPath\"
+done
+"
+
+
+ 

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

@@ -0,0 +1,92 @@
+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
+
+cd /root/code
+
+if grep '<publish>' -r --include *.csproj; 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 | head -n 1) | grep -oP '>(.*)<' | tr -d '<>')
+echo netVersion: \$netVersion
+
+
+export publishPath=/root/code/Publish/release/release/Station\(\$netVersion\)
+mkdir -p \$publishPath
+
+echo '#2 publish station'
+for file in \$(grep -a '<publish>' . -rl --include *.csproj)
+do	
+	# get publishName
+	cd /root/code
+	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 \"\/root/code/Publish/ReleaseFile/Station\" ]; then
+	echo '#3 copy station release files'
+	\cp -rf \/root/code/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%"

+ 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

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


+ 21 - 0
Publish/DevOps3/environment/build-bash__40.Station-publish__#4_copyExtraReleaseFiles.sh.bak

@@ -0,0 +1,21 @@
+set -e
+
+#---------------------------------------------------------------------
+# args
+args_="
+
+export basePath=/root/temp
+export netVersion=net6.0
+export publishPath=\"$basePath/Publish/release/release/Station($netVersion)\"
+
+# "
+
+
+
+#----------------------------------------------
+echo '#build-bash__40.Station-publish__#4_copyExtraReleaseFiles.sh'
+
+
+
+
+

+ 20 - 0
Publish/DevOps3/environment/build-bash__41.extra-publish.sh.bak

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

+ 17 - 0
Publish/DevOps3/environment/build-bash__52.docker-extra-copy.sh.bak

@@ -0,0 +1,17 @@
+set -e
+
+
+#---------------------------------------------------------------------
+# args
+
+args_="
+
+export basePath=/root/temp
+
+# "
+
+
+#---------------------------------------------------------------------
+echo '#build-bash__52.docker-extra-copy.sh'
+
+

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

@@ -0,0 +1 @@
+Vitorm.MongoDB

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

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

+ 2 - 0
Publish/DevOps3/environment/jenkins_NoNeedApprovalForBuild.txt

@@ -0,0 +1,2 @@
+# jenkins_NoNeedApprovalForBuild.txt
+if this file exists, will not need approval for jenkins build.

+ 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
+

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

@@ -0,0 +1,71 @@
+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
+
+
+
+
+
+
+

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

@@ -0,0 +1,169 @@
+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_PATH = credentials("NUGET_PATH")
+        NUGET_SERVER = credentials("NUGET_SERVER")
+        NUGET_KEY = credentials("NUGET_KEY")
+
+        DOCKER_SERVER = credentials("DOCKER_SERVER")
+        DOCKER_ImagePrefix = "${DOCKER_SERVER}/${envName}/"
+        DOCKER_Buildx = false
+        DOCKER_USERNAME = " "
+        DOCKER_PASSWORD = " "
+
+        // set to "  "  if want to skip save releaseFiles to WebDav
+        WebDav_BaseUrl = credentials("WebDav_BaseUrl")
+        // WebDav_User = "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]"
+
+                        if ( fileExists("Publish/DevOps3/environment/jenkins_NoNeedApprovalForBuild.txt") ) {
+                            echo "-------- do not need approval for build"
+	                        env.ApprovalForBuild = "yes"
+                        } else {
+                            echo "-------- waiting approval for build"
+	                        env.ApprovalForBuild = "no"
+	                        env.ApprovalForBuild = 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.ApprovalForBuild == "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.0 build - run test') {
+            when { expression { env.ApprovalForBuild == "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 10.Test.bash;  '"
+                }
+            }
+        }
+
+        stage('#3.1 build - single platflorm') {
+            when { expression { env.ApprovalForBuild == "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.ApprovalForBuild == "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.ApprovalForBuild == "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.ApprovalForBuild == "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;  '"
+                }
+            }
+        }
+
+    }
+
+    post {
+        always {
+            timeout(time:600,unit:'SECONDS') {
+                script {
+                    env.CleanFiles = "yes"
+                    env.CleanFiles = 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 ( CleanFiles == "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"
+
+
+
+

+ 306 - 1
README.md

@@ -1 +1,306 @@
-# Vitorm.MongoDB
+
+# Vitorm.MongoDB
+Vitorm.MongoDB: an simple orm for MongoDB
+>source address: [https://github.com/Vit-Orm/Vitorm.MongoDB](https://github.com/Vit-Orm/Vitorm.MongoDB "https://github.com/Vit-Orm/Vitorm.MongoDB")    
+
+![](https://img.shields.io/github/license/Vit-Orm/Vitorm.MongoDB.svg)  
+![](https://img.shields.io/github/repo-size/Vit-Orm/Vitorm.MongoDB.svg)  ![](https://img.shields.io/github/last-commit/Vit-Orm/Vitorm.MongoDB.svg)  
+ 
+
+| Build | NuGet |
+| -------- | -------- |
+|![](https://github.com/Vit-Orm/Vitorm.MongoDB/workflows/ki_devops3_build/badge.svg) | [![](https://img.shields.io/nuget/v/Vitorm.MongoDB.svg)](https://www.nuget.org/packages/Vitorm.MongoDB) ![](https://img.shields.io/nuget/dt/Vitorm.MongoDB.svg) |
+
+
+
+
+# Vitorm.MongoDB Documentation
+This guide will walk you through the steps to set up and use Vitorm.MongoDB.
+
+supported features:
+
+| feature    |  method   |  remarks   |     |
+| --- | --- | --- | --- |
+|  create table   |  TryCreateTable   |     |     |
+|  drop table   |  TryDropTable   |     |     |
+| --- | --- | --- | --- |
+|  create records   |  Add AddRange   |     |     |
+|  retrieve  records |  Query Get   |     |     |
+|  update records   |  Update UpdateRange ExecuteUpdate  |     |     |
+|  delete records   |  Delete DeleteRange DeleteByKey DeleteByKeys ExecuteDelete   |     |     |
+| --- | --- | --- | --- |
+|  change table   |  ChangeTable    |  change mapping table from database   |   |
+|  change database  |  ChangeDatabase   | change database to be connected  |   |
+| --- | --- | --- | --- |
+|  collection total count   |  TotalCount    |  Collection Total Count without Take and Skip   |   |
+|  collection total count and list  |  ToListAndTotalCount   | query List and TotalCount in one request  |   |
+|     |     |   |   |
+
+
+## Installation
+Before using Vitorm, install the necessary package:    
+``` bash
+dotnet add package Vitorm.MongoDB
+```
+
+## Minimum viable demo
+> code address: [Program_Min.cs](https://github.com/Vit-Orm/Vitorm.MongoDB/tree/master/test/Vitorm.MongoDB.Console/Program_Min.cs)    
+``` csharp
+using System;
+using System.Linq;
+using System.Threading;
+namespace App
+{
+    public class Program_Min
+    {
+        static void Main2(string[] args)
+        {
+            // #1 Init
+            using var dbContext = new Vitorm.MongoDB.DbContext("http://localhost:9200");
+            dbContext.TryDropTable<User>();
+            dbContext.TryCreateTable<User>();
+            dbContext.Add(new User { id = 1, name = "lith" });
+            Thread.Sleep(2000);
+
+            // #2 Query
+            var user = dbContext.Get<User>(1);
+            var users = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToList();
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}
+```
+
+
+## Full Example
+> This example provides a comprehensive guide to utilizing Vitorm for basic and advanced database operations while maintaining lightweight performance.    
+> code address: [Program.cs](https://github.com/Vit-Orm/Vitorm.MongoDB/tree/master/test/Vitorm.MongoDB.Console/Program.cs)    
+``` csharp
+using System;
+using System.Linq;
+using Vitorm;
+namespace App
+{
+    public class Program
+    {
+        static void Main(string[] args)
+        {
+            // #1 Configures Vitorm
+            using var dbContext = new Vitorm.MongoDB.DbContext("http://localhost:9200");
+
+            // #2 Create Table
+            dbContext.TryDropTable<User>();
+            dbContext.TryCreateTable<User>();
+
+            // #3 Insert Records
+            dbContext.Add(new User { id = 1, name = "lith" });
+            dbContext.AddRange(new[] {
+                new User {   id = 2, name = "lith", fatherId = 1 },
+                new User {   id = 3, name = "lith", fatherId = 1 }
+            });
+
+            // #4 Query Records
+            {
+                var user = dbContext.Get<User>(1);
+                var users = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToList();
+                var sql = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToExecuteString();
+            }
+
+            // #5 Update Records
+            dbContext.Update(new User { id = 1, name = "lith1" });
+            dbContext.UpdateRange(new[] {
+                new User {   id = 2, name = "lith2", fatherId = 1 },
+                new User {   id = 3, name = "lith3", fatherId = 2 }
+            });
+            //dbContext.Query<User>().Where(u => u.name.Contains("li"))
+            //    .ExecuteUpdate(u => new User { name = "Lith" + u.id });
+
+            // #6 Delete Records
+            dbContext.Delete<User>(new User { id = 1 });
+            dbContext.DeleteRange(new[] {
+                new User {  id = 2 },
+                new User {  id = 3 }
+            });
+            dbContext.DeleteByKey<User>(1);
+            dbContext.DeleteByKeys<User, int>(new[] { 1, 2 });
+            //dbContext.Query<User>().Where(u => u.name.Contains("li"))
+            //    .ExecuteDelete();
+
+            // #7 Join Queries
+
+            // #8 Transactions
+
+            // #9 Database Functions
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}
+```
+
+## Explanation   
+1. **Setup**: Initializes the database and configures Vitorm.
+2. **Create Table**: Drops and recreates the `User` table.
+3. **Insert Records**: Adds single and multiple user records.
+4. **Query Records**: Retrieves user records using various querying methods.
+5. **Update Records**: Updates single and multiple user records.
+6. **Delete Records**: Deletes single and multiple user records.
+7. **Join Queries**: Performs a join operation between user and father records.
+8. **Transactions**: Demonstrates nested transactions and rollback/commit operations.
+9. **Database Functions**: Uses custom database functions in queries.
+
+
+
+# Vitorm.Data Documentation    
+Vitorm.Data is a static class that allows you to use Vitorm without explicitly creating or disposing of a DbContext.    
+ 
+## Installation    
+Before using Vitorm.Data, install the necessary package:    
+``` bash
+dotnet add package Vitorm.Data
+dotnet add package Vitorm.MongoDB
+```
+
+## Config settings
+``` json
+// appsettings.json
+{
+  "Vitorm": {
+    "Data": [
+      {
+        "provider": "MongoDB",
+        "namespace": "App",
+        "connectionString": "http://localhost:9200"
+      }
+    ]
+  }
+}
+```
+
+## Minimum viable demo
+> After configuring the `appsettings.json` file, you can directly perform queries without any additional configuration or initialization, `Vitorm.Data` is that easy to use.    
+> code address: [Program_Min.cs](https://github.com/Vit-Orm/Vitorm/tree/master/test/Vitorm.Data.Console/Program_Min.cs)    
+``` csharp
+using Vitorm;
+namespace App
+{
+    public class Program_Min
+    {
+        static void Main2(string[] args)
+        {
+            //  Query Records
+            var user = Data.Get<User>(1);
+            var users = Data.Query<User>().Where(u => u.name.Contains("li")).ToList();
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}
+
+```
+
+## Full Example    
+> code address: [Program.cs](https://github.com/Vit-Orm/Vitorm/tree/master/test/Vitorm.Data.Console/Program.cs)    
+``` csharp
+using Vitorm;
+
+namespace App
+{
+    public class Program
+    {
+        static void Main(string[] args)
+        {
+            // #1 No need to init Vitorm.Data
+
+            // #2 Create Table
+            Data.TryDropTable<User>();
+            Data.TryCreateTable<User>();
+
+            // #3 Insert Records
+            Data.Add(new User { id = 1, name = "lith" });
+            Data.AddRange(new[] {
+                new User { id = 2, name = "lith", fatherId = 1 },
+                new User { id = 3, name = "lith", fatherId = 1 }
+            });
+
+            // #4 Query Records
+            {
+                var user = Data.Get<User>(1);
+                var users = Data.Query<User>().Where(u => u.name.Contains("li")).ToList();
+                var sql = Data.Query<User>().Where(u => u.name.Contains("li")).ToExecuteString();
+            }
+
+            // #5 Update Records
+            Data.Update(new User { id = 1, name = "lith1" });
+            Data.UpdateRange(new[] {
+                new User { id = 2, name = "lith2", fatherId = 1 },
+                new User { id = 3, name = "lith3", fatherId = 2 }
+            });
+            Data.Query<User>().Where(u => u.name.Contains("li"))
+                .ExecuteUpdate(u => new User { name = "Lith" + u.id });
+
+            // #6 Delete Records
+            Data.Delete<User>(new User { id = 1, name = "lith1" });
+            Data.DeleteRange(new[] {
+                new User { id = 2, name = "lith2", fatherId = 1 },
+                new User { id = 3, name = "lith3", fatherId = 2 }
+            });
+            Data.DeleteByKey<User>(1);
+            Data.DeleteByKeys<User, int>(new[] { 1, 2 });
+            Data.Query<User>().Where(u => u.name.Contains("li"))
+                .ExecuteDelete();
+
+            // #7 Join Queries
+
+            // #8 Transactions
+
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}
+```
+
+
+# Examples:  
+[Test Example](https://github.com/Vit-Orm/Vitorm.MongoDB/tree/master/test/Vitorm.MongoDB.MsTest)    
+
+
+

+ 63 - 0
Vitorm.MongoDB.sln

@@ -0,0 +1,63 @@
+
+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}") = "doc", "doc", "{75C25D0B-8529-4E3F-AF43-6EC1E9E40828}"
+	ProjectSection(SolutionItems) = preProject
+		README.md = README.md
+		doc\ReleaseLog.md = doc\ReleaseLog.md
+		doc\TODO.md = doc\TODO.md
+	EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7904FE51-04FF-4477-8E3A-CC340389EE32}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{05176905-A2A5-4015-9F04-2904506C902F}"
+	ProjectSection(SolutionItems) = preProject
+		src\Versions.props = src\Versions.props
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.Data.MsTest", "test\Vitorm.Data.MsTest\Vitorm.Data.MsTest.csproj", "{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.MongoDB", "src\Vitorm.MongoDB\Vitorm.MongoDB.csproj", "{7AC01F47-AD5A-4B61-9F05-38094C1EF23B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.MongoDB.Console", "test\Vitorm.MongoDB.Console\Vitorm.MongoDB.Console.csproj", "{83947A51-9945-4851-9ABA-9661DB2069E4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vitorm.MongoDB.MsTest", "test\Vitorm.MongoDB.MsTest\Vitorm.MongoDB.MsTest.csproj", "{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7AC01F47-AD5A-4B61-9F05-38094C1EF23B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7AC01F47-AD5A-4B61-9F05-38094C1EF23B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7AC01F47-AD5A-4B61-9F05-38094C1EF23B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7AC01F47-AD5A-4B61-9F05-38094C1EF23B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{83947A51-9945-4851-9ABA-9661DB2069E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{83947A51-9945-4851-9ABA-9661DB2069E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{83947A51-9945-4851-9ABA-9661DB2069E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{83947A51-9945-4851-9ABA-9661DB2069E4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{8643E97D-C1A8-434C-A393-CB1C4AA0B0D8} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+		{7AC01F47-AD5A-4B61-9F05-38094C1EF23B} = {05176905-A2A5-4015-9F04-2904506C902F}
+		{83947A51-9945-4851-9ABA-9661DB2069E4} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+		{42E0FE60-9E3B-4B59-A69E-E50A20C89D6D} = {7904FE51-04FF-4477-8E3A-CC340389EE32}
+	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

BIN
doc/vitorm_logo_v1.png


+ 6 - 0
src/Versions.props

@@ -0,0 +1,6 @@
+<Project>
+    <PropertyGroup>
+        <Version>2.2.0-develop</Version>
+        <Vitorm_Version>[2.2.0, 2.3.0)</Vitorm_Version>
+    </PropertyGroup>
+</Project>

+ 20 - 0
src/Vitorm.MongoDB/DataProvider.cs

@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace Vitorm.MongoDB
+{
+    public partial class DataProvider : Vitorm.DataProvider.DataProvider
+    {
+        protected Dictionary<string, object> config;
+        protected DbConfig dbConfig;
+
+        public override void Init(Dictionary<string, object> config)
+        {
+            this.config = config;
+            this.dbConfig = new(config);
+        }
+
+        public override Vitorm.DbContext CreateDbContext() => new Vitorm.MongoDB.DbContext(dbConfig);
+
+
+    }
+}

+ 58 - 0
src/Vitorm.MongoDB/DbConfig.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+
+using MongoDB.Driver;
+
+namespace Vitorm.MongoDB
+{
+    public class DbConfig
+    {
+        public DbConfig(string connectionString, int? commandTimeout = null)
+        {
+            this.connectionString = connectionString;
+            this.commandTimeout = commandTimeout;
+        }
+        public DbConfig(string connectionString, string readOnlyConnectionString, int? commandTimeout = null)
+        {
+            this.connectionString = connectionString;
+            this.readOnlyConnectionString = readOnlyConnectionString;
+            this.commandTimeout = commandTimeout;
+        }
+        public DbConfig(Dictionary<string, object> config)
+        {
+            object value;
+            if (config.TryGetValue("connectionString", out value))
+                this.connectionString = value as string;
+
+            if (config.TryGetValue("readOnlyConnectionString", out value))
+                this.readOnlyConnectionString = value as string;
+
+
+            if (config.TryGetValue("database", out value))
+                this.database = value as string;
+
+
+            if (config.TryGetValue("commandTimeout", out value) && value is Int32 commandTimeout)
+                this.commandTimeout = commandTimeout;
+        }
+        public string database { get; set; }
+
+        public string connectionString { get; set; }
+        public string readOnlyConnectionString { get; set; }
+        public int? commandTimeout { get; set; }
+
+        MongoClient client;
+        MongoClient readOnlyClient;
+
+        MongoClient Client => client ?? (client = new MongoClient(connectionString));
+        MongoClient ReadOnlyClient => readOnlyClient ?? (readOnlyClient = new MongoClient(readOnlyConnectionString));
+
+        public IMongoDatabase GetDatabase() => Client.GetDatabase(database);
+        public IMongoDatabase GetReadOnlyDatabase() => ReadOnlyClient.GetDatabase(database);
+
+
+
+
+        internal string dbHashCode => connectionString.GetHashCode().ToString();
+    }
+}

+ 13 - 0
src/Vitorm.MongoDB/DbContext.cs

@@ -0,0 +1,13 @@
+namespace Vitorm.MongoDB
+{
+    public partial class DbContext : Vitorm.DbContext
+    {
+        public DbConfig dbConfig { get; protected set; }
+
+        public DbContext(DbConfig dbConfig) : base(DbSetConstructor.CreateDbSet)
+        {
+            this.dbConfig = dbConfig;
+        }
+
+    }
+}

+ 248 - 0
src/Vitorm.MongoDB/DbSet.cs

@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using MongoDB.Driver;
+
+using Vit.Linq.FilterRules;
+using Vit.Linq.FilterRules.ComponentModel;
+
+using Vitorm.Entity;
+
+namespace Vitorm.MongoDB
+{
+    // https://www.mongodb.com/docs/drivers/csharp/current/
+
+
+    public class DbSetConstructor
+    {
+        public static IDbSet CreateDbSet(IDbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            return _CreateDbSet.MakeGenericMethod(entityDescriptor.entityType)
+                     .Invoke(null, new object[] { dbContext, entityDescriptor }) as IDbSet;
+        }
+
+        static readonly MethodInfo _CreateDbSet = new Func<DbContext, IEntityDescriptor, IDbSet>(CreateDbSet<object>)
+                   .Method.GetGenericMethodDefinition();
+        public static IDbSet<Entity> CreateDbSet<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            return new DbSet<Entity>(dbContext, entityDescriptor);
+        }
+
+    }
+
+
+    public partial class DbSet<Entity> : IDbSet<Entity>
+    {
+        public virtual IDbContext dbContext { get; protected set; }
+        public virtual DbContext DbContext => (DbContext)dbContext;
+
+
+        protected IEntityDescriptor _entityDescriptor;
+        public virtual IEntityDescriptor entityDescriptor => _entityDescriptor;
+
+
+        public DbSet(DbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            this.dbContext = dbContext;
+            this._entityDescriptor = entityDescriptor;
+        }
+
+        // #0 Schema :  ChangeTable
+        public virtual IEntityDescriptor ChangeTable(string tableName) => _entityDescriptor = _entityDescriptor.WithTable(tableName);
+        public virtual IEntityDescriptor ChangeTableBack() => _entityDescriptor = _entityDescriptor.GetOriginEntityDescriptor();
+
+        public IMongoDatabase database => DbContext.dbConfig.GetDatabase();
+        public IMongoCollection<Entity> collection => database.GetCollection<Entity>(entityDescriptor.tableName);
+
+
+        #region #0 Schema :  Create Drop
+
+        public virtual bool TableExist()
+        {
+            var names = database.ListCollectionNames().ToList();
+            return names.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+        }
+        public virtual async Task<bool> TableExistAsync()
+        {
+            var names = await (await database.ListCollectionNamesAsync()).ToListAsync();
+            return names.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+        }
+
+        public virtual void TryCreateTable()
+        {
+            if (!TableExist())
+                database.CreateCollection(entityDescriptor.tableName);
+        }
+        public virtual async Task TryCreateTableAsync()
+        {
+            if (!await TableExistAsync())
+                await database.CreateCollectionAsync(entityDescriptor.tableName);
+        }
+
+        public virtual void TryDropTable() => database.DropCollection(entityDescriptor.tableName);
+        public virtual Task TryDropTableAsync() => database.DropCollectionAsync(entityDescriptor.tableName);
+
+
+        public virtual void Truncate() => collection.DeleteMany(m => true);
+
+        public virtual Task TruncateAsync() => collection.DeleteManyAsync(m => true);
+
+        #endregion
+
+
+        #region #1 Create :  Add AddRange
+        public virtual Entity Add(Entity entity)
+        {
+            collection.InsertOne(entity);
+            return entity;
+        }
+
+        public virtual void AddRange(IEnumerable<Entity> entities)
+        {
+            collection.InsertMany(entities);
+        }
+
+        public virtual async Task<Entity> AddAsync(Entity entity)
+        {
+            await collection.InsertOneAsync(entity);
+            return entity;
+        }
+
+        public virtual Task AddRangeAsync(IEnumerable<Entity> entities)
+        {
+            return collection.InsertManyAsync(entities);
+        }
+        #endregion
+
+
+        #region #2 Retrieve : Get Query
+
+        public virtual Entity Get(object keyValue)
+        {
+            var predicate = GetKeyPredicate(keyValue);
+            return collection.Find(predicate).FirstOrDefault();
+        }
+
+        public virtual Task<Entity> GetAsync(object keyValue)
+        {
+            var predicate = GetKeyPredicate(keyValue);
+            return collection.Find(predicate).FirstOrDefaultAsync();
+        }
+
+        public virtual IQueryable<Entity> Query()
+        {
+            return collection.AsQueryable();
+        }
+
+        #endregion
+
+
+        public virtual Expression<Func<Entity, bool>> GetKeyPredicate(object keyValue)
+        {
+            var filter = new FilterRule { field = entityDescriptor.key.propertyName, @operator = "=", value = keyValue };
+            var predicate = FilterService.Instance.ConvertToCode_PredicateExpression<Entity>(filter);
+            return predicate;
+        }
+
+        public virtual Expression<Func<Entity, bool>> GetKeyPredicate<Key>(IEnumerable<Key> keys)
+        {
+            var filter = new FilterRule { field = entityDescriptor.key.propertyName, @operator = "In", value = keys };
+            var predicate = FilterService.Instance.ConvertToCode_PredicateExpression<Entity>(filter);
+            return predicate;
+        }
+
+        #region #3 Update: Update UpdateRange
+        public virtual int Update(Entity entity)
+        {
+            var predicate = GetKeyPredicate(entityDescriptor.key.GetValue(entity));
+            var result = collection.ReplaceOne(predicate, entity);
+            return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
+        }
+        public virtual async Task<int> UpdateAsync(Entity entity)
+        {
+            var predicate = GetKeyPredicate(entityDescriptor.key.GetValue(entity));
+            var result = await collection.ReplaceOneAsync(predicate, entity);
+            return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
+        }
+
+        public virtual int UpdateRange(IEnumerable<Entity> entities)
+        {
+            return entities.Select(entity => Update(entity)).Sum();
+        }
+        public virtual async Task<int> UpdateRangeAsync(IEnumerable<Entity> entities)
+        {
+            int count = 0;
+            foreach (var entity in entities)
+                count += await UpdateAsync(entity);
+            return count;
+        }
+        #endregion
+
+
+        #region #4 Delete : Delete DeleteRange DeleteByKey DeleteByKeys
+
+        public virtual int Delete(Entity entity)
+        {
+            var keyValue = entityDescriptor.key.GetValue(entity);
+            return DeleteByKey(keyValue);
+        }
+        public virtual Task<int> DeleteAsync(Entity entity)
+        {
+            var keyValue = entityDescriptor.key.GetValue(entity);
+            return DeleteByKeyAsync(keyValue);
+        }
+
+
+
+        public virtual int DeleteRange(IEnumerable<Entity> entities)
+        {
+            var keys = entities.Select(entity => entityDescriptor.key.GetValue(entity));
+            return DeleteByKeys(keys);
+        }
+        public virtual Task<int> DeleteRangeAsync(IEnumerable<Entity> entities)
+        {
+            var keys = entities.Select(entity => entityDescriptor.key.GetValue(entity));
+            return DeleteByKeysAsync<object>(keys);
+        }
+
+
+
+        public virtual int DeleteByKey(object keyValue)
+        {
+            var predicate = GetKeyPredicate(keyValue);
+            var result = collection.DeleteOne(predicate);
+            return result.IsAcknowledged ? (int)result.DeletedCount : 0;
+        }
+        public virtual async Task<int> DeleteByKeyAsync(object keyValue)
+        {
+            var predicate = GetKeyPredicate(keyValue);
+            var result = await collection.DeleteOneAsync(predicate);
+            return result.IsAcknowledged ? (int)result.DeletedCount : 0;
+        }
+
+
+
+        public virtual int DeleteByKeys<Key>(IEnumerable<Key> keys)
+        {
+            var predicate = GetKeyPredicate<Key>(keys);
+            var result = collection.DeleteMany(predicate);
+            return result.IsAcknowledged ? (int)result.DeletedCount : 0;
+        }
+        public virtual async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys)
+        {
+            var predicate = GetKeyPredicate<Key>(keys);
+            var result = await collection.DeleteManyAsync(predicate);
+            return result.IsAcknowledged ? (int)result.DeletedCount : 0;
+        }
+
+        #endregion
+
+
+
+
+    }
+}

+ 42 - 0
src/Vitorm.MongoDB/Vitorm.MongoDB.csproj

@@ -0,0 +1,42 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <Import Project="..\Versions.props" />
+
+    <PropertyGroup>
+        <pack>nuget</pack>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.0</TargetFramework>
+        <LangVersion>9.0</LangVersion>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Authors>Lith</Authors>
+        <Description>orm for MongoDB</Description>
+        <PackageProjectUrl>https://github.com/Vit-Orm/Vitorm.MongoDB</PackageProjectUrl>
+        <PackageIcon>vitorm_logo_v1.png</PackageIcon>
+        <PackageReadmeFile>README.md</PackageReadmeFile>
+        <PackageTags>orm vitorm database MongoDB</PackageTags>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Include="..\..\doc\vitorm_logo_v1.png">
+            <Pack>True</Pack>
+            <PackagePath>\</PackagePath>
+        </None>
+        <None Include="..\..\README.md">
+            <Pack>True</Pack>
+            <PackagePath>\</PackagePath>
+        </None>
+    </ItemGroup>
+
+    <ItemGroup>
+        <PackageReference Include="MongoDB.Driver" Version="2.28.0" />
+        <!--<PackageReference Include="Vitorm" Version="$(Vitorm_Version)" />-->
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\..\Vitorm\src\Vitorm\Vitorm.csproj" />
+    </ItemGroup>
+
+</Project>

+ 20 - 0
test/Vitorm.Data.MsTest/UserBase.cs

@@ -0,0 +1,20 @@
+
+namespace Vitorm.MsTest
+{
+
+    [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+    public class UserBase
+    {
+        [System.ComponentModel.DataAnnotations.Key]
+        public virtual int id { get; set; }
+        public string name { get; set; }
+        public DateTime? birth { get; set; }
+
+        public int? fatherId { get; set; }
+        public int? motherId { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
+        public string test { get; set; }
+    }
+
+}

+ 32 - 0
test/Vitorm.Data.MsTest/Vitorm.Data.MsTest.csproj

@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <test>MSTest</test>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
+
+        <PackageReference Include="Vit.Core" Version="2.2.0" />
+        <PackageReference Include="Vitorm.Data" Version="[2.1.0, 2.2.0)" />
+    </ItemGroup>
+
+
+    <ItemGroup>
+        <None Update="appsettings.json">
+            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+</Project>

+ 70 - 0
test/Vitorm.MongoDB.Console/Program.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Linq;
+
+using Vitorm;
+namespace App
+{
+    public class Program
+    {
+        static void Main(string[] args)
+        {
+            // #1 Configures Vitorm
+            using var dbContext = new Vitorm.ElasticSearch.DbContext("http://localhost:9200");
+
+            // #2 Create Table
+            dbContext.TryDropTable<User>();
+            dbContext.TryCreateTable<User>();
+
+            // #3 Insert Records
+            dbContext.Add(new User { id = 1, name = "lith" });
+            dbContext.AddRange(new[] {
+                new User {   id = 2, name = "lith", fatherId = 1 },
+                new User {   id = 3, name = "lith", fatherId = 1 }
+            });
+
+            // #4 Query Records
+            {
+                var user = dbContext.Get<User>(1);
+                var users = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToList();
+                var sql = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToExecuteString();
+            }
+
+            // #5 Update Records
+            dbContext.Update(new User { id = 1, name = "lith1" });
+            dbContext.UpdateRange(new[] {
+                new User {   id = 2, name = "lith2", fatherId = 1 },
+                new User {   id = 3, name = "lith3", fatherId = 2 }
+            });
+            //dbContext.Query<User>().Where(u => u.name.Contains("li"))
+            //    .ExecuteUpdate(u => new User { name = "Lith" + u.id });
+
+            // #6 Delete Records
+            dbContext.Delete<User>(new User { id = 1 });
+            dbContext.DeleteRange(new[] {
+                new User {  id = 2 },
+                new User {  id = 3 }
+            });
+            dbContext.DeleteByKey<User>(1);
+            dbContext.DeleteByKeys<User, int>(new[] { 1, 2 });
+            //dbContext.Query<User>().Where(u => u.name.Contains("li"))
+            //    .ExecuteDelete();
+
+            // #7 Join Queries
+
+            // #8 Transactions
+
+            // #9 Database Functions
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}

+ 33 - 0
test/Vitorm.MongoDB.Console/Program_Min.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using System.Threading;
+namespace App
+{
+    public class Program_Min
+    {
+        static void Main2(string[] args)
+        {
+            // #1 Init
+            using var dbContext = new Vitorm.ElasticSearch.DbContext("http://localhost:9200");
+            dbContext.TryDropTable<User>();
+            dbContext.TryCreateTable<User>();
+            dbContext.Add(new User { id = 1, name = "lith" });
+            Thread.Sleep(2000);
+
+            // #2 Query
+            var user = dbContext.Get<User>(1);
+            var users = dbContext.Query<User>().Where(u => u.name.Contains("li")).ToList();
+        }
+
+        // Entity Definition
+        [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+        public class User
+        {
+            [System.ComponentModel.DataAnnotations.Key]
+            public int id { get; set; }
+            public string name { get; set; }
+            public DateTime? birth { get; set; }
+            public int? fatherId { get; set; }
+        }
+    }
+}

+ 14 - 0
test/Vitorm.MongoDB.Console/Vitorm.MongoDB.Console.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net6.0</TargetFramework>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\..\src\Vitorm.ElasticSearch\Vitorm.MongoDB.csproj" />
+    </ItemGroup>
+
+
+</Project>

+ 170 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/CRUDAsync_Test.cs

@@ -0,0 +1,170 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public partial class CRUDAsync_Test
+    {
+        static DbContext CreateDbContext() => DataSource.CreateDbContextForWriting();
+
+        #region #0 Schema
+        [TestMethod]
+        public async Task Test_Schema()
+        {
+            using var dbContext = CreateDbContext();
+
+            await dbContext.TryDropTableAsync<User>();
+            await dbContext.TryDropTableAsync<User>();
+
+            await dbContext.TryCreateTableAsync<User>();
+            await dbContext.TryCreateTableAsync<User>();
+        }
+        #endregion
+
+
+        #region #1 Create
+        [TestMethod]
+        public async Task Test_Create()
+        {
+            using var dbContext = CreateDbContext();
+
+            var newUserList = User.NewUsers(7, 4, forAdd: true);
+
+
+            // #1 Add
+            await dbContext.AddAsync(newUserList[0]);
+
+            // #2 AddRange
+            await dbContext.AddRangeAsync(newUserList.Skip(1));
+
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().Where(user => user.id >= 7).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+            try
+            {
+                await dbContext.AddAsync(newUserList[0]);
+                Assert.Fail("should not be able to add same key twice");
+            }
+            catch (Exception ex) when (ex is not AssertFailedException)
+            {
+            }
+
+        }
+        #endregion
+
+        #region #2 Retrieve : Get Query
+        [TestMethod]
+        public async Task Test_Retrieve()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Get
+            {
+                var user = await dbContext.GetAsync<User>(1);
+                Assert.AreEqual(1, user.id);
+            }
+
+            // #2 Query
+            {
+                var userList = await dbContext.Query<User>().ToListAsync();
+                Assert.AreEqual(6, userList.Count());
+            }
+        }
+        #endregion
+
+
+        #region #3 Update
+        [TestMethod]
+        public async Task Test_Update()
+        {
+            using var dbContext = CreateDbContext();
+
+            // Update
+            {
+                var rowCount = await dbContext.UpdateAsync(User.NewUser(4));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // UpdateRange
+            {
+                var rowCount = await dbContext.UpdateRangeAsync(User.NewUsers(5, 3));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var newUserList = User.NewUsers(4, 3, forAdd: false);
+                var userList = dbContext.Query<User>().Where(m => m.id >= 4).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+        }
+        #endregion
+
+
+        #region #4 Delete
+        [TestMethod]
+        public async Task Test_Delete()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Delete
+            {
+                var rowCount = await dbContext.DeleteAsync(User.NewUser(1));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #2 DeleteRange
+            {
+                var rowCount = await dbContext.DeleteRangeAsync(User.NewUsers(2, 2));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            // #3 DeleteByKey
+            {
+                var user = User.NewUser(4);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValue = key.GetValue(user);
+                var rowCount = await dbContext.DeleteByKeyAsync<User>(keyValue);
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #4 DeleteByKeys
+            {
+                var users = User.NewUsers(5, 2);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValues = users.Select(user => key.GetValue(user));
+                var rowCount = await dbContext.DeleteByKeysAsync<User, object>(keyValues);
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().ToList();
+                Assert.AreEqual(0, userList.Count());
+            }
+        }
+        #endregion
+
+
+    }
+}

+ 168 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/CRUD_Test.cs

@@ -0,0 +1,168 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public partial class CRUD_Test
+    {
+        static DbContext CreateDbContext() => DataSource.CreateDbContextForWriting();
+
+        #region #0 Schema
+        [TestMethod]
+        public void Test_Schema()
+        {
+            using var dbContext = CreateDbContext();
+
+            dbContext.TryDropTable<User>();
+            dbContext.TryDropTable<User>();
+
+            dbContext.TryCreateTable<User>();
+            dbContext.TryCreateTable<User>();
+        }
+        #endregion
+
+        #region #1 Create
+        [TestMethod]
+        public void Test_Create()
+        {
+            using var dbContext = CreateDbContext();
+
+            var newUserList = User.NewUsers(7, 4, forAdd: true);
+
+
+            // #1 Add
+            dbContext.Add(newUserList[0]);
+
+            // #2 AddRange
+            dbContext.AddRange(newUserList.Skip(1));
+
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().Where(user => user.id >= 7).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+            try
+            {
+                dbContext.Add(newUserList[0]);
+                Assert.Fail("should not be able to add same key twice");
+            }
+            catch (Exception ex) when (ex is not AssertFailedException)
+            {
+            }
+
+
+        }
+        #endregion
+
+        #region #2 Retrieve : Get Query
+        [TestMethod]
+        public void Test_Retrieve()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Get
+            {
+                var user = dbContext.Get<User>(1);
+                Assert.AreEqual(1, user.id);
+            }
+
+            // #2 Query
+            {
+                var userList = dbContext.Query<User>().ToList();
+                Assert.AreEqual(6, userList.Count());
+            }
+        }
+        #endregion
+
+
+        #region #3 Update
+        [TestMethod]
+        public void Test_Update()
+        {
+            using var dbContext = CreateDbContext();
+
+            // Update
+            {
+                var rowCount = dbContext.Update(User.NewUser(4));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // UpdateRange
+            {
+                var rowCount = dbContext.UpdateRange(User.NewUsers(5, 3));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var newUserList = User.NewUsers(4, 3, forAdd: false);
+                var userList = dbContext.Query<User>().Where(m => m.id >= 4).ToList();
+                Assert.AreEqual(newUserList.Count, userList.Count());
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(newUserList.Select(m => m.id)).Count());
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(newUserList.Select(m => m.name)).Count());
+            }
+
+        }
+        #endregion
+
+
+        #region #4 Delete
+        [TestMethod]
+        public void Test_Delete()
+        {
+            using var dbContext = CreateDbContext();
+
+            // #1 Delete
+            {
+                var rowCount = dbContext.Delete(User.NewUser(1));
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #2 DeleteRange
+            {
+                var rowCount = dbContext.DeleteRange(User.NewUsers(2, 2));
+                Assert.AreEqual(2, rowCount);
+            }
+
+            // #3 DeleteByKey
+            {
+                var user = User.NewUser(4);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValue = key.GetValue(user);
+                var rowCount = dbContext.DeleteByKey<User>(keyValue);
+                Assert.AreEqual(1, rowCount);
+            }
+
+            // #4 DeleteByKeys
+            {
+                var users = User.NewUsers(5, 2);
+                var key = dbContext.GetEntityDescriptor(typeof(User)).key;
+                var keyValues = users.Select(user => key.GetValue(user));
+                var rowCount = dbContext.DeleteByKeys<User, object>(keyValues);
+                Assert.AreEqual(2, rowCount);
+            }
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var userList = dbContext.Query<User>().ToList();
+                Assert.AreEqual(0, userList.Count());
+            }
+        }
+        #endregion
+
+
+    }
+}

+ 170 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_CustomLoader_Test.cs

@@ -0,0 +1,170 @@
+using System.Reflection;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vitorm.Entity;
+using Vitorm.Entity.Loader.DataAnnotations;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class EntityLoader_CustomLoader_Test
+    {
+        [TestMethod]
+        public void Test_EntityDescriptor()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(User));
+            var key = entityDescriptor.key;
+
+            Assert.AreEqual("userId", key.columnName);
+        }
+
+
+        [TestMethod]
+        public void Test_EntityLoader()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // #1 EntityLoaderAttribute
+            {
+                var users = dbContext.Query<CustomUser2>().Where(m => m.name == "u146").ToList();
+                var sql = dbContext.Query<CustomUser2>().Where(m => m.name == "u146").ToExecuteString();
+                Assert.AreEqual(1, users.Count());
+                Assert.AreEqual(1, users[0].id);
+            }
+
+            // #2 defaultEntityLoader
+            {
+                EntityLoaders.Instance.loaders.Insert(0, new CustomEntityLoaderAttribute());
+
+                var users = dbContext.Query<CustomUser>().Where(m => m.name == "u146").ToList();
+                Assert.AreEqual(1, users.Count());
+                Assert.AreEqual(1, users[0].id);
+            }
+
+        }
+
+
+        #region Custom Entity
+
+        [CustomEntityLoader]
+        public class CustomUser2 : CustomUser
+        {
+        }
+
+
+        [Property(name = "TableName", value = "User")]
+        //[Property(name = "Schema", value = "orm")]
+        public class CustomUser
+        {
+            [Label("Key")]
+            [Label("Identity")]
+            [Property(name = "ColumnName", value = "userId")]
+            public int id { get; set; }
+
+            [Property(name = "ColumnName", value = "userName")]
+            [Property(name = "TypeName", value = "varchar(1000)")]
+            [Label("Required")]
+            public string name { get; set; }
+
+            [Property(name = "ColumnName", value = "userBirth")]
+            public DateTime? birth { get; set; }
+
+            [Property(name = "ColumnName", value = "userFatherId")]
+            public int? fatherId { get; set; }
+            [Property(name = "ColumnName", value = "userMotherId")]
+            public int? motherId { get; set; }
+
+            [Label("NotMapped")]
+            public string test { get; set; }
+
+            public static CustomUser NewUser(int id, bool forAdd = false) => new CustomUser { id = id, name = "testUser" + id };
+        }
+
+        #endregion
+
+
+        #region Custom EntityLoader Attribute
+
+        [System.AttributeUsage(System.AttributeTargets.All, Inherited = true, AllowMultiple = true)]
+        public class LabelAttribute : Attribute
+        {
+            public LabelAttribute(string label) { this.label = label; }
+            public string label { get; init; }
+        }
+
+        [System.AttributeUsage(System.AttributeTargets.All, Inherited = true, AllowMultiple = true)]
+        public class PropertyAttribute : Attribute
+        {
+            public string name { get; set; }
+            public string value { get; set; }
+        }
+        #endregion
+
+        #region Custom EntityLoader
+        public class CustomEntityLoaderAttribute : Attribute, IEntityLoader
+        {
+            public void CleanCache() { }
+            public (bool success, IEntityDescriptor entityDescriptor) LoadDescriptor(Type entityType) => LoadFromType(entityType);
+            public (bool success, IEntityDescriptor entityDescriptor) LoadDescriptorWithoutCache(Type entityType) => LoadFromType(entityType);
+
+            public static bool GetTableName(Type entityType, out string tableName, out string schema)
+            {
+                var properties = entityType?.GetCustomAttributes<PropertyAttribute>();
+                tableName = properties?.FirstOrDefault(attr => attr.name == "TableName")?.value;
+                schema = properties?.FirstOrDefault(attr => attr.name == "Schema")?.value;
+                return tableName != null;
+            }
+
+            public static (bool success, IEntityDescriptor EntityDescriptor) LoadFromType(Type entityType)
+            {
+                if (!GetTableName(entityType, out var tableName, out var schema)) return default;
+
+                IColumnDescriptor[] allColumns = entityType?.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                    .Select(propertyInfo =>
+                    {
+                        var labels = propertyInfo?.GetCustomAttributes<LabelAttribute>();
+                        var properties = propertyInfo?.GetCustomAttributes<PropertyAttribute>();
+
+                        if (labels.Any(m => m.label == "NotMapped")) return null;
+
+                        // #1 isKey
+                        bool isKey = labels.Any(m => m.label == "Key");
+
+                        // #2 column name and type
+                        var columnName = properties.FirstOrDefault(attr => attr.name == "ColumnName")?.value ?? propertyInfo.Name;
+                        var columnDbType = properties.FirstOrDefault(attr => attr.name == "TypeName")?.value;
+                        int? columnOrder = int.TryParse(properties.FirstOrDefault(attr => attr.name == "ColumnOrder")?.value, out var order) ? order : null;
+
+                        // #3 isIdentity
+                        var isIdentity = labels.Any(m => m.label == "Identity");
+
+                        // #4 isNullable
+                        bool isNullable;
+                        if (labels.Any(m => m.label == "Required")) isNullable = false;
+                        else
+                        {
+                            var type = propertyInfo.PropertyType;
+                            if (type == typeof(string)) isNullable = true;
+                            else
+                            {
+                                isNullable = TypeUtil.IsNullable(type);
+                            }
+                        }
+
+                        return new ColumnDescriptor(
+                            propertyInfo, columnName: columnName,
+                            isKey: isKey, isIdentity: isIdentity, isNullable: isNullable,
+                            columnDbType: columnDbType,
+                            columnOrder: columnOrder
+                            );
+                    }).Where(column => column != null).ToArray();
+
+                return (true, new EntityDescriptor(entityType, allColumns, tableName, schema));
+            }
+        }
+        #endregion
+
+    }
+}

+ 56 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_Test.cs

@@ -0,0 +1,56 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class EntityLoader_Test
+    {
+        [TestMethod]
+        public void Test_EntityDescriptor()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            {
+                var entityDescriptor = dbContext.GetEntityDescriptor<User>();
+                Assert.AreEqual("id", entityDescriptor.key?.columnName);
+                Assert.AreEqual("User", entityDescriptor.tableName);
+            }
+
+            {
+                var entityDescriptor = dbContext.GetEntityDescriptor<User2>();
+                Assert.IsNull(entityDescriptor.key);
+                Assert.AreEqual("User", entityDescriptor.tableName);
+            }
+
+            {
+                var entityDescriptor = dbContext.GetEntityDescriptor<User3>();
+                Assert.IsNull(entityDescriptor);
+            }
+        }
+
+
+        #region Custom Entity
+
+        public class User
+        {
+            public int id { get; set; }
+            public string name { get; set; }
+        }
+
+        [Vitorm.Entity.Loader.DataAnnotations.StrictEntityLoader]
+        [Table("User")]
+        public class User2 : User { }
+
+
+        [Vitorm.Entity.Loader.DataAnnotations.StrictEntityLoader]
+        public class User3 : User { }
+
+        #endregion
+
+
+
+
+    }
+}

+ 92 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Event_Test.cs

@@ -0,0 +1,92 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public partial class Event_Test
+    {
+        class AssertDisposable : IDisposable
+        {
+            public AssertDisposable()
+            {
+                executeString = null;
+            }
+            public void Dispose()
+            {
+                Assert.IsNotNull(executeString);
+            }
+        }
+        static string executeString;
+
+        static Event_Test()
+        {
+            DbContext.event_DefaultOnExecuting = (arg) =>
+            {
+                executeString = arg.executeString;
+            };
+        }
+
+
+        [TestMethod]
+        public void Test()
+        {
+            using var dbContext = DataSource.CreateDbContextForWriting();
+            AssertDisposable assertDisposable;
+
+            // TryCreateTable
+            using (assertDisposable = new())
+            {
+                dbContext.TryCreateTable<User>();
+            }
+
+
+            var newUserList = User.NewUsers(7, 4, forAdd: true);
+            // Add
+            using (assertDisposable = new())
+            {
+                dbContext.Add(newUserList[0]);
+            }
+
+            // AddRange
+            using (assertDisposable = new())
+            {
+                dbContext.AddRange(newUserList.Skip(1));
+            }
+
+
+            // Get
+            using (assertDisposable = new())
+            {
+                var user = dbContext.Get<User>(1);
+            }
+
+            // Query
+            using (assertDisposable = new())
+            {
+                var result = dbContext.Query<User>().ToList();
+            }
+            using (assertDisposable = new())
+            {
+                var result = dbContext.Query<User>().Count();
+            }
+            using (assertDisposable = new())
+            {
+                var result = dbContext.Query<User>().ToExecuteString();
+            }
+            using (assertDisposable = new())
+            {
+                var result = dbContext.Query<User>().FirstOrDefault();
+            }
+            using (assertDisposable = new())
+            {
+                var result = dbContext.Query<User>().ToListAndTotalCount();
+            }
+
+        }
+
+
+    }
+}

+ 54 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDeleteAsync_Test.cs

@@ -0,0 +1,54 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Orm_Extensions_ExecuteDeleteAsync_Test
+    {
+
+        [TestMethod]
+        public async Task Test_ExecuteDelete()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father
+                            };
+
+                var rowCount = await query.ExecuteDeleteAsync();
+
+                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);
+            }
+
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = await userQuery.Where(m => m.id == 2 || m.id == 4).ExecuteDeleteAsync();
+
+                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);
+            }
+        }
+    }
+}

+ 54 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDelete_Test.cs

@@ -0,0 +1,54 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Orm_Extensions_ExecuteDelete_Test
+    {
+
+        [TestMethod]
+        public void Test_ExecuteDelete()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father
+                            };
+
+                var rowCount = query.ExecuteDelete();
+
+                Assert.AreEqual(3, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(3, newUsers.Count());
+                Assert.AreEqual(4, newUsers.First().id);
+                Assert.AreEqual(6, newUsers.Last().id);
+            }
+
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var userQuery = dbContext.Query<User>();
+
+                var rowCount = userQuery.Where(m => m.id == 2 || m.id == 4).ExecuteDelete();
+
+                Assert.AreEqual(2, rowCount);
+
+                var newUsers = userQuery.ToList();
+                Assert.AreEqual(4, newUsers.Count());
+                Assert.AreEqual(1, newUsers.First().id);
+                Assert.AreEqual(3, newUsers[1].id);
+                Assert.AreEqual(5, newUsers[2].id);
+            }
+        }
+    }
+}

+ 78 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdateAsync_Test.cs

@@ -0,0 +1,78 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Orm_Extensions_ExecuteUpdateAsync_Test
+    {
+        [TestMethod]
+        public async Task Test_ExecuteUpdate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = await userQuery.ExecuteUpdateAsync(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? ""),
+                    birth = DateTime.Parse("2021-01-11 00:00:00")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                Assert.AreEqual(DateTime.Parse("2021-01-11 00:00:00"), userList.First().birth);
+                Assert.AreEqual("u_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = await query.ExecuteUpdateAsync(row => new User
+                {
+                    name = "u2_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(6, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u2_1_4_6", userList.First().name);
+                Assert.AreEqual("u2_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = await query.ExecuteUpdateAsync(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);
+            }
+        }
+    }
+}

+ 80 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdate_Test.cs

@@ -0,0 +1,80 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Orm_Extensions_ExecuteUpdate_Test
+    {
+
+        [TestMethod]
+        public void Test_ExecuteUpdate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = userQuery.ExecuteUpdate(row => new User
+                {
+                    name = "u_" + row.id + "_" + (row.fatherId.ToString() ?? "") + "_" + (row.motherId.ToString() ?? ""),
+                    birth = DateTime.Parse("2021-01-11 00:00:00")
+                });
+
+                Assert.AreEqual(6, count);
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u_1_4_6", userList.First().name);
+                Assert.AreEqual(DateTime.Parse("2021-01-11 00:00:00"), userList.First().birth);
+                Assert.AreEqual("u_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = query.ExecuteUpdate(row => new User
+                {
+                    name = "u2_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(6, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u2_1_4_6", userList.First().name);
+                Assert.AreEqual("u2_6__", userList.Last().name);
+            }
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id <= 5 && father != null
+                            select new
+                            {
+                                user,
+                                father,
+                                user.motherId
+                            };
+
+                var count = query.ExecuteUpdate(row => new User
+                {
+                    name = "u3_" + row.user.id + "_" + (row.father.id.ToString() ?? "") + "_" + (row.motherId.ToString() ?? "")
+                });
+                Assert.AreEqual(3, count);
+
+
+                var userList = userQuery.ToList();
+                Assert.AreEqual("u3_1_4_6", userList[0].name);
+                Assert.AreEqual("u3_3_5_6", userList[2].name);
+                Assert.AreEqual("u2_4__", userList[3].name);
+            }
+        }
+    }
+}

+ 45 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ToExecuteString_Test.cs

@@ -0,0 +1,45 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Orm_Extensions_ToExecuteString_Test
+    {
+
+        [TestMethod]
+        public void Test_ToExecuteString()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            #region users.Where().OrderBy().Skip().Take().ToList
+            /*
+            users.Where(row => row.user.id > 2)
+            .OrderBy(user=>user.id)
+            .Select(row => new {row.user })
+            .Skip(1).Take(2);
+             */
+            {
+                var query = (from user in userQuery
+                             where user.id > 2 && !user.name.Contains("3")
+                             orderby user.id descending
+                             select new
+                             {
+                                 user
+                             })
+                            .Skip(1).Take(2);
+
+                var executeString = query.ToExecuteString();
+                Assert.AreEqual(false, string.IsNullOrWhiteSpace(executeString));
+
+                var list = query.ToList();
+                Assert.AreEqual(2, list.Count);
+            }
+            #endregion
+
+        }
+    }
+}

+ 90 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_Bool_Test.cs

@@ -0,0 +1,90 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_Bool_Test
+    {
+        [TestMethod]
+        public void Test()
+        {
+            using var dbContext = DataSource.CreateDbContextForWriting();
+
+            // #1 Init
+            var name = Guid.NewGuid().ToString();
+            var dbSet = dbContext.DbSet<MyUser>();
+            dbSet.TryDropTable();
+            dbSet.TryCreateTable();
+            dbSet.Add(new MyUser { id = 1, enable = true, isEven = null });
+            dbSet.Add(new MyUser { id = 2, enable = true, isEven = true });
+            dbSet.Add(new MyUser { id = 3, enable = false, isEven = false });
+
+            DataSource.WaitForUpdate();
+
+            // #2 Assert
+            {
+                var user = dbSet.Query().Where(m => m.enable == true).OrderBy(m => m.id).First();
+                Assert.AreEqual(1, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.enable == false).OrderBy(m => m.id).First();
+                Assert.AreEqual(3, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.enable).OrderBy(m => m.id).First();
+                Assert.AreEqual(1, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => !m.enable).OrderBy(m => m.id).First();
+                Assert.AreEqual(3, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.isEven == null).OrderBy(m => m.id).First();
+                Assert.AreEqual(1, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.isEven == true).OrderBy(m => m.id).First();
+                Assert.AreEqual(2, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.isEven == false).OrderBy(m => m.id).First();
+                Assert.AreEqual(3, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => m.isEven.Value).OrderBy(m => m.id).First();
+                Assert.AreEqual(2, user.id);
+            }
+            {
+                var user = dbSet.Query().Where(m => !m.isEven.Value).OrderBy(m => m.id).First();
+                Assert.AreEqual(3, user.id);
+            }
+            {
+                var query = dbSet.Query().Where(m => m.isEven.Value).OrderBy(m => m.isEven).Select(m => new { m.id, m.isEven });
+                var sql = query.ToExecuteString();
+                var users = query.ToList();
+                var user = users.First();
+                Assert.AreEqual(2, user.id);
+                Assert.AreEqual(true, user.isEven);
+            }
+        }
+
+
+
+        // Entity
+        [Table("MyUser")]
+        public class MyUser
+        {
+            [Key]
+            public int id { get; set; }
+            public bool? isEven { get; set; }
+            public bool enable { get; set; }
+        }
+
+
+    }
+}

+ 58 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_DateTime_Test.cs

@@ -0,0 +1,58 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_DateTime_Test
+    {
+
+        [TestMethod]
+        public void Test_Equal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // ==
+            {
+                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);
+            }
+        }
+
+        [TestMethod]
+        public void Test_Compare()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Where(u => u.birth >= new DateTime(2021, 01, 01, 05, 00, 00)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 5, 6 }).Count());
+            }
+
+        }
+
+
+        [TestMethod]
+        public void Test_Calculate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                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);
+            }
+
+        }
+
+
+
+    }
+}

+ 37 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_Numeric_Calculate_Test.cs

@@ -0,0 +1,37 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_Numeric_Calculate_Test
+    {
+
+        [TestMethod]
+        public void Test_Calculate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Where(u => u.id + 1 == 4).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+            {
+                var userList = userQuery.Where(u => 4 == u.id + 1).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+            {
+                var userList = userQuery.Where(u => u.id == 4 - 1).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+        }
+
+
+    }
+}

+ 129 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_Numeric_Test.cs

@@ -0,0 +1,129 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_Numeric_Test
+    {
+        // Enumerable.Contains
+        // Queryable.Contains
+        [TestMethod]
+        public void Test_In()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Array.Contains
+            {
+                var userList = userQuery.Where(u => new[] { 3, 5 }.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 5 }).Count());
+            }
+
+            // List.Contains
+            {
+                var ids = new[] { 3, 5 }.ToList();
+                var userList = userQuery.Where(u => ids.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 5 }).Count());
+            }
+
+            // Enumerable.Contains
+            {
+                var ids = new[] { 3, 5 }.AsEnumerable();
+                var userList = userQuery.Where(u => ids.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 5 }).Count());
+            }
+
+            // Queryable.Contains
+            {
+                var ids = new[] { 3, 5 }.AsQueryable();
+                var userList = userQuery.Where(u => ids.Contains(u.id)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 5 }).Count());
+            }
+
+
+            // not Contains
+            {
+                var userList = userQuery.Where(u => !new[] { 3, 5 }.Contains(u.id)).ToList();
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 1, 2, 4, 6 }).Count());
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Equal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // #1 ==
+            {
+                var userList = userQuery.Where(u => u.id == 3 || 5 == u.id).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 5 }).Count());
+            }
+
+            // #2 !=
+            {
+                var userList = userQuery.Where(u => u.id != 1).ToList();
+                Assert.AreEqual(5, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 2, 3, 4, 5, 6 }).Count());
+            }
+            {
+                var userList = userQuery.Where(u => 1 != u.id).ToList();
+                Assert.AreEqual(5, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 2, 3, 4, 5, 6 }).Count());
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_Compare()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // #1 > and <
+            {
+                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);
+            }
+
+            // #2  > or <
+            {
+                var userList = userQuery.Where(u => u.id > 5 || u.id < 2).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 1, 6 }).Count());
+            }
+
+            // #3  >= or <=
+            {
+                var userList = userQuery.Where(u => u.id >= 6 || u.id <= 1).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 1, 6 }).Count());
+            }
+
+            // #4  in right side
+            {
+                var userList = userQuery.Where(u => 4 >= u.id && 3 <= u.id).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.id).Except(new[] { 3, 4 }).Count());
+            }
+
+        }
+
+
+
+
+
+    }
+}

+ 39 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Calculate_Test.cs

@@ -0,0 +1,39 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_String_Calculate_Test
+    {
+
+
+        [TestMethod]
+        public void Test_Calculate()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Where(u => u.name + 1 == "u3561").ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList[0].id);
+            }
+            {
+                var userList = userQuery.Where(u => "u3561" == u.name + 1).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList[0].id);
+            }
+            {
+                var userList = userQuery.Where(u => u.name == "u35" + 6).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList[0].id);
+            }
+
+        }
+
+
+    }
+}

+ 50 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Like_Test.cs

@@ -0,0 +1,50 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_String_Like_Test
+    {
+
+        [TestMethod]
+        public void Test_Like()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // StartsWith
+            {
+                var query = userQuery.Where(u => u.name.StartsWith("u35"));
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u356", userList.First().name);
+            }
+            // EndsWith
+            {
+                var query = userQuery.Where(u => u.name.EndsWith("356"));
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u356", userList.First().name);
+            }
+            // Contains
+            {
+                var query = userQuery.Where(u => u.name.Contains("35"));
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+                Assert.AreEqual("u356", userList.First().name);
+            }
+        }
+
+
+
+    }
+}

+ 94 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Test.cs

@@ -0,0 +1,94 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_String_Test
+    {
+
+        // Enumerable.Contains
+        // Queryable.Contains
+        [TestMethod]
+        public void Test_In()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Array.Contains
+            {
+                var userList = userQuery.Where(u => new[] { "u356", "u500" }.Contains(u.name)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u356", "u500" }).Count());
+            }
+
+            // List.Contains
+            {
+                var ids = new[] { "u356", "u500" }.ToList();
+                var userList = userQuery.Where(u => ids.Contains(u.name)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u356", "u500" }).Count());
+            }
+
+
+            // Enumerable.Contains
+            {
+                var ids = new[] { "u356", "u500" }.AsEnumerable();
+                var userList = userQuery.Where(u => ids.Contains(u.name)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u356", "u500" }).Count());
+            }
+
+            // Queryable.Contains
+            {
+                var ids = new[] { "u356", "u500" }.AsQueryable();
+                var userList = userQuery.Where(u => ids.Contains(u.name)).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u356", "u500" }).Count());
+            }
+
+
+            // not Contains
+            {
+                var userList = userQuery.Where(u => !new[] { "u356", "u500" }.Contains(u.name)).ToList();
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u146", "u246", "u400", "u600" }).Count());
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Equal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // #1 ==
+            {
+                var userList = userQuery.Where(u => u.name == "u356" || "u500" == u.name).ToList();
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u356", "u500" }).Count());
+            }
+
+            // #2 !=
+            {
+                var userList = userQuery.Where(u => u.name != "u146").ToList();
+                Assert.AreEqual(5, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u246", "u356", "u400", "u500", "u600" }).Count());
+            }
+            {
+                var userList = userQuery.Where(u => "u146" != u.name).ToList();
+                Assert.AreEqual(5, userList.Count);
+                Assert.AreEqual(0, userList.Select(m => m.name).Except(new[] { "u246", "u356", "u400", "u500", "u600" }).Count());
+            }
+        }
+
+
+
+
+
+
+    }
+}

+ 57 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Property_Test.cs

@@ -0,0 +1,57 @@
+using System.ComponentModel.DataAnnotations;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Property_Test
+    {
+        [TestMethod]
+        public void Test_RequiredAndMaxLength()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var dbSet = dbContext.DbSet<UserInfo>();
+
+            dbSet.TryDropTable();
+            dbSet.TryCreateTable();
+
+            {
+                dbSet.Add(new UserInfo { id = 1, name = "user1" });
+
+                try
+                {
+                    dbSet.Add(new UserInfo { id = 2 });
+                    Assert.Fail("name should be required");
+                }
+                catch (Exception ex)
+                { }
+
+                try
+                {
+                    dbSet.Add(new UserInfo { id = 3, name = "01234567890123456789" });
+                    Assert.Fail("max length of name should be 10");
+                }
+                catch (Exception ex)
+                { }
+            }
+        }
+
+
+
+        class UserInfo
+        {
+            [Key]
+            public int id { get; set; }
+
+            [Required]
+            [MaxLength(10)]
+            public string name { get; set; }
+        }
+
+
+
+
+    }
+}

+ 237 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Count_Test.cs

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

+ 73 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Distinct_Test.cs

@@ -0,0 +1,73 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_Distinct_Test
+    {
+
+        [TestMethod]
+        public void Test_Distinct()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = userQuery.Select(u => u.fatherId).Distinct();
+
+                //var sql = query.ToExecuteString();
+                var fatherIds = query.ToList();
+
+                Assert.AreEqual(3, fatherIds.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
+            }
+            {
+                var query = userQuery.Select(u => new { u.fatherId }).Distinct();
+
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                var fatherIds = userList.Select(u => u.fatherId).ToList();
+
+                Assert.AreEqual(3, fatherIds.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
+            }
+            {
+                var query = userQuery.Select(u => new { u.fatherId, u.motherId }).Distinct();
+
+                query = query.Skip(1).Take(100);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                var fatherIds = userList.Select(u => u.fatherId).ToList();
+                var motherIds = userList.Select(u => u.motherId).ToList();
+
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(0, fatherIds.Except(new int?[] { 4, 5, null }).Count());
+                Assert.AreEqual(0, motherIds.Except(new int?[] { 6, null }).Count());
+            }
+            {
+                var query = userQuery.Select(u => new { user = u, u.fatherId, u.motherId }).Distinct();
+
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+            }
+            {
+                var query = userQuery.Distinct();
+
+                //var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(6, userList.Count);
+            }
+
+        }
+
+
+
+    }
+}

+ 42 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_FilterRule_Test.cs

@@ -0,0 +1,42 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Core.Module.Serialization;
+using Vit.Linq;
+using Vit.Linq.ComponentModel;
+using Vit.Linq.FilterRules.ComponentModel;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_FilterRule_Test
+    {
+
+        [TestMethod]
+        public void Test()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var strFilter = "{'field':'id',  'operator': '>',  'value': 1 }".Replace("'", "\"");
+                var filter = Json.Deserialize<FilterRule>(strFilter);
+
+                var items = userQuery.Where(filter).OrderByDescending(u => u.id).ToList();
+                Assert.AreEqual(5, items.Count);
+                Assert.AreEqual(6, items[0].id);
+            }
+
+            {
+                var strPagedQuery = "{ 'filter':{'field':'id',  'operator': '>',  'value': 1 },  'orders':[{'field':'id','asc':false}],  'page':{'pageSize':2, 'pageIndex':1}  }".Replace("'", "\"");
+                var pagedQuery = Json.Deserialize<PagedQuery>(strPagedQuery);
+
+                var pageData = userQuery.ToPageData(pagedQuery);
+                Assert.AreEqual(5, pageData.totalCount);
+                Assert.AreEqual(6, pageData.items[0].id);
+            }
+
+        }
+
+    }
+}

+ 40 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_FilterRule_WithJoin_Test.cs

@@ -0,0 +1,40 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Core.Module.Serialization;
+using Vit.Linq;
+using Vit.Linq.ComponentModel;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_FilterRule_WithJoin_Test
+    {
+
+        [TestMethod]
+        public void Test()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id)
+                    select new { user, father };
+
+
+                var strPagedQuery = "{ 'filter':{'field':'father.name',  'operator': '=',  'value': 'u400' },  'orders':[{'field':'user.id','asc':false}],  'page':{'pageSize':2, 'pageIndex':1}  }".Replace("'", "\"");
+                var pagedQuery = Json.Deserialize<PagedQuery>(strPagedQuery);
+
+
+                var pageData = query.ToPageData(pagedQuery);
+
+                Assert.AreEqual(2, pageData.totalCount);
+                Assert.AreEqual(2, pageData.items[0].user.id);
+            }
+
+        }
+
+    }
+}

+ 183 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Group_Test.cs

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

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

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

+ 192 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_InnerJoin_BySelectMany_Test.cs

@@ -0,0 +1,192 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_InnerJoin_BySelectMany_Test
+    {
+
+        [TestMethod]
+        public void Test_InnerJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                var query =
+                    from user in userQuery
+                    from father in userQuery.Where(father => user.fatherId == father.id)
+                    where user.id > 2
+                    select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                    userQuery.SelectMany(
+                        user => userQuery.Where(father => user.fatherId == father.id)
+                        , (user, father) => new { user, father }
+                    )
+                    .Where(row => row.user.id > 2)
+                    .Select(row => new { row.user, row.father });
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().user.id);
+                Assert.AreEqual(5, userList.First().father.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_InnerJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                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);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_MultipleSelect()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id)
+                            from mother in userQuery.Where(mother => user.motherId == mother.id)
+                            orderby user.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)
+                            from mother in userQuery.Where(mother => user.motherId == mother.id)
+                            where user.id > 1
+                            orderby user.id
+                            select new
+                            {
+                                user,
+                                father,
+                                userId = user.id + 100,
+                                hasFather = user.fatherId != null ? true : false,
+                                hasFather2 = father != null,
+                                fatherName = father.name,
+                                motherName = mother.name,
+                            };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual(4, userList.First().father?.id);
+                Assert.AreEqual(5, userList.Last().father?.id);
+            }
+        }
+
+
+    }
+}

+ 108 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_ByGroupJoin_Test.cs

@@ -0,0 +1,108 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_LeftJoin_ByGroupJoin_Test
+    {
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                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
+                    orderby user.id
+                    select new { user, father };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(4, userList.Count);
+                Assert.AreEqual(3, userList[0].user.id);
+                Assert.AreEqual(5, userList[0].father?.id);
+                Assert.AreEqual(4, userList[1].user.id);
+                Assert.AreEqual(null, userList[1].father?.name);
+            }
+
+            // 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)
+                    .OrderBy(row2 => row2.row.user.id)
+                    .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?.name);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                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 user.id
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        testId = user.id + 100,
+                        hasFather = father.name != null ? true : false
+                    };
+
+                query = query.Skip(1).Take(2);
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+
+                Assert.AreEqual(2, userList.Count);
+
+                var first = userList.First();
+                Assert.AreEqual(4, first.user.id);
+                Assert.AreEqual(null, first.father?.name);
+                Assert.AreEqual(null, first.mother?.name);
+                Assert.AreEqual(104, first.testId);
+                Assert.AreEqual(false, first.hasFather);
+            }
+        }
+
+
+    }
+}

+ 188 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_BySelectMany_Test.cs

@@ -0,0 +1,188 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_LeftJoin_BySelectMany_Test
+    {
+
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Demo()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            // 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)
+                        .OrderBy(row => row.user.id)
+                        .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?.name);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_LeftJoin_Complex()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                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()
+                    from userClass in dbContext.Query<UserClass>().Where(userClass => user.classId == userClass.id).DefaultIfEmpty()
+                    where user.id > 1 && userClass.id == 1
+                    orderby user.id
+                    select new
+                    {
+                        user,
+                        father,
+                        mother,
+                        userClass,
+                        userClass.name,
+                        testId = user.id + 100,
+                        hasFather = father.name != 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?.name);
+                Assert.AreEqual(null, first.mother?.name);
+                Assert.AreEqual(104, first.testId);
+                Assert.AreEqual(false, first.hasFather);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_MultipleSelect()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+
+            {
+                var query = from user in userQuery
+                            from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                            where user.id > 2 && father.name != null
+                            orderby user.id
+                            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)
+                            orderby user.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()
+                            orderby user.id
+                            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);
+            }
+
+
+        }
+
+
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var count = (from user in userQuery
+                             from father in userQuery.Where(father => user.fatherId == father.id).DefaultIfEmpty()
+                             where user.id > 2 && father.name == null
+                             select new
+                             {
+                                 father
+                             }).Count();
+
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+    }
+}

+ 251 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_MethodAsync_Test.cs

@@ -0,0 +1,251 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_MethodAsync_Test
+    {
+
+        [TestMethod]
+        public async Task Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = await userQuery.OrderBy(m => m.id).ToListAsync();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+            {
+                var userList = await userQuery.OrderBy(m => m.id).Select(u => u.id).ToListAsync();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = await query.CountAsync();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = await query.CountAsync();
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_TotalCount()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = await query.TotalCountAsync();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = await query.TotalCountAsync();
+                Assert.AreEqual(4, count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_ToListAndTotal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // ToListAndTotalCount
+            {
+                var query = dbContext.Query<User>().Where(user => user.id > 2).Skip(1).Take(2);
+                var (list, totalCount) = await query.ToListAndTotalCountAsync();
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual(4, totalCount);
+            }
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = await userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, id);
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstOrDefaultAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstOrDefaultAsync(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderByDescending(m => m.id).FirstOrDefaultAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).Skip(1).Take(2).FirstOrDefaultAsync();
+                Assert.AreEqual(2, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_First()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).FirstAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = await userQuery.FirstAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = await userQuery.FirstAsync(user => user.id == 13);
+                    Assert.Fail("IQueryable.First should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).Skip(1).Take(2).FirstAsync();
+                Assert.AreEqual(2, user?.id);
+            }
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_LastOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = await userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefaultAsync();
+                Assert.AreEqual(1, id);
+            }
+            {
+                var user = await userQuery.OrderBy(m => m.id).LastOrDefaultAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastOrDefaultAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastOrDefaultAsync(user => user.id == 13);
+                Assert.AreEqual(null, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderByDescending(m => m.id).LastOrDefaultAsync();
+                Assert.AreEqual(1, user?.id);
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).Skip(1).Take(2).LastOrDefaultAsync();
+                Assert.AreEqual(3, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_Last()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).LastAsync();
+                Assert.AreEqual(6, user?.id);
+            }
+
+            {
+                var user = await userQuery.LastAsync(user => user.id == 3);
+                Assert.AreEqual(3, user?.id);
+            }
+
+            {
+                try
+                {
+                    var user = await userQuery.LastAsync(user => user.id == 13);
+                    Assert.Fail("IQueryable.Last should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+
+            {
+                var user = await userQuery.OrderBy(m => m.id).Skip(1).Take(2).LastAsync();
+                Assert.AreEqual(3, user?.id);
+            }
+        }
+
+
+
+
+    }
+}

+ 393 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Method_Test.cs

@@ -0,0 +1,393 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_Method_Test
+    {
+
+        [TestMethod]
+        public void Test_PlainQuery()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.OrderBy(m => m.id).ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+
+            {
+                var userList = userQuery.OrderBy(m => m.id).Select(u => u.id).ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_AllFeatures()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            #region users.Where().OrderBy().Skip().Take().ToList
+            /*
+            users.Where(row => row.user.id > 2)
+            .OrderBy(user=>user.id)
+            .Select(row => new {row.user })
+            .Skip(1).Take(2);
+             */
+            {
+                var query = (from user in userQuery
+                             where user.id > 2
+                             orderby 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_Get()
+        {
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var user = dbContext.Get<User>(3);
+                Assert.AreEqual(3, user?.id);
+            }
+            {
+                using var dbContext = DataSource.CreateDbContext();
+                var user = dbContext.DbSet<User>().Get(5);
+                Assert.AreEqual(5, user?.id);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_Select()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.Select(u => u).Where(user => user.id == 3).Select(u => u).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3, userList.First().id);
+            }
+
+            {
+                var userList = userQuery.Where(user => user.id == 3).Select(u => (float)u.id).ToList();
+                Assert.AreEqual(1, userList.Count);
+                Assert.AreEqual(3.0, userList.First());
+            }
+
+            {
+                var query =
+                    from user in userQuery
+                    orderby user.id
+                    select new
+                    {
+                        uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                        uniqueId2 = $"{user.id}_{user.fatherId}_{user.motherId}"
+                    };
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual("1_4_6", userList[0].uniqueId1);
+                Assert.AreEqual("1_4_6", userList[0].uniqueId2);
+                Assert.AreEqual("4__", userList[3].uniqueId1);
+                Assert.AreEqual("4__", userList[3].uniqueId2);
+            }
+
+        }
+
+
+        [TestMethod]
+        public void Test_OrderBy()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query = userQuery.OrderByDescending(user => user.id);
+
+                //var sql = query.ToExecuteString();
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(6, userList[0].id);
+            }
+
+            {
+                var query = userQuery.OrderByDescending(user => user.id).Select(user => new { fid = user.fatherId, user.id });
+
+                //var sql = query.ToExecuteString();
+
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual(6, userList[0].id);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = query.Count();
+                Assert.AreEqual(4, count);
+            }
+            // Skip Take Count
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = query.Count();
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_TotalCount()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                var count = query.TotalCount();
+                Assert.AreEqual(4, count);
+            }
+
+            // Skip Take TotalCountAsync
+            {
+                var query = userQuery.Where(user => user.id > 2);
+
+                query = query.Skip(1).Take(10);
+
+                var count = query.TotalCount();
+                Assert.AreEqual(4, count);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_ToListAndTotal()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+
+            // ToListAndTotalCount
+            {
+                var query = dbContext.Query<User>().Where(user => user.id > 2).Skip(1).Take(2);
+                var (list, totalCount) = query.ToListAndTotalCount();
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual(4, totalCount);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefault();
+                Assert.AreEqual(1, id);
+            }
+
+            {
+                var user = userQuery.OrderBy(m => m.id).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);
+            }
+
+            {
+                var user = userQuery.OrderBy(m => m.id).Skip(1).Take(2).FirstOrDefault();
+                Assert.AreEqual(2, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_First()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.OrderBy(m => m.id).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("IQueryable.First should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+
+            {
+                var user = userQuery.OrderBy(m => m.id).Skip(1).Take(2).First();
+                Assert.AreEqual(2, user?.id);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void Test_LastOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var id = userQuery.OrderBy(m => m.id).Select(u => u.id).FirstOrDefault();
+                Assert.AreEqual(1, id);
+            }
+            {
+                var user = userQuery.OrderBy(m => m.id).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);
+            }
+
+            {
+                var user = userQuery.OrderBy(m => m.id).Skip(1).Take(2).LastOrDefault();
+                Assert.AreEqual(3, user?.id);
+            }
+        }
+
+
+        [TestMethod]
+        public void Test_Last()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var user = userQuery.OrderBy(m => m.id).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("IQueryable.Last should throw Exception");
+                }
+                catch (Exception ex) when (ex is not AssertFailedException)
+                {
+                }
+            }
+
+            {
+                var user = userQuery.OrderBy(m => m.id).Skip(1).Take(2).Last();
+                Assert.AreEqual(3, user?.id);
+            }
+        }
+
+        // Enumerable.ToArray
+        [TestMethod]
+        public void Test_Enumerable_ToArray()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var userList = userQuery.OrderBy(m => m.id).ToArray();
+                Assert.AreEqual(6, userList.Length);
+                Assert.AreEqual(1, userList.First().id);
+                Assert.AreEqual(6, userList.Last().id);
+            }
+
+            {
+                var userList = userQuery.OrderBy(m => m.id).Select(u => u.id).ToArray();
+                Assert.AreEqual(6, userList.Length);
+                Assert.AreEqual(1, userList.First());
+                Assert.AreEqual(6, userList.Last());
+            }
+        }
+
+
+
+
+    }
+}

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

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

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

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

+ 173 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Select_Test.cs

@@ -0,0 +1,173 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_Select_Test
+    {
+
+        [TestMethod]
+        public void Test_Select()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // 
+            {
+                var userList = userQuery.ToList();
+            }
+
+            // userQuery.Select(user => user)
+            {
+                var rows = userQuery.Select(user => user).ToList();
+                Assert.AreEqual(6, rows.Count);
+            }
+
+            // userQuery.Select(user => user.id);
+            {
+                var rows = userQuery.Select(user => user.id).ToList();
+            }
+            {
+                var rows = userQuery.Select(user => user.id + 100).ToList();
+            }
+            {
+                var rows = userQuery.Select(user => 1000 + user.id + 100).ToList();
+            }
+            {
+                var rows = userQuery.Select(user => (double)user.id).ToList();
+            }
+            {
+                var rows = userQuery.Select(user => (double)user.id + 0.1).ToList();
+            }
+            {
+                var rows = userQuery.Select(user => user.id + 0.1).ToList();
+            }
+
+            {
+                var query =
+                    from user in userQuery
+                    where user.id > 1
+                    orderby user.id
+                    select new
+                    {
+                        id = user.id,
+                        user = user,
+                        ext = new
+                        {
+                            father = user,
+                            fid = user.fatherId
+                        },
+                        trueValue = true,
+                    };
+
+                var sql = query.ToExecuteString();
+                var rows = query.ToList();
+            }
+
+
+            // String.Format(format: "{0}_{1}_{2}", "0", "1", "2")
+            {
+                var query =
+                    from user in userQuery
+                    orderby user.id
+                    select new
+                    {
+                        uniqueId1 = user.id + "_" + user.fatherId + "_" + user.motherId,
+                        uniqueId2 = $"{user.id}_{user.fatherId}_{user.motherId}"
+                    };
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(6, userList.Count);
+                Assert.AreEqual("1_4_6", userList[0].uniqueId1);
+                Assert.AreEqual("1_4_6", userList[0].uniqueId2);
+                Assert.AreEqual("4__", userList[3].uniqueId1);
+                Assert.AreEqual("4__", userList[3].uniqueId2);
+            }
+
+
+        }
+
+
+
+
+        [TestMethod]
+        public void Test_Select_ExistClass()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var query =
+                    from user in userQuery
+                    where user.id > 1
+                    orderby user.id
+                    select new User2(user.id)
+                    ;
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(5, userList.Count);
+
+            }
+
+            {
+                var query =
+                    from user in userQuery
+                    where user.id > 1
+                    orderby user.id
+                    select new User2(user.name)
+                    ;
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(5, userList.Count);
+            }
+            {
+                var query =
+                    from user in userQuery
+                    where user.id > 1
+                    orderby user.id
+                    select new User2(user.name)
+                    {
+                        id = user.id + 100,
+                        fatherId = user.fatherId,
+                        father = user,
+                    }
+                    ;
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(5, userList.Count);
+
+            }
+
+            {
+                var query =
+                    from user in userQuery
+                    where user.id > 1
+                    orderby user.id
+                    select new User2
+                    {
+                        id = user.id,
+                        fatherId = 12,
+                    };
+
+                var sql = query.ToExecuteString();
+                var userList = query.ToList();
+                Assert.AreEqual(5, userList.Count);
+            }
+        }
+
+        class User2 : User
+        {
+            public User2() { }
+            public User2(int nid) { id = nid; }
+            public User2(string name) { this.name = name; }
+            public User father;
+        }
+
+
+    }
+}

+ 79 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_ToListAndTotalCount_Test.cs

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

+ 24 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Schema_Test.cs

@@ -0,0 +1,24 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public partial class Schema_Test
+    {
+
+        [TestMethod]
+        public void Test_CreateAndTropTable()
+        {
+            using var dbContext = DataSource.CreateDbContextForWriting();
+
+            dbContext.TryCreateTable<User>();
+            dbContext.TryCreateTable<User>();
+            dbContext.TryDropTable<User>();
+            dbContext.TryDropTable<User>();
+        }
+
+
+
+    }
+}

+ 57 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Truncate_Test.cs

@@ -0,0 +1,57 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public partial class Truncate_Test
+    {
+
+        [TestMethod]
+        public void Test_Truncate()
+        {
+            using var dbContext = DataSource.CreateDbContextForWriting();
+
+            // assert
+            {
+                var count = dbContext.Query<User>().Count();
+                Assert.AreEqual(6, count);
+            }
+
+            dbContext.Truncate<User>();
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var count = dbContext.Query<User>().Count();
+                Assert.AreEqual(0, count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_TruncateAsync()
+        {
+            using var dbContext = DataSource.CreateDbContextForWriting();
+
+            // assert
+            {
+                var count = dbContext.Query<User>().Count();
+                Assert.AreEqual(6, count);
+            }
+
+            await dbContext.TruncateAsync<User>();
+
+            DataSource.WaitForUpdate();
+
+            // assert
+            {
+                var count = dbContext.Query<User>().Count();
+                Assert.AreEqual(0, count);
+            }
+        }
+
+
+
+    }
+}

+ 128 - 0
test/Vitorm.MongoDB.MsTest/DataSource.cs

@@ -0,0 +1,128 @@
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+
+using Vit.Core.Util.ConfigurationManager;
+
+using Vitorm.MongoDB;
+
+namespace Vitorm.MsTest
+{
+    [System.ComponentModel.DataAnnotations.Schema.Table("User")]
+    public class User
+    {
+        public User() { }
+
+        public User(int id) { this.id = id; }
+        public User(string name) { this.name = name; }
+
+
+        [System.ComponentModel.DataAnnotations.Key]
+        [System.ComponentModel.DataAnnotations.Schema.Column("userId")]
+        [BsonId]
+        [BsonElement("userId")]
+        //[BsonRepresentation(BsonType.Int32)]
+        public int id { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.Column("userName")]
+        [BsonElement("userName")]
+        public string name { get; set; }
+
+        //[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
+        public DateTime? birth { get; set; }
+
+        public int? fatherId { get; set; }
+
+
+        public int? motherId { get; set; }
+
+        // [BsonIgnoreIfNull]
+        public int? classId { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
+        [BsonIgnore]
+        public string test { get; set; }
+
+
+        public static User NewUser(int id, bool forAdd = false) => new User { id = id, name = "testUser" + id };
+
+        public static List<User> NewUsers(int startId, int count = 1, bool forAdd = false)
+        {
+            return Enumerable.Range(startId, count).Select(id => NewUser(id, forAdd)).ToList();
+        }
+    }
+
+    [System.ComponentModel.DataAnnotations.Schema.Table("UserClass")]
+    public class UserClass
+    {
+        [System.ComponentModel.DataAnnotations.Key]
+        [System.ComponentModel.DataAnnotations.Schema.Column("classId")]
+        [BsonId]
+        [BsonElement("classId")]
+        public int id { get; set; }
+
+        [System.ComponentModel.DataAnnotations.Schema.Column("className")]
+        [BsonElement("className")]
+        public string name { get; set; }
+
+        public static List<UserClass> NewClasses(int startId, int count = 1)
+        {
+            return Enumerable.Range(startId, count).Select(id => new UserClass { id = id, name = "class" + id }).ToList();
+        }
+    }
+
+
+    public class DataSource
+    {
+        public static void WaitForUpdate() { }
+
+        public static DbContext CreateDbContextForWriting(bool autoInit = true) => CreateDbContext(autoInit);
+        public static DbContext CreateDbContext(bool autoInit = true)
+        {
+            var dbConfig = new DbConfig(Appsettings.json.GetByPath<Dictionary<string, object>>("Vitorm.MongoDB"));
+            var dbContext = new Vitorm.MongoDB.DbContext(dbConfig);
+
+            //dbContext.BeginTransaction();
+
+            if (autoInit)
+                InitDbContext(dbContext);
+
+            return dbContext;
+        }
+
+
+        public static void InitDbContext(DbContext dbContext)
+        {
+            #region #1 init User
+            {
+                dbContext.TryDropTable<User>();
+                dbContext.TryCreateTable<User>();
+
+                var users = new List<User> {
+                    new User { id=1, name="u146", fatherId=4, motherId=6 },
+                    new User { id=2, name="u246", fatherId=4, motherId=6 },
+                    new User { id=3, name="u356", fatherId=5, motherId=6 },
+                    new User { id=4, name="u400" },
+                    new User { id=5, name="u500" },
+                    new User { id=6, name="u600" },
+                };
+                users.ForEach(user =>
+                {
+                    user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id);
+                    user.classId = user.id % 2 + 1;
+                });
+
+                dbContext.AddRange(users);
+            }
+            #endregion
+
+            #region #2 init Class
+            {
+                dbContext.TryDropTable<UserClass>();
+                dbContext.TryCreateTable<UserClass>();
+                dbContext.AddRange(UserClass.NewClasses(1, 6));
+            }
+            #endregion
+        }
+
+    }
+}

+ 36 - 0
test/Vitorm.MongoDB.MsTest/Vitorm.MongoDB.MsTest.csproj

@@ -0,0 +1,36 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <test>MSTest</test>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <TargetFramework>net6.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+        <RootNamespace>Vitorm.MsTest</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
+
+        <PackageReference Include="Vit.Core" Version="2.2.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\src\Vitorm.MongoDB\Vitorm.MongoDB.csproj" />
+    </ItemGroup>
+ 
+
+    <ItemGroup>
+      <None Update="appsettings.json">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </None>
+    </ItemGroup>
+
+</Project>