Browse Source

Merge pull request #8 from LithWang/master

release/2.5.0
Lith 4 months ago
parent
commit
0a0829828a
76 changed files with 2908 additions and 1914 deletions
  1. 6 1
      Publish/DevOps3/README.md
  2. 1 1
      Publish/DevOps3/build-bash/10.Test.bash
  3. 1 1
      Publish/DevOps3/build-bash/30.nuget-pack.sh
  4. 1 1
      Publish/DevOps3/build-bash/40.Station-publish.sh
  5. 1 1
      Publish/DevOps3/release-bash/72.nuget-push.sh
  6. 16 8
      Publish/environment/build-bash__10.Test__#1.InitEnv.sh
  7. 1 1
      Publish/environment/build-bash__10.Test__#3.CleanEnv.sh
  8. 12 0
      doc/ReleaseNotes.md
  9. 2 2
      src/Versions.props
  10. 18 18
      src/Vitorm.MongoDB/DbConfig.cs
  11. 85 49
      src/Vitorm.MongoDB/DbContext.cs
  12. 12 28
      src/Vitorm.MongoDB/DbSet.Query.cs
  13. 75 24
      src/Vitorm.MongoDB/DbSet.cs
  14. 118 0
      src/Vitorm.MongoDB/EntityReader/EntityReader.cs
  15. 15 0
      src/Vitorm.MongoDB/EntityReader/IArgReader.cs
  16. 36 0
      src/Vitorm.MongoDB/EntityReader/ModelReader.cs
  17. 34 0
      src/Vitorm.MongoDB/EntityReader/ValueReader.cs
  18. 57 0
      src/Vitorm.MongoDB/QueryExecutor/Async/ToListAsync.cs
  19. 51 0
      src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.ReverseOrder.cs
  20. 208 4
      src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.cs
  21. 58 0
      src/Vitorm.MongoDB/QueryExecutor/Sync/Count.cs
  22. 51 0
      src/Vitorm.MongoDB/QueryExecutor/Sync/FirstOrDefault.cs
  23. 35 0
      src/Vitorm.MongoDB/QueryExecutor/Sync/ToExecuteString.cs
  24. 16 101
      src/Vitorm.MongoDB/QueryExecutor/Sync/ToList.cs
  25. 72 105
      src/Vitorm.MongoDB/QueryExecutor/TranslateService.cs
  26. 53 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.Count.cs
  27. 83 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.FirstOrDefault.cs
  28. 33 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToExecuteString.cs
  29. 96 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToList.cs
  30. 108 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToListAsync.cs
  31. 218 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.cs
  32. 20 0
      src/Vitorm.MongoDB/SearchExecutor/ISearchExecutor.cs
  33. 57 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.Count.cs
  34. 72 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.FirstOrDefault.cs
  35. 36 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToExecuteString.cs
  36. 66 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToList.cs
  37. 61 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToListAsync.cs
  38. 92 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.cs
  39. 51 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.Count.cs
  40. 69 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.FirstOrDefault.cs
  41. 74 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToExecuteString.cs
  42. 63 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToList.cs
  43. 67 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToListAsync.cs
  44. 87 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.cs
  45. 56 0
      src/Vitorm.MongoDB/Transaction/Transaction.cs
  46. 39 0
      src/Vitorm.MongoDB/Transaction/TransactionManager.cs
  47. 2 3
      src/Vitorm.MongoDB/Vitorm.MongoDB.csproj
  48. 4 4
      test/Vitorm.MongoDB.Data.MsTest/CommonTest/Common_Test.cs
  49. 4 4
      test/Vitorm.MongoDB.Data.MsTest/Vitorm.MongoDB.Data.MsTest.csproj
  50. 1 1
      test/Vitorm.MongoDB.Data.MsTest/appsettings.json
  51. 0 1
      test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_CustomLoader_Test.cs
  52. 0 54
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDeleteAsync_Test.cs
  53. 0 54
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteDelete_Test.cs
  54. 0 78
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdateAsync_Test.cs
  55. 0 80
      test/Vitorm.MongoDB.MsTest/CommonTest/Orm_Extensions_ExecuteUpdate_Test.cs
  56. 3 2
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Bool_Test.cs
  57. 0 37
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_Numeric_Calculate_Test.cs
  58. 0 39
      test/Vitorm.MongoDB.MsTest/CommonTest/Property_String_Calculate_Test.cs
  59. 12 12
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Count_Test.cs
  60. 0 40
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_FilterRule_WithJoin_Test.cs
  61. 56 0
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Group_FirstOrDefault_Test.cs
  62. 0 210
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_InnerJoin_ByJoin_Test.cs
  63. 0 187
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_InnerJoin_BySelectMany_Test.cs
  64. 0 105
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_ByGroupJoin_Test.cs
  65. 0 186
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_LeftJoin_BySelectMany_Test.cs
  66. 0 251
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_MethodAsync_Test.cs
  67. 1 1
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Method_Test.cs
  68. 0 86
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ScopeParam_LeftJoin_Test.cs
  69. 0 79
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ToListAndTotalCount_Test.cs
  70. 4 46
      test/Vitorm.MongoDB.MsTest/CommonTest/Transaction_Test.cs
  71. 0 1
      test/Vitorm.MongoDB.MsTest/CustomTest/Linq_Test.cs
  72. 140 0
      test/Vitorm.MongoDB.MsTest/CustomTest/Primitive_Test.cs
  73. 179 0
      test/Vitorm.MongoDB.MsTest/CustomTest/Query_Method_Test.cs
  74. 13 2
      test/Vitorm.MongoDB.MsTest/DataSource.cs
  75. 5 5
      test/Vitorm.MongoDB.MsTest/Vitorm.MongoDB.MsTest.csproj
  76. 1 1
      test/Vitorm.MongoDB.MsTest/appsettings.json

+ 6 - 1
Publish/DevOps3/README.md

@@ -1,5 +1,5 @@
 
 
-# DevOps 3.7
+# DevOps 3.8
 
 
 
 
 # build-bash
 # build-bash
@@ -24,6 +24,11 @@ if this file exists, will not need approval for jenkins build.
 ----------------------------------------------
 ----------------------------------------------
 # ReleaseLog
 # ReleaseLog
 
 
+-----------------------
+# 3.8
+> 2024-12-19
+- upgrade net to 8.0
+
 -----------------------
 -----------------------
 # 3.7
 # 3.7
 > 2024-09-21
 > 2024-09-21

+ 1 - 1
Publish/DevOps3/build-bash/10.Test.bash

@@ -37,7 +37,7 @@ docker run -i --rm \
 -v $NUGET_PATH:/root/.nuget \
 -v $NUGET_PATH:/root/.nuget \
 -v "$basePath":/root/code \
 -v "$basePath":/root/code \
 -v "$basePath":"$basePath" \
 -v "$basePath":"$basePath" \
-serset/dotnet:sdk-6.0 \
+serset/dotnet:sdk-8.0 \
 bash -c "
 bash -c "
 set -e
 set -e
 
 

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

@@ -22,7 +22,7 @@ docker run -i --rm \
 --env LANG=C.UTF-8 \
 --env LANG=C.UTF-8 \
 -v $NUGET_PATH:/root/.nuget \
 -v $NUGET_PATH:/root/.nuget \
 -v $basePath:/root/code \
 -v $basePath:/root/code \
-serset/dotnet:sdk-6.0 \
+serset/dotnet:sdk-8.0 \
 bash -c "
 bash -c "
 
 
 publishPath=/root/code/Publish/release/release/nuget
 publishPath=/root/code/Publish/release/release/nuget

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

@@ -23,7 +23,7 @@ docker run -i --rm \
 -v $NUGET_PATH:/root/.nuget \
 -v $NUGET_PATH:/root/.nuget \
 -v "$basePath":/root/code \
 -v "$basePath":/root/code \
 -v "$basePath":"$basePath" \
 -v "$basePath":"$basePath" \
-serset/dotnet:sdk-6.0 \
+serset/dotnet:sdk-8.0 \
 bash -c "
 bash -c "
 set -e
 set -e
 
 

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

@@ -25,7 +25,7 @@ fi
 docker run -i --rm \
 docker run -i --rm \
 --env LANG=C.UTF-8 \
 --env LANG=C.UTF-8 \
 -v $basePath:/root/code \
 -v $basePath:/root/code \
-serset/dotnet:sdk-6.0 \
+serset/dotnet:sdk-8.0 \
 bash -c "
 bash -c "
 for file in /root/code/Publish/release/release/nuget/*.nupkg
 for file in /root/code/Publish/release/release/nuget/*.nupkg
 do
 do

+ 16 - 8
Publish/environment/build-bash__10.Test__#1.InitEnv.sh

@@ -13,19 +13,27 @@ export basePath=/root/temp
 
 
 #---------------------------------------------------------------------
 #---------------------------------------------------------------------
 echo '#build-bash__10.Test__#1.InitEnv.sh -> #1 start MongoDB container'
 echo '#build-bash__10.Test__#1.InitEnv.sh -> #1 start MongoDB container'
+
+
+docker rm vitorm-mongodb2 -f || true
 docker rm vitorm-mongodb -f || true
 docker rm vitorm-mongodb -f || true
-docker run -d \
---name vitorm-mongodb \
--p 27017:27017 \
--e MONGO_INITDB_ROOT_USERNAME=mongoadmin \
--e MONGO_INITDB_ROOT_PASSWORD=mongoadminsecret \
-mongo:4.4.29
+
+docker run -d --net host --name vitorm-mongodb  mongo:4.4.29 mongod --replSet my-mongo-set
+docker run -d --net host --name vitorm-mongodb2 mongo:4.4.29 mongod --port 27018 --replSet my-mongo-set
+
+
+#---------------------------------------------------------------------
+echo '#build-bash__10.Test__#1.InitEnv.sh -> #2 wait for MongoDB to init'
+docker run -t --rm --net host mongo:4.4.29 timeout 120 sh -c "until (echo 'use db_orm;' | mongo); do echo waiting for MongoDB; sleep 2; done;"
+
+docker run -t --rm --net host mongo:4.4.29 timeout 120 sh -c "until (echo 'use db_orm;' | mongo --host localhost --port 27018); do echo waiting for MongoDB; sleep 2; done;"
 
 
 
 
 #---------------------------------------------------------------------
 #---------------------------------------------------------------------
-echo '#build-bash__10.Test__#1.InitEnv.sh -> #8 wait for MongoDB to init'
-docker run -t --rm --link vitorm-mongodb mongo:4.4.29 timeout 120 sh -c "until (echo 'use db_dev' | mongo --host vitorm-mongodb -u mongoadmin -p mongoadminsecret); do echo waiting for MongoDB; sleep 2; done;"
+echo '#build-bash__10.Test__#1.InitEnv.sh -> #3 start replica set'
+docker run -t --rm --net host mongo:4.4.29 sh -c "echo 'rs.initiate({\"_id\":\"my-mongo-set\",\"members\":[{\"_id\":0,\"host\":\"localhost:27017\"},{\"_id\":1,\"host\":\"localhost:27018\"}]})' | mongo"
 
 
+docker run -t --rm --net host mongo:4.4.29 timeout 120 sh -c "until (echo 'use db_orm;\nuse db_orm2;' | mongo); do echo waiting for MongoDB; sleep 2; done;"
 
 
 
 
 #---------------------------------------------------------------------
 #---------------------------------------------------------------------

+ 1 - 1
Publish/environment/build-bash__10.Test__#3.CleanEnv.sh

@@ -16,7 +16,7 @@ echo '#build-bash__10.Test_#3.CleanEnv.sh'
 
 
 
 
 echo '#build-bash__10.Test_#3.CleanEnv.sh -> #1 remove MongoDB'
 echo '#build-bash__10.Test_#3.CleanEnv.sh -> #1 remove MongoDB'
+docker rm vitorm-mongodb2 -f || true
 docker rm vitorm-mongodb -f || true
 docker rm vitorm-mongodb -f || true
 
 
 
 
-

+ 12 - 0
doc/ReleaseNotes.md

@@ -1 +1,13 @@
 # Vitorm.MongoDB ReleaseNotes
 # Vitorm.MongoDB ReleaseNotes
+
+-----------------------
+# 2.4.0
+- support Transaction
+- upgrade net to 8.0
+- support group query
+- support PlainDistinctSearch
+- support Count
+- support ToListAsync
+- support ToExecuteString
+- support FirstOrDefault
+

+ 2 - 2
src/Versions.props

@@ -1,7 +1,7 @@
 <Project>
 <Project>
     <PropertyGroup>
     <PropertyGroup>
-        <Version>2.3.0-develop</Version>
-        <Vitorm_Version>[2.3.0, 2.4.0)</Vitorm_Version>
+        <Version>2.5.0</Version>
+        <Vitorm_Version>[2.5.0, 2.6.0)</Vitorm_Version>
     </PropertyGroup>
     </PropertyGroup>
 
 
     <PropertyGroup>
     <PropertyGroup>

+ 18 - 18
src/Vitorm.MongoDB/DbConfig.cs

@@ -7,13 +7,9 @@ namespace Vitorm.MongoDB
 {
 {
     public class DbConfig
     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)
+        public DbConfig(string database, string connectionString, string readOnlyConnectionString = null, int? commandTimeout = null)
         {
         {
+            this.database = database;
             this.connectionString = connectionString;
             this.connectionString = connectionString;
             this.readOnlyConnectionString = readOnlyConnectionString;
             this.readOnlyConnectionString = readOnlyConnectionString;
             this.commandTimeout = commandTimeout;
             this.commandTimeout = commandTimeout;
@@ -35,31 +31,35 @@ namespace Vitorm.MongoDB
             if (config.TryGetValue("commandTimeout", out value) && value is Int32 commandTimeout)
             if (config.TryGetValue("commandTimeout", out value) && value is Int32 commandTimeout)
                 this.commandTimeout = commandTimeout;
                 this.commandTimeout = commandTimeout;
         }
         }
-        public string database { get; set; }
+        public string database { get; protected set; }
 
 
-        public string connectionString { get; set; }
-        public string readOnlyConnectionString { get; set; }
-        public int? commandTimeout { get; set; }
+        public string connectionString { get; protected set; }
+        public string readOnlyConnectionString { get; protected set; }
+        public int? commandTimeout { get; protected set; }
 
 
-        MongoClient client;
-        MongoClient readOnlyClient;
+        protected MongoClient client;
+        protected MongoClient readOnlyClient;
 
 
-        MongoClient Client => client ??= new MongoClient(connectionString);
-        MongoClient ReadOnlyClient => readOnlyClient ??= new MongoClient(readOnlyConnectionString);
+        public virtual MongoClient Client => client ??= new MongoClient(connectionString);
+        public virtual MongoClient ReadOnlyClient => readOnlyClient ??= new MongoClient(readOnlyConnectionString);
 
 
-        public IMongoDatabase GetDatabase() => Client.GetDatabase(database);
-        public IMongoDatabase GetReadOnlyDatabase() => ReadOnlyClient.GetDatabase(database);
+        public virtual IMongoDatabase GetDatabase() => Client.GetDatabase(database);
+        public virtual IMongoDatabase GetReadOnlyDatabase() => ReadOnlyClient.GetDatabase(database);
 
 
 
 
+        public virtual DbConfig WithDatabase(string databaseName)
+        {
+            return new(databaseName, connectionString, readOnlyConnectionString, commandTimeout);
+        }
 
 
 
 
-        internal string dbHashCode => connectionString.GetHashCode().ToString();
+        internal string dbHashCode => connectionString.GetHashCode().ToString() + "::" + database;
 
 
 
 
 
 
         /// <summary>
         /// <summary>
         /// to identify whether contexts are from the same database
         /// to identify whether contexts are from the same database
         /// </summary>
         /// </summary>
-        public virtual string dbGroupName => "DbSet_" + dbHashCode;
+        public virtual string dbGroupName => "MongoDb_DbSet_" + dbHashCode;
     }
     }
 }
 }

+ 85 - 49
src/Vitorm.MongoDB/DbContext.cs

@@ -1,21 +1,43 @@
 using System;
 using System;
 using System.Collections;
 using System.Collections;
-using System.Data;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
 using MongoDB.Bson;
 using MongoDB.Bson;
+using MongoDB.Driver;
 
 
 using Vit.Linq;
 using Vit.Linq;
 
 
 using Vitorm.Entity;
 using Vitorm.Entity;
 using Vitorm.Entity.PropertyType;
 using Vitorm.Entity.PropertyType;
 using Vitorm.MongoDB.QueryExecutor;
 using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.MongoDB.SearchExecutor;
+using Vitorm.MongoDB.Transaction;
 using Vitorm.StreamQuery;
 using Vitorm.StreamQuery;
+using Vitorm.Transaction;
 
 
 namespace Vitorm.MongoDB
 namespace Vitorm.MongoDB
 {
 {
     public partial class DbContext : Vitorm.DbContext
     public partial class DbContext : Vitorm.DbContext
     {
     {
+
+        public override void Dispose()
+        {
+            try
+            {
+                transactionManager?.Dispose();
+            }
+            finally
+            {
+                transactionManager = null;
+                base.Dispose();
+            }
+        }
+
+
+
+
+
         public DbConfig dbConfig { get; protected set; }
         public DbConfig dbConfig { get; protected set; }
 
 
         public DbContext(DbConfig dbConfig) : base(DbSetConstructor.CreateDbSet)
         public DbContext(DbConfig dbConfig) : base(DbSetConstructor.CreateDbSet)
@@ -23,21 +45,31 @@ namespace Vitorm.MongoDB
             this.dbConfig = dbConfig;
             this.dbConfig = dbConfig;
         }
         }
 
 
-        public DbContext(string connectionString) : this(new DbConfig(connectionString))
+        public DbContext(string database, string connectionString) : this(new DbConfig(database, connectionString))
         {
         {
         }
         }
 
 
 
 
-        #region Transaction
-        public virtual IDbTransaction BeginTransaction() => throw new System.NotImplementedException();
-        public virtual IDbTransaction GetCurrentTransaction() => throw new System.NotImplementedException();
 
 
-        #endregion
 
 
+        #region Transaction  
+        protected virtual TransactionManager transactionManager { get; set; }
 
 
+        public override ITransaction BeginTransaction()
+        {
+            transactionManager ??= new TransactionManager(this);
+            return transactionManager.BeginTransaction();
+        }
+        public virtual IClientSessionHandle session => transactionManager?.session;
 
 
-        public virtual string databaseName => throw new System.NotImplementedException();
-        public virtual void ChangeDatabase(string databaseName) => throw new System.NotImplementedException();
+        #endregion
+
+
+        public virtual string databaseName => dbConfig.database;
+        public virtual void ChangeDatabase(string databaseName)
+        {
+            dbConfig = dbConfig.WithDatabase(databaseName);
+        }
 
 
 
 
         #region StreamReader
         #region StreamReader
@@ -45,34 +77,35 @@ namespace Vitorm.MongoDB
         public StreamReader streamReader = defaultStreamReader;
         public StreamReader streamReader = defaultStreamReader;
         #endregion
         #endregion
 
 
-        #region StreamReader
+        #region TranslateService
         public static TranslateService defaultTranslateService = new TranslateService();
         public static TranslateService defaultTranslateService = new TranslateService();
         public TranslateService translateService = defaultTranslateService;
         public TranslateService translateService = defaultTranslateService;
         #endregion
         #endregion
 
 
-        #region Serialize
 
 
-        public virtual BsonDocument Serialize(object entity, IEntityDescriptor entityDescriptor)
-        {
-            return SerializeObject(entity, entityDescriptor.propertyType) as BsonDocument;
-        }
+        #region SearchExecutor
+        public static List<ISearchExecutor> defaultSearchExecutor = new() {
+            new PlainExecutor(),
+            new GroupExecutor(),
+            new PlainDistinctExecutor(),
+        };
+        public List<ISearchExecutor> searchExecutor = defaultSearchExecutor;
 
 
-        protected virtual BsonValue SerializeObject(object entity, IPropertyObjectType objectType)
+        public virtual ISearchExecutor GetSearchExecutor(QueryExecutorArgument arg)
         {
         {
-            if (entity == null) return BsonValue.Create(null);
+            return searchExecutor.FirstOrDefault(m => m.IsMatch(arg));
+        }
+        #endregion
 
 
-            var doc = new BsonDocument();
 
 
-            objectType.properties.ForEach(propertyDescriptor =>
-            {
-                var value = propertyDescriptor.GetValue(entity);
-                doc.Set(propertyDescriptor.columnName, SerializeProperty(value, propertyDescriptor.propertyType));
-            });
+        #region Serialize
 
 
-            return doc;
+        public virtual BsonDocument Serialize(object entity, IEntityDescriptor entityDescriptor)
+        {
+            return Serialize(entity, entityDescriptor.propertyType) as BsonDocument;
         }
         }
 
 
-        protected virtual BsonValue SerializeProperty(object value, IPropertyType propertyType)
+        public virtual BsonValue Serialize(object value, IPropertyType propertyType)
         {
         {
             switch (propertyType)
             switch (propertyType)
             {
             {
@@ -83,15 +116,23 @@ namespace Vitorm.MongoDB
                         var bsonArray = new BsonArray();
                         var bsonArray = new BsonArray();
                         foreach (var item in enumerable)
                         foreach (var item in enumerable)
                         {
                         {
-                            bsonArray.Add(SerializeProperty(item, arrayType.elementPropertyType));
+                            bsonArray.Add(Serialize(item, arrayType.elementPropertyType));
                         }
                         }
                         return bsonArray;
                         return bsonArray;
                     }
                     }
                 case IPropertyObjectType objectType:
                 case IPropertyObjectType objectType:
                     {
                     {
-                        if (value == null) break;
+                        var entity = value;
+                        if (entity == null) return BsonValue.Create(null);
 
 
-                        return SerializeObject(value, objectType);
+                        var doc = new BsonDocument();
+                        objectType.properties.ForEach(propertyDescriptor =>
+                        {
+                            var value = propertyDescriptor.GetValue(entity);
+                            doc.Set(propertyDescriptor.columnName, Serialize(value, propertyDescriptor.propertyType));
+                        });
+
+                        return doc;
                     }
                     }
                 case IPropertyValueType valueType:
                 case IPropertyValueType valueType:
                     {
                     {
@@ -103,37 +144,20 @@ namespace Vitorm.MongoDB
 
 
         #endregion
         #endregion
 
 
-
-
         #region Deserialize
         #region Deserialize
 
 
         public virtual Entity Deserialize<Entity>(BsonDocument doc, IEntityDescriptor entityDescriptor)
         public virtual Entity Deserialize<Entity>(BsonDocument doc, IEntityDescriptor entityDescriptor)
         {
         {
-            return (Entity)Deserialize(doc, entityDescriptor);
+            return (Entity)Deserialize(doc, entityDescriptor.propertyType);
         }
         }
 
 
         public virtual object Deserialize(BsonDocument doc, IEntityDescriptor entityDescriptor)
         public virtual object Deserialize(BsonDocument doc, IEntityDescriptor entityDescriptor)
         {
         {
-            return DeserializeObject(doc, entityDescriptor.entityType, entityDescriptor.properties);
+            return Deserialize(doc, entityDescriptor.propertyType);
         }
         }
 
 
-        protected virtual object DeserializeObject(BsonDocument doc, Type clrType, IPropertyDescriptor[] properties)
-        {
-            if (doc == null) return TypeUtil.GetDefaultValue(clrType);
-            var entity = Activator.CreateInstance(clrType);
-
-            properties.ForEach(propertyDescriptor =>
-            {
-                if (!doc.TryGetValue(propertyDescriptor.columnName, out var bsonValue)) return;
-                var propertyValue = DeserializeProperty(bsonValue, propertyDescriptor.propertyType);
-                propertyDescriptor.SetValue(entity, propertyValue);
-            });
-
-            return entity;
-        }
 
 
-
-        protected virtual object DeserializeProperty(BsonValue bsonValue, IPropertyType propertyType)
+        public virtual object Deserialize(BsonValue bsonValue, IPropertyType propertyType)
         {
         {
             switch (propertyType)
             switch (propertyType)
             {
             {
@@ -142,7 +166,7 @@ namespace Vitorm.MongoDB
                         if (bsonValue?.BsonType != BsonType.Array) return null;
                         if (bsonValue?.BsonType != BsonType.Array) return null;
 
 
                         var bsonArray = bsonValue.AsBsonArray;
                         var bsonArray = bsonValue.AsBsonArray;
-                        var elements = bsonArray.Select(m => DeserializeProperty(m, arrayType.elementPropertyType));
+                        var elements = bsonArray.Select(m => Deserialize(m, arrayType.elementPropertyType));
                         return arrayType.CreateArray(elements);
                         return arrayType.CreateArray(elements);
                     }
                     }
                 case IPropertyObjectType objectType:
                 case IPropertyObjectType objectType:
@@ -150,7 +174,19 @@ namespace Vitorm.MongoDB
                         if (bsonValue?.BsonType != BsonType.Document) return null;
                         if (bsonValue?.BsonType != BsonType.Document) return null;
 
 
                         var bsonDoc = bsonValue.AsBsonDocument;
                         var bsonDoc = bsonValue.AsBsonDocument;
-                        return DeserializeObject(bsonDoc, objectType.type, objectType.properties);
+                        var clrType = objectType.type;
+
+                        if (bsonDoc == null) return TypeUtil.GetDefaultValue(clrType);
+
+                        var entity = Activator.CreateInstance(clrType);
+                        objectType.properties?.ForEach(propertyDescriptor =>
+                        {
+                            if (!bsonDoc.TryGetValue(propertyDescriptor.columnName, out var bsonValue)) return;
+                            var propertyValue = Deserialize(bsonValue, propertyDescriptor.propertyType);
+                            propertyDescriptor.SetValue(entity, propertyValue);
+                        });
+
+                        return entity;
                     }
                     }
                 case IPropertyValueType valueType:
                 case IPropertyValueType valueType:
                     {
                     {

+ 12 - 28
src/Vitorm.MongoDB/DbSet.Query.cs

@@ -19,21 +19,6 @@ namespace Vitorm.MongoDB
             return QueryableBuilder.Build<Entity>(QueryExecutor, DbContext.dbConfig.dbGroupName);
             return QueryableBuilder.Build<Entity>(QueryExecutor, DbContext.dbConfig.dbGroupName);
         }
         }
 
 
-        protected object QueryExecutor(Expression expression, Type expressionResultType)
-        {
-            object result = null;
-            Action dispose = null;
-            try
-            {
-                return result = ExecuteQuery(expression, expressionResultType, dispose);
-            }
-            catch
-            {
-                dispose?.Invoke();
-                throw;
-            }
-        }
-
 
 
         #region QueryExecutor
         #region QueryExecutor
 
 
@@ -54,22 +39,22 @@ namespace Vitorm.MongoDB
             //// Orm_Extensions
             //// Orm_Extensions
             //AddDefaultQueryExecutor(ExecuteUpdate.Instance);
             //AddDefaultQueryExecutor(ExecuteUpdate.Instance);
             //AddDefaultQueryExecutor(ExecuteDelete.Instance);
             //AddDefaultQueryExecutor(ExecuteDelete.Instance);
-            //AddDefaultQueryExecutor(ToExecuteString.Instance);
+            AddDefaultQueryExecutor(ToExecuteString.Instance);
 
 
             // ToList
             // ToList
             AddDefaultQueryExecutor(ToList.Instance);
             AddDefaultQueryExecutor(ToList.Instance);
-            //// Count TotalCount
-            //AddDefaultQueryExecutor(Count.Instance);
-            //AddDefaultQueryExecutor(Count.Instance, methodName: nameof(Queryable_Extensions.TotalCount));
+            // Count TotalCount
+            AddDefaultQueryExecutor(Count.Instance);
+            AddDefaultQueryExecutor(Count.Instance, methodName: nameof(Queryable_Extensions.TotalCount));
 
 
             //// ToListAndTotalCount
             //// ToListAndTotalCount
             //AddDefaultQueryExecutor(ToListAndTotalCount.Instance);
             //AddDefaultQueryExecutor(ToListAndTotalCount.Instance);
 
 
-            //// FirstOrDefault First LastOrDefault Last
-            //AddDefaultQueryExecutor(FirstOrDefault.Instance);
-            //AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.First));
-            //AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.LastOrDefault));
-            //AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.Last));
+            // FirstOrDefault First LastOrDefault Last
+            AddDefaultQueryExecutor(FirstOrDefault.Instance);
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.First));
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.LastOrDefault));
+            AddDefaultQueryExecutor(FirstOrDefault.Instance, methodName: nameof(Queryable.Last));
             #endregion
             #endregion
 
 
 
 
@@ -78,8 +63,8 @@ namespace Vitorm.MongoDB
             //AddDefaultQueryExecutor(ExecuteUpdateAsync.Instance);
             //AddDefaultQueryExecutor(ExecuteUpdateAsync.Instance);
             //AddDefaultQueryExecutor(ExecuteDeleteAsync.Instance);
             //AddDefaultQueryExecutor(ExecuteDeleteAsync.Instance);
 
 
-            //// ToList
-            //AddDefaultQueryExecutor(ToListAsync.Instance);
+            // ToList
+            AddDefaultQueryExecutor(ToListAsync.Instance);
             //// Count TotalCount
             //// Count TotalCount
             //AddDefaultQueryExecutor(CountAsync.Instance);
             //AddDefaultQueryExecutor(CountAsync.Instance);
             //AddDefaultQueryExecutor(CountAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.TotalCountAsync));
             //AddDefaultQueryExecutor(CountAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.TotalCountAsync));
@@ -114,7 +99,7 @@ namespace Vitorm.MongoDB
             return false;
             return false;
         }
         }
 
 
-        protected virtual object ExecuteQuery(Expression expression, Type expressionResultType, Action dispose)
+        protected virtual object QueryExecutor(Expression expression, Type expressionResultType)
         {
         {
             // #1 convert to ExpressionNode 
             // #1 convert to ExpressionNode 
             ExpressionNode_Lambda node = DbContext.convertService.ConvertToData_LambdaNode(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
             ExpressionNode_Lambda node = DbContext.convertService.ConvertToData_LambdaNode(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
@@ -133,7 +118,6 @@ namespace Vitorm.MongoDB
                 dbContext = DbContext,
                 dbContext = DbContext,
                 expression = expression,
                 expression = expression,
                 expressionResultType = expressionResultType,
                 expressionResultType = expressionResultType,
-                dispose = dispose,
             };
             };
 
 
 
 

+ 75 - 24
src/Vitorm.MongoDB/DbSet.cs

@@ -52,7 +52,7 @@ namespace Vitorm.MongoDB
 
 
         public virtual bool TableExists()
         public virtual bool TableExists()
         {
         {
-            var collectionNames = database.ListCollectionNames().ToList();
+            var collectionNames = DbContext.session == null ? database.ListCollectionNames().ToList() : database.ListCollectionNames(DbContext.session).ToList();
             var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
             var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
 
 
             // Event_OnExecuting
             // Event_OnExecuting
@@ -73,7 +73,7 @@ namespace Vitorm.MongoDB
         }
         }
         public virtual async Task<bool> TableExistsAsync()
         public virtual async Task<bool> TableExistsAsync()
         {
         {
-            var collectionNames = await (await database.ListCollectionNamesAsync()).ToListAsync();
+            var collectionNames = DbContext.session == null ? await (await database.ListCollectionNamesAsync()).ToListAsync() : await (await database.ListCollectionNamesAsync(DbContext.session)).ToListAsync();
             var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
             var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
 
 
             // Event_OnExecuting
             // Event_OnExecuting
@@ -114,7 +114,10 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            collection.Indexes.CreateOne(indexModel);
+            if (DbContext.session == null)
+                collection.Indexes.CreateOne(indexModel);
+            else
+                collection.Indexes.CreateOne(DbContext.session, indexModel);
         }
         }
         public virtual async Task CreateIndexAsync(string field, bool ascending = true, bool unique = false)
         public virtual async Task CreateIndexAsync(string field, bool ascending = true, bool unique = false)
         {
         {
@@ -134,7 +137,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            await collection.Indexes.CreateOneAsync(indexModel);
+            if (DbContext.session == null)
+                await collection.Indexes.CreateOneAsync(indexModel);
+            else
+                await collection.Indexes.CreateOneAsync(DbContext.session, indexModel);
+
         }
         }
 
 
 
 
@@ -156,7 +163,12 @@ namespace Vitorm.MongoDB
 
 
             if (TableExists()) return;
             if (TableExists()) return;
 
 
-            database.CreateCollection(entityDescriptor.tableName);
+
+            if (DbContext.session == null)
+                database.CreateCollection(entityDescriptor.tableName);
+            else
+                database.CreateCollection(DbContext.session, entityDescriptor.tableName);
+
 
 
             // create unique index
             // create unique index
             if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
             if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
@@ -182,7 +194,12 @@ namespace Vitorm.MongoDB
 
 
             if (await TableExistsAsync()) return;
             if (await TableExistsAsync()) return;
 
 
-            await database.CreateCollectionAsync(entityDescriptor.tableName);
+
+            if (DbContext.session == null)
+                await database.CreateCollectionAsync(entityDescriptor.tableName);
+            else
+                await database.CreateCollectionAsync(DbContext.session, entityDescriptor.tableName);
+
 
 
             // create unique index
             // create unique index
             if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
             if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
@@ -205,7 +222,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            database.DropCollection(entityDescriptor.tableName);
+            if (DbContext.session == null)
+                database.DropCollection(entityDescriptor.tableName);
+            else
+                database.DropCollection(DbContext.session, entityDescriptor.tableName);
+
         }
         }
         public virtual async Task TryDropTableAsync()
         public virtual async Task TryDropTableAsync()
         {
         {
@@ -221,7 +242,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            await database.DropCollectionAsync(entityDescriptor.tableName);
+
+            if (DbContext.session == null)
+                await database.DropCollectionAsync(entityDescriptor.tableName);
+            else
+                await database.DropCollectionAsync(DbContext.session, entityDescriptor.tableName);
         }
         }
 
 
         public virtual void Truncate()
         public virtual void Truncate()
@@ -238,7 +263,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            collection.DeleteMany(m => true);
+            if (DbContext.session == null)
+                collection.DeleteMany(m => true);
+            else
+                collection.DeleteMany(DbContext.session, m => true);
+
         }
         }
         public virtual async Task TruncateAsync()
         public virtual async Task TruncateAsync()
         {
         {
@@ -254,7 +283,12 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            await collection.DeleteManyAsync(m => true);
+
+            if (DbContext.session == null)
+                await collection.DeleteManyAsync(m => true);
+            else
+                await collection.DeleteManyAsync(DbContext.session, m => true);
+
         }
         }
         #endregion
         #endregion
 
 
@@ -278,7 +312,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            collection.InsertOne(doc);
+            if (DbContext.session == null)
+                collection.InsertOne(doc);
+            else
+                collection.InsertOne(DbContext.session, doc);
+
             return entity;
             return entity;
         }
         }
 
 
@@ -301,7 +339,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            await collection.InsertOneAsync(doc);
+            if (DbContext.session == null)
+                await collection.InsertOneAsync(doc);
+            else
+                await collection.InsertOneAsync(DbContext.session, doc);
+
             return entity;
             return entity;
         }
         }
 
 
@@ -323,7 +365,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            collection.InsertMany(docs);
+            if (DbContext.session == null)
+                collection.InsertMany(docs);
+            else
+                collection.InsertMany(DbContext.session, docs);
+
         }
         }
 
 
 
 
@@ -345,7 +391,11 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            await collection.InsertManyAsync(docs);
+            if (DbContext.session == null)
+                await collection.InsertManyAsync(docs);
+            else
+                await collection.InsertManyAsync(DbContext.session, docs);
+
         }
         }
         #endregion
         #endregion
 
 
@@ -368,7 +418,9 @@ namespace Vitorm.MongoDB
                 }))
                 }))
             );
             );
 
 
-            return Deserialize(collection.Find(predicate).FirstOrDefault());
+            var fluent = DbContext.session == null ? collection.Find(predicate) : collection.Find(DbContext.session, predicate);
+
+            return Deserialize(fluent.FirstOrDefault());
         }
         }
 
 
         public virtual async Task<Entity> GetAsync(object keyValue)
         public virtual async Task<Entity> GetAsync(object keyValue)
@@ -386,8 +438,8 @@ namespace Vitorm.MongoDB
                     ["Method"] = "GetAsync"
                     ["Method"] = "GetAsync"
                 }))
                 }))
             );
             );
-
-            return Deserialize(await collection.Find(predicate).FirstOrDefaultAsync());
+            var fluent = DbContext.session == null ? await collection.FindAsync(predicate) : await collection.FindAsync(DbContext.session, predicate);
+            return Deserialize(await fluent.FirstOrDefaultAsync());
         }
         }
 
 
         #endregion
         #endregion
@@ -424,8 +476,7 @@ namespace Vitorm.MongoDB
                     ["doc"] = doc,
                     ["doc"] = doc,
                 })));
                 })));
 
 
-
-            var result = collection.ReplaceOne(predicate, doc);
+            var result = DbContext.session == null ? collection.ReplaceOne(predicate, doc) : collection.ReplaceOne(DbContext.session, predicate, doc);
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
         }
         }
         public virtual async Task<int> UpdateAsync(Entity entity)
         public virtual async Task<int> UpdateAsync(Entity entity)
@@ -447,7 +498,7 @@ namespace Vitorm.MongoDB
                     ["doc"] = doc,
                     ["doc"] = doc,
                 })));
                 })));
 
 
-            var result = await collection.ReplaceOneAsync(predicate, doc);
+            var result = DbContext.session == null ? await collection.ReplaceOneAsync(predicate, doc) : await collection.ReplaceOneAsync(DbContext.session, predicate, doc);
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
         }
         }
 
 
@@ -511,7 +562,7 @@ namespace Vitorm.MongoDB
                     ["key"] = keyValue,
                     ["key"] = keyValue,
                 })));
                 })));
 
 
-            var result = collection.DeleteOne(predicate);
+            var result = DbContext.session == null ? collection.DeleteOne(predicate) : collection.DeleteOne(DbContext.session, predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         }
         public virtual async Task<int> DeleteByKeyAsync(object keyValue)
         public virtual async Task<int> DeleteByKeyAsync(object keyValue)
@@ -531,7 +582,7 @@ namespace Vitorm.MongoDB
                     ["key"] = keyValue,
                     ["key"] = keyValue,
                 })));
                 })));
 
 
-            var result = await collection.DeleteOneAsync(predicate);
+            var result = DbContext.session == null ? await collection.DeleteOneAsync(predicate) : await collection.DeleteOneAsync(DbContext.session, predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         }
 
 
@@ -554,7 +605,7 @@ namespace Vitorm.MongoDB
                     ["keys"] = keys,
                     ["keys"] = keys,
                 })));
                 })));
 
 
-            var result = collection.DeleteMany(predicate);
+            var result = DbContext.session == null ? collection.DeleteMany(predicate) : collection.DeleteMany(DbContext.session, predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         }
         public virtual async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys)
         public virtual async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys)
@@ -574,7 +625,7 @@ namespace Vitorm.MongoDB
                     ["keys"] = keys,
                     ["keys"] = keys,
                 })));
                 })));
 
 
-            var result = await collection.DeleteManyAsync(predicate);
+            var result = DbContext.session == null ? await collection.DeleteManyAsync(predicate) : await collection.DeleteManyAsync(DbContext.session, predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         }
 
 

+ 118 - 0
src/Vitorm.MongoDB/EntityReader/EntityReader.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+using MongoDB.Bson;
+
+using Vit.Linq.ExpressionNodes;
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.Entity.PropertyType;
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.EntityReader
+{
+    /// <summary>
+    ///  get all sql column values, compile EntityGenerator to Lambda .  Invoke the lambda when reading rows , pass sql column values as lambda args. 
+    /// </summary>
+    public class EntityReader
+    {
+        public List<IArgReader> entityArgReaders = new List<IArgReader>();
+        protected Delegate lambdaCreateEntity;
+
+        public void Init(DbContext dbContext, Type entityType, ExpressionNode resultSelector)
+        {
+            var cloner = new ExpressionNodeCloner();
+            cloner.clone = (node) =>
+            {
+                if (node?.nodeType == NodeType.Member)
+                {
+                    ExpressionNode_Member member = node;
+
+                    var argName = GetArgument(dbContext, member);
+
+                    if (argName != null)
+                    {
+                        return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
+                    }
+                }
+                else if (node?.nodeType == NodeType.MethodCall)
+                {
+                    ExpressionNode_MethodCall methodCall = node;
+
+                    //// deal with aggregate functions like Sum(id)
+                    //if (methodCall.methodCall_typeName == "Enumerable")
+                    //{
+                    //    string argName = null;
+
+                    //    var sqlColumnSentence = sqlTranslateService.EvalExpression(arg, node);
+                    //    var columnType = methodCall.MethodCall_GetReturnType();
+                    //    argName = GetArgument(config, sqlColumnSentence, columnType);
+                    //    if (argName != null)
+                    //    {
+                    //        return (true, ExpressionNode.Member(parameterName: argName, memberName: null));
+                    //    }
+                    //}
+                    throw new InvalidOperationException();
+                }
+                return default;
+            };
+            ExpressionNode newResultSelector = cloner.Clone(resultSelector);
+
+            // compile ResultCreate lambda
+            lambdaCreateEntity = CompileExpression(dbContext.convertService, entityArgReaders.Select(m => m.argName).ToArray(), newResultSelector);
+        }
+
+        public object ReadEntity(BsonDocument reader)
+        {
+            var lambdaArgs = entityArgReaders.Select(m => m.Read(reader)).ToArray();
+            var entity = lambdaCreateEntity.DynamicInvoke(lambdaArgs);
+            return entity;
+        }
+
+
+
+        protected string GetArgument(DbContext dbContext, ExpressionNode_Member member)
+        {
+            var fieldPath = QueryExecutorArgument.GetFieldPath(dbContext, (ExpressionNode)member, out Type t);
+
+            IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.fieldPath == fieldPath);
+
+            if (argReader == null)
+            {
+                var argName = "arg_" + entityArgReaders.Count;
+
+                var argType = member.Member_GetType();
+
+                bool isValueType = TypeUtil.IsValueType(argType);
+                if (isValueType)
+                {
+                    // Value arg 
+                    argReader = new ValueReader(argType, fieldPath, argName);
+                }
+                else
+                {
+                    // Entity arg
+                    var fieldPath2 = QueryExecutorArgument.GetFieldPath(dbContext, (ExpressionNode)member, out IPropertyType propertyType);
+
+                    argReader = new ModelReader(dbContext, propertyType, argType, fieldPath ?? "$ROOT", argName);
+                }
+                entityArgReaders.Add(argReader);
+            }
+            return argReader.argName;
+        }
+
+
+        Delegate CompileExpression(ExpressionConvertService convertService, string[] parameterNames, ExpressionNode newExp)
+        {
+            var lambdaNode = ExpressionNode.Lambda(entityArgReaders.Select(m => m.argName).ToArray(), newExp);
+            // var strNode = Json.Serialize(lambdaNode);
+
+            var lambdaExp = convertService.ConvertToCode_LambdaExpression(lambdaNode, entityArgReaders.Select(m => m.entityType).ToArray());
+
+            return lambdaExp.Compile();
+        }
+
+    }
+}

+ 15 - 0
src/Vitorm.MongoDB/EntityReader/IArgReader.cs

@@ -0,0 +1,15 @@
+using System;
+
+using MongoDB.Bson;
+
+namespace Vitorm.MongoDB.EntityReader
+{
+    public interface IArgReader
+    {
+        string fieldPath { get; }
+        string argName { get; }
+        Type entityType { get; }
+        object Read(BsonDocument reader);
+    }
+
+}

+ 36 - 0
src/Vitorm.MongoDB/EntityReader/ModelReader.cs

@@ -0,0 +1,36 @@
+using System;
+
+using MongoDB.Bson;
+
+using Vitorm.Entity.PropertyType;
+
+namespace Vitorm.MongoDB.EntityReader
+{
+    class ModelReader : IArgReader
+    {
+        public string argName { get; set; }
+        public string fieldPath { get; set; }
+        public Type entityType { get; }
+        IPropertyType propertyType;
+        DbContext dbContext;
+        public ModelReader(DbContext dbContext, IPropertyType propertyType, Type entityType, string fieldPath, string argName)
+        {
+            this.dbContext = dbContext;
+            this.entityType = entityType;
+            this.propertyType = propertyType;
+
+            this.fieldPath = fieldPath;
+            this.argName = argName;
+        }
+
+        public object Read(BsonDocument reader)
+        {
+            var bsonValue = reader[argName];
+            return dbContext.Deserialize(bsonValue, propertyType);
+        }
+
+    }
+
+
+
+}

+ 34 - 0
src/Vitorm.MongoDB/EntityReader/ValueReader.cs

@@ -0,0 +1,34 @@
+using System;
+
+using MongoDB.Bson;
+
+namespace Vitorm.MongoDB.EntityReader
+{
+    class ValueReader : IArgReader
+    {
+        public string argName { get; set; }
+
+        public string fieldPath { get; set; }
+
+        protected Type valueType { get; set; }
+        protected Type underlyingType;
+        public Type entityType { get => valueType; }
+
+        public ValueReader(Type valueType, string fieldPath, string argName)
+        {
+            this.valueType = valueType;
+            underlyingType = TypeUtil.GetUnderlyingType(valueType);
+
+            this.fieldPath = fieldPath;
+            this.argName = argName;
+        }
+        public object Read(BsonDocument reader)
+        {
+            var bsonValue = reader[argName];
+            var value = BsonTypeMapper.MapToDotNetValue(bsonValue);
+            return TypeUtil.ConvertToUnderlyingType(value, underlyingType);
+        }
+    }
+
+
+}

+ 57 - 0
src/Vitorm.MongoDB/QueryExecutor/Async/ToListAsync.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using MongoDB.Driver;
+
+using Vit.Linq;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public partial class ToListAsync : IQueryExecutor
+    {
+        public static readonly ToListAsync Instance = new();
+
+        public string methodName => nameof(Queryable_Extensions.ToListAsync);
+
+        public object ExecuteQuery(QueryExecutorArgument arg)
+        {
+
+            IQueryable query = null;
+            if (arg.combinedStream.source is SourceStream sourceStream)
+            {
+                query = sourceStream.GetSource() as IQueryable;
+            }
+            else if (arg.combinedStream.source is CombinedStream baseStream)
+            {
+                query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
+            }
+
+            var entityType = query.ElementType;
+            var resultEntityType = arg.expression.Type.GetGenericArguments().First().GetGenericArguments().First();
+
+            return Execute_MethodInfo(entityType, resultEntityType).Invoke(null, new object[] { arg });
+        }
+
+
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType, Type resultEntityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<List<string>>>(Execute<object, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType);
+
+
+        public static Task<List<ResultEntity>> Execute<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToListAsync<Entity, ResultEntity>(arg);
+        }
+
+
+    }
+
+}

+ 51 - 0
src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.ReverseOrder.cs

@@ -0,0 +1,51 @@
+using System.Linq;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public partial class QueryExecutorArgument
+    {
+        public virtual void ReverseOrder()
+        {
+            DbContext dbContext = this.dbContext;
+            CombinedStream stream = this.combinedStream;
+
+            stream.orders ??= new();
+            var orders = stream.orders;
+            // make sure orders exist
+            if (!orders.Any())
+            {
+                AddOrder(stream.source);
+                //stream.joins?.ForEach(right => AddOrder(right.right));
+
+                #region AddOrder
+                void AddOrder(IStream source)
+                {
+                    if (source is SourceStream sourceStream)
+                    {
+                        var entityType = sourceStream.GetEntityType();
+                        var entityDescriptor = dbContext.GetEntityDescriptor(entityType);
+                        if (entityDescriptor != null)
+                        {
+                            var parentMember = ExpressionNode.Member(objectValue: null, memberName: null);
+                            parentMember.Member_SetType(entityType);
+
+                            var member = ExpressionNode.Member(objectValue: parentMember, memberName: entityDescriptor.key.propertyName);
+                            member.Member_SetType(entityDescriptor.key.type);
+
+                            orders.Add(new ExpressionNodeOrderField { member = member, asc = true });
+                        }
+                    }
+                }
+                #endregion
+            }
+
+            // reverse order
+            orders?.ForEach(order => order.asc = !order.asc);
+        }
+    }
+
+}

+ 208 - 4
src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.cs

@@ -1,11 +1,15 @@
 using System;
 using System;
+using System.Linq;
 using System.Linq.Expressions;
 using System.Linq.Expressions;
 
 
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.Entity.PropertyType;
 using Vitorm.StreamQuery;
 using Vitorm.StreamQuery;
 
 
 namespace Vitorm.MongoDB.QueryExecutor
 namespace Vitorm.MongoDB.QueryExecutor
 {
 {
-    public class QueryExecutorArgument : IDisposable
+    public partial class QueryExecutorArgument
     {
     {
         public CombinedStream combinedStream;
         public CombinedStream combinedStream;
         public DbContext dbContext;
         public DbContext dbContext;
@@ -13,11 +17,211 @@ namespace Vitorm.MongoDB.QueryExecutor
         public Expression expression;
         public Expression expression;
         public Type expressionResultType;
         public Type expressionResultType;
 
 
-        public Action dispose;
 
 
-        public void Dispose()
+        public virtual string GetFieldPath(ExpressionNode member) => GetFieldPath(this.dbContext, member, out Type propertyType);
+
+        public static string GetFieldPath(DbContext dbContext, ExpressionNode member, out Type propertyType)
+        {
+            switch (member?.nodeType)
+            {
+                case NodeType.Member:
+                    {
+                        if (member.objectValue != null)
+                        {
+                            // nested field
+                            var parentPath = GetFieldPath(dbContext, member.objectValue, out Type parentPropertyType);
+
+                            // bool?.Value
+                            if (member.memberName == nameof(Nullable<bool>.Value) && TypeUtil.IsNullable(parentPropertyType))
+                            {
+                                propertyType = parentPropertyType;
+                                return parentPath;
+                            }
+
+
+                            if (!string.IsNullOrWhiteSpace(member.memberName))
+                            {
+                                var parentEntityDescriptor = dbContext.GetEntityDescriptor(parentPropertyType);
+                                var propertyDescriptor = parentEntityDescriptor?.properties?.FirstOrDefault(property => property.propertyName == member.memberName);
+
+                                if (propertyDescriptor != null)
+                                {
+                                    propertyType = propertyDescriptor.propertyType.type;
+
+                                    var columnName = propertyDescriptor.columnName;
+
+                                    var fieldPath = columnName;
+                                    if (parentPath != null) fieldPath = parentPath + "." + fieldPath;
+                                    return fieldPath;
+                                }
+                            }
+                            break;
+                        }
+                        else
+                        {
+                            // entity root
+                            propertyType = member.Member_GetType();
+                            if (string.IsNullOrWhiteSpace(member.memberName)) return null;
+                            break;
+                        }
+                    }
+                case NodeType.ArrayIndex:
+                    {
+                        ExpressionNode_ArrayIndex arrayIndex = member;
+
+                        var index = arrayIndex.right.value;
+                        var parentPath = GetFieldPath(dbContext, arrayIndex.left, out Type parentPropertyType);
+
+                        var elementType = TypeUtil.GetElementTypeFromArray(parentPropertyType);
+                        if (elementType != null)
+                        {
+                            propertyType = elementType;
+
+                            var filePath = $"{index}";
+                            if (parentPath != null) filePath = parentPath + "." + filePath;
+                            return filePath;
+                        }
+                        break;
+                    }
+
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = member;
+
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 List.get_Item
+                            case "get_Item" when methodCall.@object is not null && methodCall.arguments.Length == 1:
+                                {
+                                    var index = methodCall.arguments[0]?.value;
+                                    var parentPath = GetFieldPath(dbContext, methodCall.@object, out Type parentPropertyType);
+
+                                    var elementType = TypeUtil.GetElementTypeFromArray(parentPropertyType);
+                                    if (elementType != null)
+                                    {
+                                        propertyType = elementType;
+
+                                        var filePath = $"{index}";
+                                        if (parentPath != null) filePath = parentPath + "." + filePath;
+                                        return filePath;
+                                    }
+                                    break;
+                                }
+                            // ##2 Count
+                            case nameof(Enumerable.Count) when methodCall.@object is null && methodCall.arguments.Length == 1:
+                                {
+                                    propertyType = null;
+                                    return "count";
+                                }
+                        }
+                        break;
+                    }
+            }
+
+            throw new InvalidOperationException($"Can not get fieldPath from member:{member?.nodeType}");
+        }
+
+
+
+        public static string GetFieldPath(DbContext dbContext, ExpressionNode member, out IPropertyType propertyType)
         {
         {
-            dispose?.Invoke();
+            switch (member?.nodeType)
+            {
+                case NodeType.Member:
+                    {
+                        if (member.objectValue != null)
+                        {
+                            // nested field
+                            var parentPath = GetFieldPath(dbContext, member.objectValue, out IPropertyType parentPropertyType);
+
+                            // bool?.Value
+                            if (member.memberName == nameof(Nullable<bool>.Value) && TypeUtil.IsNullable(parentPropertyType.type))
+                            {
+                                propertyType = parentPropertyType;
+                                return parentPath;
+                            }
+
+
+                            if (!string.IsNullOrWhiteSpace(member.memberName) && parentPropertyType is IPropertyObjectType parentObjectType)
+                            {
+                                var propertyDescriptor = parentObjectType.properties?.FirstOrDefault(property => property.propertyName == member.memberName);
+
+                                if (propertyDescriptor != null)
+                                {
+                                    propertyType = propertyDescriptor.propertyType;
+
+                                    var columnName = propertyDescriptor.columnName;
+
+                                    var fieldPath = columnName;
+                                    if (parentPath != null) fieldPath = parentPath + "." + fieldPath;
+                                    return fieldPath;
+                                }
+                            }
+                            break;
+                        }
+                        else
+                        {
+                            // entity root
+                            var entityType = member.Member_GetType();
+                            var entityDescriptor = dbContext.GetEntityDescriptor(entityType);
+                            propertyType = entityDescriptor.propertyType;
+
+                            if (string.IsNullOrWhiteSpace(member.memberName)) return null;
+                            break;
+                        }
+                    }
+                case NodeType.ArrayIndex:
+                    {
+                        ExpressionNode_ArrayIndex arrayIndex = member;
+
+                        var index = arrayIndex.right.value;
+                        var parentPath = GetFieldPath(dbContext, arrayIndex.left, out IPropertyType parentPropertyType);
+
+                        if (parentPropertyType is IPropertyArrayType arrayType)
+                        {
+                            propertyType = arrayType.elementPropertyType;
+
+                            var filePath = $"{index}";
+                            if (parentPath != null) filePath = parentPath + "." + filePath;
+                            return filePath;
+                        }
+                        break;
+                    }
+
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = member;
+
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 List.get_Item
+                            case "get_Item" when methodCall.@object is not null && methodCall.arguments.Length == 1:
+                                {
+                                    var index = methodCall.arguments[0]?.value;
+                                    var parentPath = GetFieldPath(dbContext, methodCall.@object, out IPropertyType parentPropertyType);
+
+                                    if (parentPropertyType is IPropertyArrayType arrayType)
+                                    {
+                                        propertyType = arrayType?.elementPropertyType;
+
+                                        var filePath = $"{index}";
+                                        if (parentPath != null) filePath = parentPath + "." + filePath;
+                                        return filePath;
+                                    }
+                                    break;
+                                }
+                            // ##2 Count
+                            case nameof(Enumerable.Count) when methodCall.@object is null && methodCall.arguments.Length == 1:
+                                {
+                                    propertyType = null;
+                                    return "count";
+                                }
+                        }
+                        break;
+                    }
+            }
+
+            throw new InvalidOperationException($"Can not get fieldPath from member:{member?.nodeType}");
         }
         }
     }
     }
 
 

+ 58 - 0
src/Vitorm.MongoDB/QueryExecutor/Sync/Count.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Linq;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    /// <summary>
+    /// Queryable.Count or Queryable_Extensions.TotalCount
+    /// </summary>
+    public partial class Count : IQueryExecutor
+    {
+        public static readonly Count Instance = new();
+
+        public string methodName => nameof(Queryable.Count);
+
+        public object ExecuteQuery(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+
+            IQueryable query = null;
+            if (combinedStream.source is SourceStream sourceStream)
+            {
+                query = sourceStream.GetSource() as IQueryable;
+            }
+            else if (combinedStream.source is CombinedStream baseStream)
+            {
+                query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
+            }
+
+            var skipAndTake = (combinedStream.skip, combinedStream.take);
+            (combinedStream.skip, combinedStream.take) = (null, null);
+
+            var entityType = query?.ElementType;
+
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            var count = executor.Count(arg, entityType);
+            (combinedStream.skip, combinedStream.take) = skipAndTake;
+
+            // Count and TotalCount
+            if (count > 0 && combinedStream.method == nameof(Queryable.Count))
+            {
+                if (combinedStream.skip > 0) count = Math.Max(count - combinedStream.skip.Value, 0);
+
+                if (combinedStream.take.HasValue)
+                    count = Math.Min(count, combinedStream.take.Value);
+            }
+
+            return count;
+        }
+
+
+
+
+
+    }
+}

+ 51 - 0
src/Vitorm.MongoDB/QueryExecutor/Sync/FirstOrDefault.cs

@@ -0,0 +1,51 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public partial class FirstOrDefault : IQueryExecutor
+    {
+        public static readonly FirstOrDefault Instance = new();
+
+        public string methodName => nameof(Queryable.FirstOrDefault);
+
+        public object ExecuteQuery(QueryExecutorArgument arg)
+        {
+            IQueryable query = null;
+            if (arg.combinedStream.source is SourceStream sourceStream)
+            {
+                query = sourceStream.GetSource() as IQueryable;
+            }
+            else if (arg.combinedStream.source is CombinedStream baseStream)
+            {
+                query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
+            }
+
+            var entityType = query?.ElementType;
+            var resultEntityType = arg.expression.Type;
+
+            return Execute_MethodInfo(entityType, resultEntityType).Invoke(null, new object[] { arg });
+        }
+
+
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType, Type resultEntityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, string>(Execute<object, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType);
+
+
+
+        public static ResultEntity Execute<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.FirstOrDefault<Entity, ResultEntity>(arg);
+        }
+
+
+
+    }
+}

+ 35 - 0
src/Vitorm.MongoDB/QueryExecutor/Sync/ToExecuteString.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Linq;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public partial class ToExecuteString : IQueryExecutor
+    {
+        public static readonly ToExecuteString Instance = new();
+
+        public string methodName => nameof(Orm_Extensions.ToExecuteString);
+
+        public object ExecuteQuery(QueryExecutorArgument arg)
+        {
+            IQueryable query = null;
+            if (arg.combinedStream.source is SourceStream sourceStream)
+            {
+                query = sourceStream.GetSource() as IQueryable;
+            }
+            else if (arg.combinedStream.source is CombinedStream baseStream)
+            {
+                query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
+            }
+
+            var entityType = query?.ElementType;
+
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToExecuteString(arg, entityType);
+        }
+
+    }
+
+}

+ 16 - 101
src/Vitorm.MongoDB/QueryExecutor/Sync/ToList.cs

@@ -3,10 +3,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 
 
-using MongoDB.Bson;
 using MongoDB.Driver;
 using MongoDB.Driver;
 
 
-using Vitorm.Entity;
 using Vitorm.StreamQuery;
 using Vitorm.StreamQuery;
 
 
 namespace Vitorm.MongoDB.QueryExecutor
 namespace Vitorm.MongoDB.QueryExecutor
@@ -17,133 +15,50 @@ namespace Vitorm.MongoDB.QueryExecutor
 
 
         public string methodName => nameof(Enumerable.ToList);
         public string methodName => nameof(Enumerable.ToList);
 
 
-        public object ExecuteQuery(QueryExecutorArgument execArg)
-        {
-            using var _ = execArg;
-
-            var resultEntityType = execArg.expression.Type.GetGenericArguments()?.FirstOrDefault();
-            return Execute(execArg, resultEntityType);
-        }
 
 
 
 
-        public static object Execute(QueryExecutorArgument execArg, Type resultEntityType)
+        public object ExecuteQuery(QueryExecutorArgument arg)
         {
         {
-            CombinedStream combinedStream = execArg.combinedStream;
-            var dbContext = execArg.dbContext;
-            var translateService = dbContext.translateService;
-
-            // #2 filter
-            var filter = translateService.TranslateQuery(execArg, combinedStream);
-
             IQueryable query = null;
             IQueryable query = null;
-
-            if (combinedStream.source is SourceStream sourceStream)
+            if (arg.combinedStream.source is SourceStream sourceStream)
             {
             {
                 query = sourceStream.GetSource() as IQueryable;
                 query = sourceStream.GetSource() as IQueryable;
             }
             }
-            else if (combinedStream.source is CombinedStream baseStream)
+            else if (arg.combinedStream.source is CombinedStream baseStream)
             {
             {
                 query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
                 query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
-
             }
             }
 
 
-            var queryEntityType = query?.ElementType;
-            var entityDescriptor = dbContext.GetEntityDescriptor(queryEntityType);
+            var entityType = query?.ElementType;
+            var resultEntityType = arg.expression.Type.GetGenericArguments().First();
 
 
-            // #3 execute query
-            var database = dbContext.dbConfig.GetDatabase();
-            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
-
-            // #4 read entity
-            object result;
+            return Execute_MethodInfo(entityType, resultEntityType).Invoke(null, new object[] { arg });
+        }
 
 
 
 
 
 
-            // Sort
-            BsonDocument sortDoc = null;
-            var fluent = collection.Find(filter);
-            if (combinedStream.orders?.Any() == true)
-            {
-                sortDoc = new BsonDocument();
-
-                combinedStream.orders.ForEach(item =>
-                {
-                    var fieldPath = translateService.GetFieldPath(execArg, item.member);
-                    sortDoc.Add(fieldPath, BsonValue.Create(item.asc ? 1 : -1));
-                });
-                fluent = fluent.Sort(sortDoc);
-            }
 
 
-            if (combinedStream.skip > 0) fluent = fluent.Skip(combinedStream.skip);
-            if (combinedStream.take > 0) fluent = fluent.Limit(combinedStream.take);
-
-            using var cursor = fluent.ToCursor();
-
-            // Event_OnExecuting
-            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
-                dbContext: dbContext,
-                executeString: filter.ToJson(),
-                extraParam: new()
-                {
-                    ["entityDescriptor"] = entityDescriptor,
-                    ["Method"] = "ToList",
-                    ["sortDoc"] = sortDoc,
-                    ["combinedStream"] = combinedStream,
-                }))
-            );
-
-            if (combinedStream.select?.resultSelector != null)
-            {
-                // Select
-                var lambdaExp = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+        private static MethodInfo Execute_MethodInfo_;
+        static MethodInfo Execute_MethodInfo(Type entityType, Type resultEntityType) =>
+            (Execute_MethodInfo_ ??= new Func<QueryExecutorArgument, List<string>>(Execute<object, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType);
 
 
-                var delSelect = lambdaExp.Compile();
 
 
-                result = Method_ReadListAndConvert.MakeGenericMethod(queryEntityType, resultEntityType)
-                    .Invoke(null, new object[] { dbContext, entityDescriptor, cursor, delSelect });
-            }
-            else
-            {
-                result = Method_ReadList.MakeGenericMethod(queryEntityType)
-                  .Invoke(null, new object[] { dbContext, entityDescriptor, cursor });
-            }
 
 
-            return result;
+        public static List<ResultEntity> Execute<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToList<Entity, ResultEntity>(arg);
         }
         }
 
 
 
 
-        static MethodInfo Method_ReadListAndConvert = new Func<DbContext, IEntityDescriptor, IAsyncCursor<BsonDocument>, Func<object, object>, List<object>>(ReadListAndConvert<object, object>)
-                    .Method.GetGenericMethodDefinition();
 
 
-        static List<ResultEntity> ReadListAndConvert<Entity, ResultEntity>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor, Func<Entity, ResultEntity> delSelect)
-        {
-            var entities = Read<Entity>(dbContext, entityDescriptor, cursor);
 
 
-            return entities.Select(delSelect).ToList();
-        }
 
 
 
 
 
 
-        static MethodInfo Method_ReadList = new Func<DbContext, IEntityDescriptor, IAsyncCursor<BsonDocument>, List<object>>(ReadList<object>)
-                  .Method.GetGenericMethodDefinition();
 
 
-        static List<Entity> ReadList<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor)
-        {
-            return Read<Entity>(dbContext, entityDescriptor, cursor).ToList();
-        }
-
-
-        static IEnumerable<Entity> Read<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor)
-        {
-            while (cursor.MoveNext())
-            {
-                IEnumerable<BsonDocument> batch = cursor.Current;
-                foreach (BsonDocument document in batch)
-                {
-                    yield return dbContext.Deserialize<Entity>(document, entityDescriptor);
-                }
-            }
-        }
     }
     }
 
 
 }
 }

+ 72 - 105
src/Vitorm.MongoDB/QueryExecutor/TranslateService.cs

@@ -2,115 +2,27 @@
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Text.RegularExpressions;
 
 
 using MongoDB.Bson;
 using MongoDB.Bson;
 
 
 using Vit.Linq.ExpressionNodes.ComponentModel;
 using Vit.Linq.ExpressionNodes.ComponentModel;
 
 
-using Vitorm.Entity.PropertyType;
-using Vitorm.StreamQuery;
-
 namespace Vitorm.MongoDB.QueryExecutor
 namespace Vitorm.MongoDB.QueryExecutor
 {
 {
     public class TranslateService
     public class TranslateService
     {
     {
-        public virtual BsonDocument TranslateQuery(QueryExecutorArgument arg, CombinedStream combinedStream)
+        public virtual BsonDocument TranslateFilter(QueryExecutorArgument arg)
         {
         {
-            if (combinedStream?.where == null) return new();
-
-            return EvalExpression(arg, combinedStream.where);
+            if (arg?.combinedStream?.where == null) return new();
+            return TranslateFilter(arg, arg.combinedStream.where);
         }
         }
 
 
 
 
-        public virtual string GetFieldPath(QueryExecutorArgument arg, ExpressionNode member, out IPropertyType propertyType)
-        {
-            switch (member?.nodeType)
-            {
-                case NodeType.Member:
-                    {
-                        if (member.objectValue == null)
-                        {
-                            // entity root
-                            var entityType = member.Member_GetType();
-                            var entityDescriptor = arg.dbContext.GetEntityDescriptor(entityType);
-                            propertyType = entityDescriptor.propertyType;
 
 
-                            if (string.IsNullOrWhiteSpace(member.memberName)) return null;
-                            break;
-                        }
-                        else
-                        {
-                            // nested field
-                            var parentPath = GetFieldPath(arg, member.objectValue, out var parentPropertyType);
-
-                            if (!string.IsNullOrWhiteSpace(member.memberName) && parentPropertyType is IPropertyObjectType parentObjectType)
-                            {
-                                var propertyDescriptor = parentObjectType.properties?.FirstOrDefault(property => property.propertyName == member.memberName);
-
-                                if (propertyDescriptor != null)
-                                {
-                                    propertyType = propertyDescriptor.propertyType;
-
-                                    var columnName = propertyDescriptor.columnName;
-
-                                    var fieldPath = columnName;
-                                    if (parentPath != null) fieldPath = parentPath + "." + fieldPath;
-                                    return fieldPath;
-                                }
-                            }
-                            break;
-                        }
-                    }
-                case NodeType.ArrayIndex:
-                    {
-                        ExpressionNode_ArrayIndex arrayIndex = member;
-
-                        var index = arrayIndex.right.value;
-                        var parentPath = GetFieldPath(arg, arrayIndex.left, out var parentPropertyType);
-
-                        if (parentPropertyType is IPropertyArrayType arrayType)
-                        {
-                            propertyType = arrayType.elementPropertyType;
-
-                            var filePath = $"{index}";
-                            if (parentPath != null) filePath = parentPath + "." + filePath;
-                            return filePath;
-                        }
-                        break;
-                    }
-
-                case NodeType.MethodCall:
-                    {
-                        ExpressionNode_MethodCall methodCall = member;
-
-                        switch (methodCall.methodName)
-                        {
-                            // ##1 List.get_Item
-                            case "get_Item" when methodCall.@object is not null && methodCall.arguments.Length == 1:
-                                {
-                                    var index = methodCall.arguments[0]?.value;
-                                    var parentPath = GetFieldPath(arg, methodCall.@object, out var parentPropertyType);
-
-                                    if (parentPropertyType is IPropertyArrayType arrayType)
-                                    {
-                                        propertyType = arrayType?.elementPropertyType;
-
-                                        var filePath = $"{index}";
-                                        if (parentPath != null) filePath = parentPath + "." + filePath;
-                                        return filePath;
-                                    }
-                                    break;
-                                }
-                        }
-                        break;
-                    }
-            }
-
-            throw new InvalidOperationException($"Can not get fieldPath from member:{member?.nodeType}");
-        }
         public virtual string GetFieldPath(QueryExecutorArgument arg, ExpressionNode member)
         public virtual string GetFieldPath(QueryExecutorArgument arg, ExpressionNode member)
         {
         {
-            return GetFieldPath(arg, member, out _);
+            return arg.GetFieldPath(member);
         }
         }
 
 
 
 
@@ -138,10 +50,10 @@ namespace Vitorm.MongoDB.QueryExecutor
 
 
                         if (TypeUtil.IsArrayType(type))
                         if (TypeUtil.IsArrayType(type))
                         {
                         {
-                            if (v is ICollection collection)
+                            if (v is IEnumerable enumerable)
                             {
                             {
                                 var array = new BsonArray();
                                 var array = new BsonArray();
-                                foreach (var item in collection)
+                                foreach (var item in enumerable)
                                 {
                                 {
                                     array.Add(BsonValue.Create(item));
                                     array.Add(BsonValue.Create(item));
                                 }
                                 }
@@ -211,24 +123,25 @@ namespace Vitorm.MongoDB.QueryExecutor
             return false;
             return false;
         }
         }
 
 
-        public virtual BsonDocument EvalExpression(QueryExecutorArgument arg, ExpressionNode node)
+        public virtual BsonDocument TranslateFilter(QueryExecutorArgument arg, ExpressionNode node)
         {
         {
             switch (node?.nodeType)
             switch (node?.nodeType)
             {
             {
                 case NodeType.AndAlso:
                 case NodeType.AndAlso:
                     {
                     {
                         ExpressionNode_Binary binary = node;
                         ExpressionNode_Binary binary = node;
-                        return new BsonDocument("$and", new BsonArray { EvalExpression(arg, binary.left), EvalExpression(arg, binary.right) });
+                        return new BsonDocument("$and", new BsonArray { TranslateFilter(arg, binary.left), TranslateFilter(arg, binary.right) });
                     }
                     }
                 case NodeType.OrElse:
                 case NodeType.OrElse:
                     {
                     {
                         ExpressionNode_Binary binary = node;
                         ExpressionNode_Binary binary = node;
-                        return new BsonDocument("$or", new BsonArray { EvalExpression(arg, binary.left), EvalExpression(arg, binary.right) });
+                        return new BsonDocument("$or", new BsonArray { TranslateFilter(arg, binary.left), TranslateFilter(arg, binary.right) });
                     }
                     }
                 case NodeType.Not:
                 case NodeType.Not:
                     {
                     {
                         ExpressionNode_Not not = node;
                         ExpressionNode_Not not = node;
-                        return new BsonDocument("$not", EvalExpression(arg, not.body));
+                        //return new BsonDocument("$not", EvalExpression(arg, not.body));
+                        return new BsonDocument("$nor", new BsonArray { TranslateFilter(arg, not.body) });
                     }
                     }
                 case NodeType.Equal:
                 case NodeType.Equal:
                 case NodeType.NotEqual:
                 case NodeType.NotEqual:
@@ -259,21 +172,74 @@ namespace Vitorm.MongoDB.QueryExecutor
                         {
                         {
                             NodeType.Equal => "$eq",
                             NodeType.Equal => "$eq",
                             NodeType.NotEqual => "$ne",
                             NodeType.NotEqual => "$ne",
-                            NodeType.LessThan => fieldInLeft ? "$lt" : "$gte",
-                            NodeType.LessThanOrEqual => fieldInLeft ? "$lte" : "$gt",
-                            NodeType.GreaterThan => fieldInLeft ? "$gt" : "$lte",
-                            NodeType.GreaterThanOrEqual => fieldInLeft ? "$gte" : "$lt",
+                            NodeType.LessThan => fieldInLeft ? "$lt" : "$gt",
+                            NodeType.LessThanOrEqual => fieldInLeft ? "$lte" : "$gte",
+                            NodeType.GreaterThan => fieldInLeft ? "$gt" : "$lt",
+                            NodeType.GreaterThanOrEqual => fieldInLeft ? "$gte" : "$lte",
                         };
                         };
 
 
                         return new BsonDocument(fieldPath, new BsonDocument(operate, value));
                         return new BsonDocument(fieldPath, new BsonDocument(operate, value));
                     }
                     }
+                case NodeType.Member:
+                    {
+                        var propertyType = node.Member_GetType();
+                        var fieldPath = GetFieldPath(arg, node);
+                        if (propertyType == typeof(bool) || propertyType == typeof(bool?))
+                            return new BsonDocument(fieldPath, new BsonDocument("$eq", BsonValue.Create(true)));
+                        break;
+                    }
                 case NodeType.MethodCall:
                 case NodeType.MethodCall:
                     {
                     {
                         ExpressionNode_MethodCall methodCall = node;
                         ExpressionNode_MethodCall methodCall = node;
 
 
                         switch (methodCall.methodName)
                         switch (methodCall.methodName)
                         {
                         {
-                            // ##1 in
+
+                            #region String method:  StartsWith EndsWith Contains
+                            case nameof(string.StartsWith): // String.StartsWith
+                                {
+                                    var member = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+
+                                    var fieldPath = GetFieldPath(arg, member);
+                                    TryReadValue(arg, value, out var bValue);
+                                    string strValue = bValue.AsString;
+                                    strValue = Regex.Escape(strValue);
+
+                                    var regex = $"^{strValue}";
+                                    return new BsonDocument(fieldPath, new BsonDocument("$regex", BsonValue.Create(regex)));
+                                }
+                            case nameof(string.EndsWith): // String.EndsWith
+                                {
+                                    var member = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+
+                                    var fieldPath = GetFieldPath(arg, member);
+                                    TryReadValue(arg, value, out var bValue);
+                                    string strValue = bValue.AsString;
+                                    strValue = Regex.Escape(strValue);
+
+                                    var regex = $"{strValue}$";
+                                    return new BsonDocument(fieldPath, new BsonDocument("$regex", BsonValue.Create(regex)));
+                                }
+                            case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
+                                {
+                                    var member = methodCall.@object;
+                                    var value = methodCall.arguments[0];
+
+                                    var fieldPath = GetFieldPath(arg, member);
+                                    TryReadValue(arg, value, out var bValue);
+                                    string strValue = bValue.AsString;
+                                    strValue = Regex.Escape(strValue);
+
+                                    var regex = $"{strValue}";
+                                    return new BsonDocument(fieldPath, new BsonDocument("$regex", BsonValue.Create(regex)));
+                                }
+                            #endregion
+
+
+
+                            // in
                             case nameof(List<string>.Contains) when methodCall.@object is not null && methodCall.arguments.Length == 1:
                             case nameof(List<string>.Contains) when methodCall.@object is not null && methodCall.arguments.Length == 1:
                                 {
                                 {
                                     var values = methodCall.@object;
                                     var values = methodCall.@object;
@@ -298,7 +264,8 @@ namespace Vitorm.MongoDB.QueryExecutor
                         throw new NotSupportedException("[TranslateService] not supported MethodCall: " + methodCall.methodName);
                         throw new NotSupportedException("[TranslateService] not supported MethodCall: " + methodCall.methodName);
                     }
                     }
             }
             }
-            throw new NotSupportedException("[TranslateService] not supported notType: " + node?.nodeType);
+
+            throw new NotSupportedException("[TranslateService] not supported nodeType: " + node?.nodeType);
         }
         }
 
 
     }
     }

+ 53 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.Count.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor
+    {
+        public int Count(QueryExecutorArgument arg, Type entityType)
+        {
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(entityType);
+
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+            var pipeline = GetPipeline(arg);
+            pipeline = pipeline.Concat(new[] { new BsonDocument("$count", "count") }).ToArray();
+
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "Count",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+
+            // Execute aggregation
+            using var cursor = dbContext.session == null ? collection.Aggregate<BsonDocument>(pipeline) : collection.Aggregate<BsonDocument>(dbContext.session, pipeline);
+
+            var doc = cursor.FirstOrDefault();
+
+            return doc?["count"].AsInt32 ?? 0;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 83 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.FirstOrDefault.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+using MongoDB.Bson;
+
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor
+    {
+        public ResultEntity FirstOrDefault<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            Type keyType;
+            var resultSelector = arg.combinedStream.select.resultSelector;
+            if (resultSelector == null)
+            {
+                keyType = typeof(ResultEntity);
+            }
+            else
+            {
+                var groupType = resultSelector.Lambda_GetParamTypes()[0];
+                keyType = groupType.GetGenericArguments()[0];
+            }
+
+            return (ResultEntity)FirstOrDefault_MethodInfo(typeof(Entity), typeof(ResultEntity), keyType).Invoke(null, new[] { arg });
+        }
+
+
+        private static MethodInfo FirstOrDefault_MethodInfo_;
+        static MethodInfo FirstOrDefault_MethodInfo(Type entityType, Type resultEntityType, Type keyType) =>
+            (FirstOrDefault_MethodInfo_ ??= new Func<QueryExecutorArgument, string>(FirstOrDefault<string, string, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType, keyType);
+
+
+        static ResultEntity FirstOrDefault<Entity, ResultEntity, Key>(QueryExecutorArgument arg)
+        {
+            var combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+            if (combinedStream.method.Contains("Last"))
+            {
+                if (combinedStream.skip.HasValue)
+                {
+                    combinedStream.skip = combinedStream.skip.Value + (combinedStream.take ?? 0) - 1;
+                }
+                else
+                {
+                    arg.ReverseOrder();
+                }
+            }
+            if (combinedStream.take != 0)
+                combinedStream.take = 1;
+
+
+            using var cursor = Execute<Entity, ResultEntity>(arg, entityDescriptor);
+
+            var nullable = combinedStream.method.Contains("OrDefault");
+            if (combinedStream.take == 0)
+            {
+                return nullable ? default : throw new InvalidOperationException("Sequence contains no elements");
+            }
+
+            BsonDocument document = nullable ? cursor?.FirstOrDefault() : cursor.First();
+            if (document == null) return default;
+
+            var firstGroup = new Grouping<Key, Entity>(dbContext, entityDescriptor, document);
+
+            var lambdaExpression = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+            var delSelect = (Func<IGrouping<Key, Entity>, ResultEntity>)lambdaExpression.Compile();
+
+            return delSelect(firstGroup);
+        }
+
+
+
+
+    }
+}

+ 33 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToExecuteString.cs

@@ -0,0 +1,33 @@
+using System;
+
+using MongoDB.Bson;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor
+    {
+        public string ToExecuteString(QueryExecutorArgument arg, Type entityType)
+        {
+            var dbContext = arg.dbContext;
+
+            var pipeline = GetPipeline(arg);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = dbContext.GetEntityDescriptor(entityType),
+                    ["Method"] = arg.combinedStream.method ?? "ToExecuteString",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+            return pipeline.ToJson();
+        }
+
+    }
+}

+ 96 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToList.cs

@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor
+    {
+
+        public List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            Type keyType;
+            var resultSelector = arg.combinedStream.select.resultSelector;
+            if (resultSelector == null)
+            {
+                keyType = typeof(ResultEntity);
+            }
+            else
+            {
+                var groupType = resultSelector.Lambda_GetParamTypes()[0];
+                keyType = groupType.GetGenericArguments()[0];
+            }
+
+            return (List<ResultEntity>)ToList_MethodInfo(typeof(Entity), typeof(ResultEntity), keyType).Invoke(null, new[] { arg });
+        }
+
+
+        private static MethodInfo ToList_MethodInfo_;
+        static MethodInfo ToList_MethodInfo(Type entityType, Type resultEntityType, Type keyType) =>
+            (ToList_MethodInfo_ ??= new Func<QueryExecutorArgument, List<string>>(ToList<string, string, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType, keyType);
+
+
+        static List<ResultEntity> ToList<Entity, ResultEntity, Key>(QueryExecutorArgument arg)
+        {
+            var combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+
+            using var cursor = Execute<Entity, ResultEntity>(arg, entityDescriptor);
+
+            var lambdaExpression = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+            var delSelect = (Func<IGrouping<Key, Entity>, ResultEntity>)lambdaExpression.Compile();
+
+            var groups = ReadGroups<Key, Entity>(dbContext, entityDescriptor, cursor);
+
+            return groups.Select(delSelect).ToList();
+
+        }
+        static IAsyncCursor<BsonDocument> Execute<Entity, ResultEntity>(QueryExecutorArgument arg, IEntityDescriptor entityDescriptor)
+        {
+            var dbContext = arg.dbContext;
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+            var pipeline = GetPipeline(arg);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "ToList",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+            if (arg.combinedStream.take == 0) return null;
+
+            // Execute aggregation
+            return dbContext.session == null ? collection.Aggregate<BsonDocument>(pipeline) : collection.Aggregate<BsonDocument>(dbContext.session, pipeline);
+        }
+        static IEnumerable<IGrouping<Key, Element>> ReadGroups<Key, Element>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor)
+        {
+            if (cursor == null) yield break;
+            while (cursor.MoveNext())
+            {
+                foreach (BsonDocument document in cursor.Current)
+                {
+                    yield return new Grouping<Key, Element>(dbContext, entityDescriptor, document);
+                }
+            }
+        }
+
+    }
+}

+ 108 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToListAsync.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor
+    {
+
+        public Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            Type keyType;
+            var resultSelector = arg.combinedStream.select.resultSelector;
+            if (resultSelector == null)
+            {
+                keyType = typeof(ResultEntity);
+            }
+            else
+            {
+                var groupType = resultSelector.Lambda_GetParamTypes()[0];
+                keyType = groupType.GetGenericArguments()[0];
+            }
+
+            return (Task<List<ResultEntity>>)ToListAsync_MethodInfo(typeof(Entity), typeof(ResultEntity), keyType).Invoke(null, new[] { arg });
+        }
+
+
+        private static MethodInfo ToListAsync_MethodInfo_;
+        static MethodInfo ToListAsync_MethodInfo(Type entityType, Type resultEntityType, Type keyType) =>
+            (ToListAsync_MethodInfo_ ??= new Func<QueryExecutorArgument, Task<List<string>>>(ToListAsync<string, string, string>).Method.GetGenericMethodDefinition())
+            .MakeGenericMethod(entityType, resultEntityType, keyType);
+
+
+        static async Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity, Key>(QueryExecutorArgument arg)
+        {
+            var combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+
+            using var cursor = await ExecuteAsync<Entity, ResultEntity>(arg, entityDescriptor);
+
+            var lambdaExpression = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+            var delSelect = (Func<IGrouping<Key, Entity>, ResultEntity>)lambdaExpression.Compile();
+
+            var list = await ReadListAsync<Entity, ResultEntity, Key>(dbContext, entityDescriptor, cursor, delSelect);
+
+            return list;
+
+        }
+        static Task<IAsyncCursor<BsonDocument>> ExecuteAsync<Entity, ResultEntity>(QueryExecutorArgument arg, IEntityDescriptor entityDescriptor)
+        {
+            var dbContext = arg.dbContext;
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+            var pipeline = GetPipeline(arg);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "ToListAsync",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+            if (arg.combinedStream.take == 0) return null;
+
+            // Execute aggregation
+            return dbContext.session == null ? collection.AggregateAsync<BsonDocument>(pipeline) : collection.AggregateAsync<BsonDocument>(dbContext.session, pipeline);
+        }
+
+        static async Task<List<ResultEntity>> ReadListAsync<Entity, ResultEntity, Key>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor, Func<IGrouping<Key, Entity>, ResultEntity> Select)
+        {
+            List<ResultEntity> list = new();
+            if (cursor == null) return list;
+
+            while (await cursor.MoveNextAsync())
+            {
+                foreach (BsonDocument document in cursor.Current)
+                {
+                    var group = new Grouping<Key, Entity>(dbContext, entityDescriptor, document);
+                    list.Add(Select(group));
+                }
+            }
+            return list;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 218 - 0
src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.cs

@@ -0,0 +1,218 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class GroupExecutor : ISearchExecutor
+    {
+        public virtual bool IsMatch(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+
+            var dbContext = arg.dbContext;
+
+            //if (combinedStream.source is not SourceStream) return false;
+            if (!combinedStream.isGroupedStream) return false;
+            if (combinedStream.joins?.Any() == true) return false;
+            //if (combinedStream.distinct != null) return false;
+            if (combinedStream.select?.resultSelector == null) return false;
+
+            return true;
+        }
+
+        public static BsonDocument[] GetPipeline(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var translateService = dbContext.translateService;
+
+            // #2 filter
+            var filter = combinedStream?.where == null ? null : translateService.TranslateFilter(arg, combinedStream.where);
+
+            // #3 groupByFields
+            List<(string field, string fieldAs)> groupFields = new();
+            BsonValue groupByFields;
+            var groupFieldArg = new QueryExecutorArgument_GroupFilter(arg, groupFields);
+
+            #region groupByFields
+            {
+                var node = combinedStream.groupByFields;
+
+                if (node?.nodeType == NodeType.New)
+                {
+                    ExpressionNode_New newNode = node;
+                    newNode.constructorArgs.ForEach(nodeArg =>
+                    {
+                        var fieldAs = nodeArg.name;
+                        var field = arg.GetFieldPath(nodeArg.value);
+                        groupFields.Add((field, fieldAs));
+                    });
+
+                    groupByFields = new BsonDocument(groupFields.ToDictionary(field => field.fieldAs, field => "$" + field.field));
+                }
+                else if (node?.nodeType == NodeType.Member)
+                {
+                    string fieldAs = null;
+                    var field = arg.GetFieldPath(node);
+                    groupFields.Add((field, fieldAs));
+
+                    groupByFields = "$" + field;
+                }
+                else
+                {
+                    throw new NotSupportedException("[GroupExecutor] groupByFields is not valid: NodeType must be New or Member");
+                }
+            }
+            #endregion
+
+            // #4 filter to groups
+            var groupFilter = combinedStream.having == null ? null : translateService.TranslateFilter(groupFieldArg, combinedStream.having);
+
+
+            // #5 order by fields
+            List<(string field, bool asc, int index)> orderFields = combinedStream.orders?.Select((orderField, index) =>
+            {
+                var field = groupFieldArg.GetFieldPath(orderField.member);
+                return (field, orderField.asc, index);
+            }).ToList();
+            var orderByFields = orderFields == null ? null : new BsonDocument(orderFields.ToDictionary(field => field.field, field => BsonValue.Create(field.asc ? 1 : -1)));
+
+            // Aggregation pipeline
+            var pipeline = new[]
+            {
+                    //new BsonDocument("$match", new BsonDocument("userId", new BsonDocument("$gt", 1))),
+                    filter == null ? null : new BsonDocument("$match", filter),
+
+                    new BsonDocument("$group", new BsonDocument
+                    {
+                        //{ "_id", new BsonDocument { { "userFatherId", "$userFatherId" }, { "userMotherId", "$userMotherId" } } },
+                        { "_id", groupByFields },
+                        { "count", new BsonDocument("$sum", 1) },
+                        { "items", new BsonDocument("$push", "$$ROOT") },
+                    }),
+
+                    //new BsonDocument("$match", new BsonDocument("count", new BsonDocument("$gte", 1))),
+                    groupFilter == null ? null : new BsonDocument("$match", groupFilter),
+
+                    //new BsonDocument("$sort", new BsonDocument("count", -1)),
+                     orderByFields == null ? null : new BsonDocument("$sort", orderByFields),
+
+                    new BsonDocument("$project", new BsonDocument
+                    {
+                        { "_id", 1 },
+                        { "items", 1 },
+                        { "count", 1 },
+                    }),
+
+                    //new BsonDocument("$skip", 1),
+                    combinedStream.skip>0 ? new BsonDocument("$skip", combinedStream.skip.Value) : null ,
+
+                    //new BsonDocument("$limit", 5),
+                    combinedStream.take>0 ? new BsonDocument("$limit", combinedStream.take.Value) : null ,
+
+            }.Where(m => m != null).ToArray();
+
+            return pipeline;
+        }
+
+
+        class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
+        {
+            class KeyWrap
+            {
+                public TKey Key { get; set; }
+            }
+            public Grouping(DbContext dbContext, IEntityDescriptor entityDescriptor, BsonDocument document)
+            {
+                // #1 read key
+                var docKey = document["_id"];
+                if (docKey.IsBsonDocument)
+                {
+                    Key = BsonSerializer.Deserialize<TKey>(docKey.AsBsonDocument);
+                }
+                else
+                {
+                    Key = BsonSerializer.Deserialize<KeyWrap>(new BsonDocument("Key", docKey)).Key;
+                }
+
+                // #2 read list
+                list = document["items"]?.AsBsonArray.AsQueryable()?.Select(item => dbContext.Deserialize<TElement>(item.AsBsonDocument, entityDescriptor));
+            }
+            public TKey Key { get; private set; }
+
+            IEnumerable<TElement> list;
+
+            public IEnumerator<TElement> GetEnumerator() => list.GetEnumerator();
+
+            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+        }
+
+
+        public class QueryExecutorArgument_GroupFilter : QueryExecutorArgument
+        {
+            List<(string field, string fieldAs)> groupFields;
+            public QueryExecutorArgument_GroupFilter(QueryExecutorArgument arg, List<(string field, string fieldAs)> groupFields)
+            {
+                this.combinedStream = arg.combinedStream;
+                this.dbContext = arg.dbContext;
+                this.expression = arg.expression;
+                this.expressionResultType = arg.expressionResultType;
+                this.groupFields = groupFields;
+            }
+
+            public override string GetFieldPath(ExpressionNode member)
+            {
+                switch (member?.nodeType)
+                {
+                    case NodeType.MethodCall:
+                        {
+                            ExpressionNode_MethodCall methodCall = member;
+
+                            switch (methodCall.methodName)
+                            {
+                                // ##1 Count
+                                case nameof(Enumerable.Count) when methodCall.@object is null && methodCall.arguments.Length == 1:
+                                    {
+                                        return "count";
+                                    }
+                            }
+                            break;
+                        }
+                }
+                var field = base.GetFieldPath(member);
+
+                var groupField = groupFields.FirstOrDefault(f => f.field == field);
+                if (groupField.field == field)
+                {
+                    var fieldAs = groupField.fieldAs;
+                    if (fieldAs == null) return "_id";
+                    return "_id." + fieldAs;
+                }
+
+                // $ROOT
+                groupField = groupFields.FirstOrDefault(f => f.field == "$ROOT");
+                if (groupField == default) throw new InvalidOperationException();
+
+                {
+                    var fieldAs = groupField.fieldAs;
+                    if (fieldAs == null) return "_id." + field;
+                    return "_id." + fieldAs + "." + field;
+                }
+            }
+        }
+
+
+    }
+}

+ 20 - 0
src/Vitorm.MongoDB/SearchExecutor/ISearchExecutor.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public interface ISearchExecutor
+    {
+        bool IsMatch(QueryExecutorArgument arg);
+        List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg);
+
+        Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg);
+        int Count(QueryExecutorArgument arg, Type entityType);
+        string ToExecuteString(QueryExecutorArgument arg, Type entityType);
+
+        ResultEntity FirstOrDefault<Entity, ResultEntity>(QueryExecutorArgument arg);
+    }
+}

+ 57 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.Count.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor
+    {
+        public int Count(QueryExecutorArgument arg, Type entityType)
+        {
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(entityType);
+
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+            var entityReader = new EntityReader.EntityReader();
+            entityReader.Init(dbContext, entityType, arg.combinedStream.select.fields);
+
+
+            var pipeline = GetPipeline(arg, entityReader);
+            pipeline = pipeline.Concat(new[] { new BsonDocument("$count", "count") }).ToArray();
+
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "Count",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+
+            // Execute aggregation
+            using var cursor = dbContext.session == null ? collection.Aggregate<BsonDocument>(pipeline) : collection.Aggregate<BsonDocument>(dbContext.session, pipeline);
+
+            var doc = cursor.FirstOrDefault();
+
+            return doc?["count"].AsInt32 ?? 0;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 72 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.FirstOrDefault.cs

@@ -0,0 +1,72 @@
+using System;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor
+    {
+        public ResultEntity FirstOrDefault<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+            if (combinedStream.method.Contains("Last"))
+            {
+                if (combinedStream.skip.HasValue)
+                {
+                    combinedStream.skip = combinedStream.skip.Value + (combinedStream.take ?? 0) - 1;
+                }
+                else
+                {
+                    arg.ReverseOrder();
+                }
+            }
+            if (combinedStream.take != 0)
+                combinedStream.take = 1;
+
+
+            var entityReader = new EntityReader.EntityReader();
+            entityReader.Init(dbContext, typeof(Entity), combinedStream.select.fields);
+
+
+            var pipeline = GetPipeline(arg, entityReader);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method,
+                    ["combinedStream"] = combinedStream,
+                }))
+            );
+
+            var nullable = combinedStream.method.Contains("OrDefault");
+            if (combinedStream.take == 0)
+            {
+                return nullable ? default : throw new InvalidOperationException("Sequence contains no elements");
+            }
+
+
+            var collection = dbContext.dbConfig.GetDatabase().GetCollection<BsonDocument>(entityDescriptor.tableName);
+            using var cursor = dbContext.session == null ? collection.Aggregate<BsonDocument>(pipeline) : collection.Aggregate<BsonDocument>(dbContext.session, pipeline);
+
+
+            // read entity
+            BsonDocument document = nullable ? cursor?.FirstOrDefault() : cursor.First();
+            if (document == null) return default;
+
+            var group = document?["_id"].AsBsonDocument;
+            return (ResultEntity)entityReader.ReadEntity(group);
+        }
+
+    }
+}

+ 36 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToExecuteString.cs

@@ -0,0 +1,36 @@
+using System;
+
+using MongoDB.Bson;
+
+using Vitorm.MongoDB.QueryExecutor;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor
+    {
+        public string ToExecuteString(QueryExecutorArgument arg, Type entityType)
+        {
+            var dbContext = arg.dbContext;
+
+            var entityReader = new EntityReader.EntityReader();
+            entityReader.Init(dbContext, entityType, arg.combinedStream.select.fields);
+
+            var pipeline = GetPipeline(arg, entityReader);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = dbContext.GetEntityDescriptor(entityType),
+                    ["Method"] = arg.combinedStream.method ?? "ToExecuteString",
+                    ["combinedStream"] = arg.combinedStream,
+                }))
+            );
+
+            return pipeline.ToJson();
+        }
+
+    }
+}

+ 66 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToList.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor
+    {
+
+        public List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            return ReadList<Entity, ResultEntity>(arg).ToList();
+        }
+
+        IEnumerable<ResultEntity> ReadList<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+
+            var entityReader = new EntityReader.EntityReader();
+            entityReader.Init(dbContext, typeof(Entity), combinedStream.select.fields);
+
+
+            var pipeline = GetPipeline(arg, entityReader);
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "ToList",
+                    ["combinedStream"] = combinedStream,
+                }))
+            );
+
+            if (arg.combinedStream.take == 0) yield break;
+
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+            using var cursor = dbContext.session == null ? collection.Aggregate<BsonDocument>(pipeline) : collection.Aggregate<BsonDocument>(dbContext.session, pipeline);
+
+            while (cursor.MoveNext())
+            {
+                foreach (BsonDocument document in cursor.Current)
+                {
+                    var group = document["_id"].AsBsonDocument;
+                    yield return (ResultEntity)entityReader.ReadEntity(group);
+                }
+            }
+        }
+
+
+
+    }
+}

+ 61 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToListAsync.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+using MongoDB.Bson;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor
+    {
+        public async Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+            var entityReader = new EntityReader.EntityReader();
+            entityReader.Init(dbContext, typeof(Entity), combinedStream.select.fields);
+
+            var pipeline = GetPipeline(arg, entityReader);
+
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "ToListAsync",
+                    ["combinedStream"] = combinedStream,
+                }))
+            );
+
+
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+            using var cursor = dbContext.session == null ? await collection.AggregateAsync<BsonDocument>(pipeline) : await collection.AggregateAsync<BsonDocument>(dbContext.session, pipeline);
+
+
+            var list = new List<ResultEntity>();
+            if (cursor != null)
+            {
+                while (await cursor.MoveNextAsync())
+                {
+                    foreach (BsonDocument document in cursor.Current)
+                    {
+                        var group = document["_id"].AsBsonDocument;
+                        list.Add((ResultEntity)entityReader.ReadEntity(group));
+                    }
+                }
+            }
+            return list;
+        }
+
+    }
+}

+ 92 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.cs

@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+using static Vitorm.MongoDB.SearchExecutor.GroupExecutor;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainDistinctExecutor : ISearchExecutor
+    {
+
+        public virtual bool IsMatch(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+
+            var dbContext = arg.dbContext;
+
+            if (combinedStream.source is not SourceStream) return false;
+            if (combinedStream.isGroupedStream) return false;
+            if (combinedStream.joins?.Any() == true) return false;
+            if (combinedStream.distinct != true) return false;
+            if (combinedStream.select == null) return false;
+
+            return true;
+        }
+
+
+
+        public static BsonDocument[] GetPipeline(QueryExecutorArgument arg, EntityReader.EntityReader entityReader)
+        {
+            // #1
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var translateService = dbContext.translateService;
+
+            // #2 filter
+            var filter = combinedStream?.where == null ? null : translateService.TranslateFilter(arg, combinedStream.where);
+
+            // #3 groupByFields
+            List<(string field, string fieldAs)> groupFields = entityReader.entityArgReaders.Select(f => (f.fieldPath, f.argName)).ToList();
+            var groupByFields = new BsonDocument(groupFields.ToDictionary(f => f.fieldAs, f => "$" + f.field));
+            var groupFieldArg = new QueryExecutorArgument_GroupFilter(arg, groupFields);
+
+            // #5 order by fields
+            List<(string field, bool asc, int index)> orderFields = combinedStream.orders?.Select((orderField, index) =>
+            {
+                var field = groupFieldArg.GetFieldPath(orderField.member);
+                return (field, orderField.asc, index);
+            }).ToList();
+            var orderByFields = orderFields == null ? null : new BsonDocument(orderFields.ToDictionary(field => field.field, field => BsonValue.Create(field.asc ? 1 : -1)));
+
+
+            // Aggregation pipeline
+            var pipeline = new[]
+            {
+                    //new BsonDocument("$match", new BsonDocument("userId", new BsonDocument("$gt", 1))),
+                    filter == null ? null : new BsonDocument("$match", filter),
+
+                    new BsonDocument("$group", new BsonDocument
+                    {
+                        //{ "_id", new BsonDocument { { "userFatherId", "$userFatherId" }, { "userMotherId", "$userMotherId" } } },
+                        { "_id", groupByFields },
+                    }),
+                     
+
+                    //new BsonDocument("$sort", new BsonDocument("count", -1)),
+                     orderByFields == null ? null : new BsonDocument("$sort", orderByFields),
+
+                    new BsonDocument("$project", new BsonDocument
+                    {
+                        { "_id", 1 },
+                    }),
+
+                    //new BsonDocument("$skip", 1),
+                    combinedStream.skip>0 ? new BsonDocument("$skip", combinedStream.skip.Value) : null ,
+
+                    //new BsonDocument("$limit", 5),
+                    combinedStream.take>0 ? new BsonDocument("$limit", combinedStream.take.Value) : null ,
+
+            }.Where(m => m != null).ToArray();
+
+            return pipeline;
+
+        }
+    }
+}

+ 51 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.Count.cs

@@ -0,0 +1,51 @@
+using System;
+
+using MongoDB.Bson;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor
+    {
+        public int Count(QueryExecutorArgument arg, Type entityType)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(entityType);
+            var translateService = dbContext.translateService;
+
+            // #2 filter
+            var filter = translateService.TranslateFilter(arg);
+
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: filter.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "Count",
+                    ["combinedStream"] = combinedStream,
+                }))
+            );
+
+
+            // #3 execute query
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+            var count = (int)collection.CountDocuments(filter);
+
+            return count;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 69 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.FirstOrDefault.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor
+    {
+        public ResultEntity FirstOrDefault<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+
+            if (combinedStream.method.Contains("Last"))
+            {
+                if (combinedStream.skip.HasValue)
+                {
+                    combinedStream.skip = combinedStream.skip.Value + (combinedStream.take ?? 0) - 1;
+                }
+                else
+                {
+                    arg.ReverseOrder();
+                }
+            }
+            if (combinedStream.take != 0)
+                combinedStream.take = 1;
+
+
+            var fluent = ExecuteQuery<Entity, ResultEntity>(arg, entityDescriptor);
+
+            var nullable = combinedStream.method.Contains("OrDefault");
+            if (combinedStream.take == 0)
+            {
+                return nullable ? default : throw new InvalidOperationException("Sequence contains no elements");
+            }
+
+
+            // read entity
+            BsonDocument document = nullable ? fluent?.FirstOrDefault() : fluent.First();
+
+            if (combinedStream.select?.resultSelector != null)
+            {
+                // Select
+                var lambdaExp = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+                var delSelect = (Func<Entity, ResultEntity>)lambdaExp.Compile();
+
+                var entity = dbContext.Deserialize<Entity>(document, entityDescriptor);
+                return entity == null ? default : delSelect(entity);
+            }
+            else
+            {
+                var entity = dbContext.Deserialize<ResultEntity>(document, entityDescriptor);
+                return entity;
+            }
+
+        }
+
+
+
+
+    }
+}

+ 74 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToExecuteString.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor
+    {
+        public string ToExecuteString(QueryExecutorArgument arg, Type entityType)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var translateService = dbContext.translateService;
+
+
+            // #2 filter
+            var filter = translateService.TranslateFilter(arg);
+
+
+            // #3 sortDoc
+            BsonDocument sortDoc = null;
+            if (combinedStream.orders?.Any() == true)
+            {
+                sortDoc = new BsonDocument();
+                combinedStream.orders.ForEach(item =>
+                {
+                    var fieldPath = translateService.GetFieldPath(arg, item.member);
+                    sortDoc.Add(fieldPath, BsonValue.Create(item.asc ? 1 : -1));
+                });
+            }
+
+
+            // pipeline
+            var pipeline = new[]
+            {
+                    //new BsonDocument("$match", new BsonDocument("userId", new BsonDocument("$gt", 1))),
+                    filter == null ? null : new BsonDocument("$match", filter),
+                     
+
+                    //new BsonDocument("$sort", new BsonDocument("count", -1)),
+                     sortDoc == null ? null : new BsonDocument("$sort", sortDoc),
+
+                    //new BsonDocument("$skip", 1),
+                    combinedStream.skip>0 ? new BsonDocument("$skip", combinedStream.skip.Value) : null ,
+
+                    //new BsonDocument("$limit", 5),
+                    combinedStream.take>0 ? new BsonDocument("$limit", combinedStream.take.Value) : null ,
+
+            }.Where(m => m != null).ToArray();
+
+
+            // Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: pipeline.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = dbContext.GetEntityDescriptor(entityType),
+                    ["Method"] = arg.combinedStream.method ?? "ToExecuteString",
+                    ["combinedStream"] = arg.combinedStream,
+                    ["sortDoc"] = sortDoc,
+                }))
+            );
+
+            return pipeline.ToJson();
+        }
+
+    }
+}

+ 63 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToList.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor
+    {
+        public List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+            var fluent = ExecuteQuery<Entity, ResultEntity>(arg, entityDescriptor);
+
+
+            List<ResultEntity> result;
+
+            using var cursor = fluent?.ToCursor();
+            if (combinedStream.select?.resultSelector != null)
+            {
+                // Select
+                var lambdaExp = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+                var delSelect = (Func<Entity, ResultEntity>)lambdaExp.Compile();
+
+                var entities = ReadList<Entity>(dbContext, entityDescriptor, cursor).Select(delSelect);
+
+                result = entities.ToList();
+            }
+            else
+            {
+                var entities = ReadList<ResultEntity>(dbContext, entityDescriptor, cursor);
+                result = entities.ToList();
+            }
+
+            return result;
+        }
+
+        public static IEnumerable<Entity> ReadList<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor)
+        {
+            if (cursor == null) yield break;
+
+            while (cursor.MoveNext())
+            {
+                IEnumerable<BsonDocument> batch = cursor.Current;
+                foreach (BsonDocument document in batch)
+                {
+                    yield return dbContext.Deserialize<Entity>(document, entityDescriptor);
+                }
+            }
+        }
+
+    }
+}

+ 67 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToListAsync.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor : ISearchExecutor
+    {
+
+        public async Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var entityDescriptor = dbContext.GetEntityDescriptor(typeof(Entity));
+
+            var fluent = ExecuteQuery<Entity, ResultEntity>(arg, entityDescriptor);
+
+
+            List<ResultEntity> result;
+
+            using var cursor = await fluent?.ToCursorAsync();
+            if (combinedStream.select?.resultSelector != null)
+            {
+                // Select
+                var lambdaExp = combinedStream.select.resultSelector.Lambda_GetLambdaExpression();
+
+                var delSelect = lambdaExp.Compile() as Func<Entity, ResultEntity>;
+
+                var entities = await ReadListAsync<Entity>(dbContext, entityDescriptor, cursor);
+                result = entities.Select(delSelect).ToList();
+            }
+            else
+            {
+                result = await ReadListAsync<ResultEntity>(dbContext, entityDescriptor, cursor);
+            }
+
+            return result;
+        }
+
+        static async Task<List<Entity>> ReadListAsync<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor, IAsyncCursor<BsonDocument> cursor)
+        {
+            var list = new List<Entity>();
+            if (cursor == null) return list;
+
+            while (await cursor.MoveNextAsync())
+            {
+                foreach (BsonDocument document in cursor.Current)
+                {
+                    var entity = dbContext.Deserialize<Entity>(document, entityDescriptor);
+                    list.Add(entity);
+                }
+            }
+            return list;
+        }
+
+
+    }
+}

+ 87 - 0
src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.cs

@@ -0,0 +1,87 @@
+using System;
+using System.Linq;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+
+namespace Vitorm.MongoDB.SearchExecutor
+{
+    public partial class PlainExecutor : ISearchExecutor
+    {
+        public virtual bool IsMatch(QueryExecutorArgument arg)
+        {
+            CombinedStream combinedStream = arg.combinedStream;
+
+            var dbContext = arg.dbContext;
+
+            if (combinedStream.source is not SourceStream) return false;
+            if (combinedStream.isGroupedStream) return false;
+            if (combinedStream.joins?.Any() == true) return false;
+            if (combinedStream.distinct != null) return false;
+
+            return true;
+        }
+
+
+
+
+        IFindFluent<BsonDocument, BsonDocument> ExecuteQuery<Entity, ResultEntity>(QueryExecutorArgument arg, IEntityDescriptor entityDescriptor)
+        {
+            // #1
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
+            var translateService = dbContext.translateService;
+
+
+            // #2 filter
+            var filter = translateService.TranslateFilter(arg);
+
+
+            // #3 sortDoc
+            BsonDocument sortDoc = null;
+            if (combinedStream.orders?.Any() == true)
+            {
+                sortDoc = new BsonDocument();
+                combinedStream.orders.ForEach(item =>
+                {
+                    var fieldPath = translateService.GetFieldPath(arg, item.member);
+                    sortDoc.Add(fieldPath, BsonValue.Create(item.asc ? 1 : -1));
+                });
+            }
+
+            // #4 Event_OnExecuting
+            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: dbContext,
+                executeString: filter.ToJson(),
+                extraParam: new()
+                {
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = arg.combinedStream.method ?? "ToList",
+                    ["combinedStream"] = combinedStream,
+                    ["sortDoc"] = sortDoc,
+                }))
+            );
+
+            if (combinedStream.take == 0) return null;
+
+            // #5 execute query
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+            var fluent = dbContext.session == null ? collection.Find(filter) : collection.Find(dbContext.session, filter);
+
+            // #6 execute query
+            if (sortDoc != null) fluent = fluent.Sort(sortDoc);
+
+            // #7 skip take
+            if (combinedStream.skip > 0) fluent = fluent.Skip(combinedStream.skip);
+            if (combinedStream.take > 0) fluent = fluent.Limit(combinedStream.take);
+
+            return fluent;
+        }
+    }
+}

+ 56 - 0
src/Vitorm.MongoDB/Transaction/Transaction.cs

@@ -0,0 +1,56 @@
+using MongoDB.Driver;
+
+using Vitorm.Transaction;
+
+namespace Vitorm.MongoDB.Transaction
+{
+    public partial class Transaction : ITransaction
+    {
+        public IClientSessionHandle session { get; protected set; }
+        public bool isActive { get; protected set; }
+        public Transaction(DbContext dbContext)
+        {
+            session = dbContext.dbConfig.Client.StartSession();
+            session.StartTransaction();
+            isActive = true;
+        }
+
+
+        public virtual void Commit()
+        {
+            if (!isActive) throw new System.InvalidOperationException("MongoDB Transaction is not started.");
+
+            session.CommitTransaction();
+            session.Dispose();
+            session = null;
+
+            isActive = false;
+
+        }
+        public virtual void Dispose()
+        {
+            if (session == null) return;
+
+            if (isActive)
+            {
+                session.AbortTransaction();
+                isActive = false;
+            }
+
+            session.Dispose();
+            session = null;
+        }
+
+        public virtual void Rollback()
+        {
+            if (!isActive) throw new System.InvalidOperationException("MongoDB Transaction is not started.");
+
+            session.AbortTransaction();
+            session.Dispose();
+            session = null;
+
+            isActive = false;
+        }
+    }
+
+}

+ 39 - 0
src/Vitorm.MongoDB/Transaction/TransactionManager.cs

@@ -0,0 +1,39 @@
+using MongoDB.Driver;
+
+using Vitorm.Transaction;
+
+namespace Vitorm.MongoDB.Transaction
+{
+    public class TransactionManager : ITransactionManager
+    {
+        public TransactionManager(DbContext dbContext)
+        {
+            this.dbContext = dbContext;
+        }
+
+        protected DbContext dbContext;
+        public Transaction transaction;
+
+        public virtual IClientSessionHandle session => transaction?.isActive == true ? transaction.session : null;
+
+        public virtual ITransaction BeginTransaction()
+        {
+            if (transaction?.isActive == true) throw new System.NotSupportedException("MongoDB do not support nested transaction.");
+
+            transaction = new Transaction(dbContext);
+            return transaction;
+        }
+
+        public virtual void Dispose()
+        {
+            if (transaction != null)
+            {
+                transaction.Dispose();
+                transaction = null;
+            }
+        }
+
+
+    }
+
+}

+ 2 - 3
src/Vitorm.MongoDB/Vitorm.MongoDB.csproj

@@ -6,7 +6,7 @@
     </PropertyGroup>
     </PropertyGroup>
 
 
     <PropertyGroup>
     <PropertyGroup>
-        <TargetFramework>netstandard2.0</TargetFramework>
+        <TargetFramework>netstandard2.1</TargetFramework>
         <LangVersion>9.0</LangVersion>
         <LangVersion>9.0</LangVersion>
     </PropertyGroup>
     </PropertyGroup>
 
 
@@ -32,9 +32,8 @@
     </ItemGroup>
     </ItemGroup>
 
 
     <ItemGroup>
     <ItemGroup>
-        <PackageReference Include="MongoDB.Driver" Version="2.29.0" />
+        <PackageReference Include="MongoDB.Driver" Version="3.1.0" />
         <PackageReference Include="Vitorm" Version="$(Vitorm_Version)" />
         <PackageReference Include="Vitorm" Version="$(Vitorm_Version)" />
     </ItemGroup>
     </ItemGroup>
 
 
-
 </Project>
 </Project>

+ 4 - 4
test/Vitorm.MongoDB.Data.MsTest/CommonTest/Common_Test.cs

@@ -21,9 +21,9 @@ namespace Vitorm.MsTest.CommonTest
             Test_Get();
             Test_Get();
             Test_Query();
             Test_Query();
             //Test_QueryJoin();
             //Test_QueryJoin();
-            //Test_ToExecuteString();
+            Test_ToExecuteString();
             //Test_ExecuteUpdate();
             //Test_ExecuteUpdate();
-            Test_ExecuteDelete();
+            //Test_ExecuteDelete();
             Test_Create();
             Test_Create();
             Test_Update();
             Test_Update();
             Test_Delete();
             Test_Delete();
@@ -37,8 +37,8 @@ namespace Vitorm.MsTest.CommonTest
             await Test_GetAsync();
             await Test_GetAsync();
             await Test_QueryAsync();
             await Test_QueryAsync();
             //await Test_QueryJoinAsync();
             //await Test_QueryJoinAsync();
-            await Test_ExecuteUpdateAsync();
-            await Test_ExecuteDeleteAsync();
+            //await Test_ExecuteUpdateAsync();
+            //await Test_ExecuteDeleteAsync();
             await Test_UpdateAsync();
             await Test_UpdateAsync();
             await Test_DeleteAsync();
             await Test_DeleteAsync();
         }
         }

+ 4 - 4
test/Vitorm.MongoDB.Data.MsTest/Vitorm.MongoDB.Data.MsTest.csproj

@@ -6,7 +6,7 @@
     </PropertyGroup>
     </PropertyGroup>
 
 
     <PropertyGroup>
     <PropertyGroup>
-        <TargetFramework>net6.0</TargetFramework>
+        <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <ImplicitUsings>enable</ImplicitUsings>
 
 
         <IsPackable>false</IsPackable>
         <IsPackable>false</IsPackable>
@@ -15,9 +15,9 @@
     </PropertyGroup>
     </PropertyGroup>
 
 
     <ItemGroup>
     <ItemGroup>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
-        <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
-        <PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.7.0" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.7.0" />
 
 
         <PackageReference Include="Vit.Core" Version="2.3.0" />
         <PackageReference Include="Vit.Core" Version="2.3.0" />
         <PackageReference Include="Vitorm.Data" Version="$(Vitorm_Version)" />
         <PackageReference Include="Vitorm.Data" Version="$(Vitorm_Version)" />

+ 1 - 1
test/Vitorm.MongoDB.Data.MsTest/appsettings.json

@@ -4,7 +4,7 @@
       {
       {
         "provider": "MongoDB",
         "provider": "MongoDB",
         "namespace": "Vitorm.MsTest",
         "namespace": "Vitorm.MsTest",
-        "connectionString": "mongodb://mongoadmin:mongoadminsecret@localhost:27017",
+        "connectionString": "mongodb://localhost:27017",
         "database": "db_orm"
         "database": "db_orm"
       }
       }
     ]
     ]

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

@@ -162,7 +162,6 @@ namespace Vitorm.MsTest.CommonTest
                             );
                             );
                     }).Where(column => column != null);
                     }).Where(column => column != null);
 
 
-
                 var propertyType = new PropertyObjectType(entityType);
                 var propertyType = new PropertyObjectType(entityType);
                 propertyType.properties = propertyDescriptors.Select(m => (IPropertyDescriptor)m).ToArray();
                 propertyType.properties = propertyDescriptors.Select(m => (IPropertyDescriptor)m).ToArray();
 
 

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

@@ -1,54 +0,0 @@
-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);
-            }
-        }
-    }
-}

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

@@ -1,54 +0,0 @@
-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);
-            }
-        }
-    }
-}

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

@@ -1,78 +0,0 @@
-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);
-            }
-        }
-    }
-}

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

@@ -1,80 +0,0 @@
-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);
-            }
-        }
-    }
-}

+ 3 - 2
test/Vitorm.MongoDB.MsTest/CommonTest/Property_Bool_Test.cs

@@ -60,8 +60,9 @@ namespace Vitorm.MsTest.CommonTest
                 Assert.AreEqual(2, user.id);
                 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 users = dbSet.Query().Where(m => !m.isEven.Value).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', users.Select(m => m.id));
+                Assert.AreEqual("1,3", ids);
             }
             }
             {
             {
                 var query = dbSet.Query().Where(m => m.isEven.Value).OrderBy(m => m.isEven).Select(m => new { m.id, m.isEven });
                 var query = dbSet.Query().Where(m => m.isEven.Value).OrderBy(m => m.isEven).Select(m => new { m.id, m.isEven });

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

@@ -1,37 +0,0 @@
-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);
-            }
-        }
-
-
-    }
-}

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

@@ -1,39 +0,0 @@
-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);
-            }
-
-        }
-
-
-    }
-}

+ 12 - 12
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Count_Test.cs

@@ -91,14 +91,14 @@ namespace Vitorm.MsTest.CommonTest
 
 
 
 
                 // joinedTable
                 // 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.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) => 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);
+                //Test(users.SelectMany(user => users.Where(father => father.id == user.fatherId), (user, father) => new { user, father }), config);
 
 
 
 
 
 
@@ -147,14 +147,14 @@ namespace Vitorm.MsTest.CommonTest
 
 
 
 
                 // joinedTable
                 // 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.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) => 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);
+                //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
                 // order by alias column

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

@@ -1,40 +0,0 @@
-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);
-            }
-
-        }
-
-    }
-}

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

@@ -0,0 +1,56 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+
+namespace Vitorm.MsTest.CommonTest
+{
+    [TestClass]
+    public class Query_Group_FirstOrDefault_Test
+    {
+
+        [TestMethod]
+        public void Test_Group_FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // Linq Expression
+            {
+                var query =
+                        from user in userQuery
+                        where user.fatherId != null
+                        group user by new { user.fatherId, user.motherId } into userGroup
+                        orderby userGroup.Key.fatherId, userGroup.Key.motherId
+                        select new { userGroup.Key.fatherId, userGroup.Key.motherId };
+
+                var row = query.FirstOrDefault();
+
+                Assert.AreEqual(4, row?.fatherId);
+                Assert.AreEqual(6, row?.motherId);
+            }
+
+            // Lambda Expression
+            {
+                var query =
+                        userQuery.Where(m => m.fatherId != null)
+                        .GroupBy(user => new { user.fatherId, user.motherId })
+                        .OrderBy(userGroup => userGroup.Key.fatherId).ThenBy(userGroup => userGroup.Key.motherId)
+                        .Select(userGroup => new
+                        {
+                            userGroup.Key.fatherId,
+                            userGroup.Key.motherId
+                        })
+                        ;
+
+                var row = query.FirstOrDefault();
+
+                Assert.AreEqual(4, row?.fatherId);
+                Assert.AreEqual(6, row?.motherId);
+            }
+        }
+
+
+
+    }
+}

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

@@ -1,210 +0,0 @@
-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 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 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 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 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 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 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 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 userList = query.ToList();
-
-                Assert.AreEqual(1, userList.Count);
-                Assert.AreEqual(2, userList.First().user.id);
-            }
-
-        }
-
-
-
-
-    }
-}

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

@@ -1,187 +0,0 @@
-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 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 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 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 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 userList = query.ToList();
-
-                Assert.AreEqual(2, userList.Count);
-                Assert.AreEqual(4, userList.First().father?.id);
-                Assert.AreEqual(5, userList.Last().father?.id);
-            }
-        }
-
-
-    }
-}

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

@@ -1,105 +0,0 @@
-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 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 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 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);
-            }
-        }
-
-
-    }
-}

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

@@ -1,186 +0,0 @@
-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 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 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);
-            }
-        }
-
-
-    }
-}

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

@@ -1,251 +0,0 @@
-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);
-            }
-        }
-
-
-
-
-    }
-}

+ 1 - 1
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Method_Test.cs

@@ -200,7 +200,7 @@ namespace Vitorm.MsTest.CommonTest
         }
         }
 
 
 
 
-        [TestMethod]
+        //[TestMethod]
         public void Test_ToListAndTotal()
         public void Test_ToListAndTotal()
         {
         {
             using var dbContext = DataSource.CreateDbContext();
             using var dbContext = DataSource.CreateDbContext();

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

@@ -1,86 +0,0 @@
-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 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 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 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);
-            }
-        }
-
-
-
-
-    }
-}

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

@@ -1,79 +0,0 @@
-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);
-            }
-        }
-
-
-
-    }
-}

+ 4 - 46
test/Vitorm.MongoDB.MsTest/CommonTest/Transaction_Test.cs

@@ -48,8 +48,7 @@ namespace Vitorm.MsTest.CommonTest
         }
         }
 
 
 
 
-        // can not test for db is not durable
-        //[TestMethod]
+        [TestMethod]
         public void Test_Dispose()
         public void Test_Dispose()
         {
         {
             {
             {
@@ -72,58 +71,17 @@ namespace Vitorm.MsTest.CommonTest
                 }
                 }
                 Assert.AreEqual("u4003", userSet.Get(4).name);
                 Assert.AreEqual("u4003", userSet.Get(4).name);
             }
             }
+
             {
             {
-                using var dbContext = DataSource.CreateDbContext();
+                using var dbContext = DataSource.CreateDbContext(autoInit: false);
                 var userSet = dbContext.DbSet<User>();
                 var userSet = dbContext.DbSet<User>();
 
 
-                //Assert.AreEqual("u4002", userSet.Get(4).name);
+                Assert.AreEqual("u4002", userSet.Get(4).name);
             }
             }
 
 
         }
         }
 
 
-        [TestMethod]
-        public void Test_NestedTransaction()
-        {
-            #region NestedTransaction
-            {
-                using var dbContext = DataSource.CreateDbContext();
-                var userSet = dbContext.DbSet<User>();
 
 
-                Assert.AreEqual("u400", userSet.Get(4).name);
-
-                using (var tran1 = dbContext.BeginTransaction())
-                {
-                    dbContext.Update(new User { id = 4, name = "u4001" });
-                    Assert.AreEqual("u4001", userSet.Get(4).name);
-
-                    using (var tran2 = dbContext.BeginTransaction())
-                    {
-                        dbContext.Update(new User { id = 4, name = "u4002" });
-                        Assert.AreEqual("u4002", userSet.Get(4).name);
-                    }
-                    Assert.AreEqual("u4001", userSet.Get(4).name);
-
-                    using (var tran2 = dbContext.BeginTransaction())
-                    {
-                        dbContext.Update(new User { id = 4, name = "u4002" });
-                        Assert.AreEqual("u4002", userSet.Get(4).name);
-                        tran2.Rollback();
-                    }
-                    Assert.AreEqual("u4001", userSet.Get(4).name);
-
-                    using (var tran2 = dbContext.BeginTransaction())
-                    {
-                        dbContext.Update(new User { id = 4, name = "u4003" });
-                        Assert.AreEqual("u4003", userSet.Get(4).name);
-                        tran2.Commit();
-                    }
-                    Assert.AreEqual("u4003", userSet.Get(4).name);
-                }
-
-                Assert.AreEqual("u400", userSet.Get(4).name);
-            }
-            #endregion
-        }
 
 
 
 
 
 

+ 0 - 1
test/Vitorm.MongoDB.MsTest/CustomTest/Linq_Test.cs

@@ -24,7 +24,6 @@ namespace Vitorm.MsTest.CustomTest
             public List<float?> scoreList { get; set; }
             public List<float?> scoreList { get; set; }
             public List<int> scoreRankList { get; set; }
             public List<int> scoreRankList { get; set; }
 
 
-            public User2 father { get; set; }
             public User2[] parents { get; set; }
             public User2[] parents { get; set; }
             public List<User2> parentList { get; set; }
             public List<User2> parentList { get; set; }
 
 

+ 140 - 0
test/Vitorm.MongoDB.MsTest/CustomTest/Primitive_Test.cs

@@ -81,6 +81,146 @@ namespace Vitorm.MsTest.CustomTest
 
 
 
 
 
 
+        [TestMethod]
+        public async Task GroupTest()
+        {
+            using var dbContext = DataSource.CreateDbContext(autoInit: true);
+
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>("User");
+
+            // group
+            {
+                /* -- MySql
+                    select userFatherId,userMotherId ,count(*) as count from `User` 
+                    where userId >1
+                    group by userFatherId ,userMotherId 
+                    having count(*)>=1
+                    order by count(*);
+                */
+                // Aggregation pipeline
+                var pipeline = new[]
+                {
+                    new BsonDocument("$match", new BsonDocument("userId", new BsonDocument("$gt", 1))),
+                    new BsonDocument("$group", new BsonDocument
+                    {
+                        { "_id", new BsonDocument { { "userFatherId", "$userFatherId" }, { "userMotherId", "$userMotherId" } } },
+                        { "count", new BsonDocument("$sum", 1) }
+                    }),
+                    new BsonDocument("$match", new BsonDocument("count", new BsonDocument("$gte", 1))),
+                    new BsonDocument("$sort", new BsonDocument("count", -1)),
+                    new BsonDocument("$project", new BsonDocument
+                    {
+                        { "userFatherId", "$_id.userFatherId" },
+                        { "userMotherId", "$_id.userMotherId" },
+                        { "count", 1 },
+                        { "_id", 0 }
+                    })
+                };
+
+
+                // Execute the aggregation
+                using var results = await collection.AggregateAsync<BsonDocument>(pipeline);
+                var json = results.ToList()?.ToJson();
+            }
+
+        }
+
+
+
+
+
+        [TestMethod]
+        public async Task TransactionTest()
+        {
+            using var dbContext = DataSource.CreateDbContext(autoInit: false);
+
+            var database = dbContext.dbConfig.GetDatabase();
+
+            // #1 Create table
+            if (database.ListCollectionNames().ToList().Contains("User2", StringComparer.OrdinalIgnoreCase))
+                await database.DropCollectionAsync("User2");
+
+            await database.CreateCollectionAsync("User2");
+            var collection = database.GetCollection<BsonDocument>("User2");
+
+            // transaction
+            {
+                {
+                    using var session = dbContext.dbConfig.Client.StartSession();
+                    session.StartTransaction();
+                    try
+                    {
+                        var user = new BsonDocument
+                        {
+                            ["userId"] = BsonValue.Create(1),
+                        };
+
+                        await collection.InsertOneAsync(session, user);
+
+                        // Commits our transaction
+                        await session.CommitTransactionAsync();
+                    }
+                    catch (Exception e)
+                    {
+                        await session.AbortTransactionAsync();
+                    }
+                }
+
+                // assert
+                var list = collection.AsQueryable().ToList();
+                Assert.AreEqual(1, list.Count);
+            }
+
+            // transaction
+            {
+                {
+                    using var session = dbContext.dbConfig.Client.StartSession();
+                    session.StartTransaction();
+
+                    var user = new BsonDocument
+                    {
+                        ["userId"] = BsonValue.Create(2),
+                    };
+
+                    await collection.InsertOneAsync(session, user);
+                    await session.AbortTransactionAsync();
+                }
+
+                // assert
+                var list = collection.AsQueryable().ToList();
+                Assert.AreEqual(1, list.Count);
+            }
+
+
+
+            // transaction
+            {
+                {
+                    using var session = dbContext.dbConfig.Client.StartSession();
+                    session.StartTransaction();
+
+                    var user = new BsonDocument
+                    {
+                        ["userId"] = BsonValue.Create(2),
+                    };
+
+                    await collection.InsertOneAsync(session, user);
+                    //await session.AbortTransactionAsync();
+                }
+
+                // assert
+                var list = collection.AsQueryable().ToList();
+                Assert.AreEqual(1, list.Count);
+            }
+
+
+
+        }
+
+
+
+
 
 
 
 
     }
     }

+ 179 - 0
test/Vitorm.MongoDB.MsTest/CustomTest/Query_Method_Test.cs

@@ -0,0 +1,179 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CustomTest
+{
+
+    [TestClass]
+    public class Query_Method_Test
+    {
+
+        [TestMethod]
+        public async Task ToListAsync()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var list = await userQuery.OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).ToListAsync();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",,,4,4,5", ids);
+            }
+
+            // Group
+            {
+                var list = await userQuery.GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).ToListAsync();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",4,5", ids);
+            }
+
+            // PlainDistinctSearch
+            {
+                var list = await userQuery.Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().ToListAsync();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",4,5", ids);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void ToList()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var list = userQuery.OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",,,4,4,5", ids);
+            }
+
+            // Group
+            {
+                var list = userQuery.GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).ToList();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",4,5", ids);
+            }
+
+            // PlainDistinctSearch
+            {
+                var list = userQuery.Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().ToList();
+                var ids = String.Join(',', list.Select(m => m.fatherId));
+                Assert.AreEqual(",4,5", ids);
+            }
+        }
+
+
+        [TestMethod]
+        public void Count()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var count = userQuery.OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).Count();
+                Assert.AreEqual(6, count);
+            }
+
+            // Group
+            {
+                var count = userQuery.GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).Count();
+                Assert.AreEqual(3, count);
+            }
+
+            // PlainDistinctSearch
+            {
+                var count = userQuery.Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().Count();
+                Assert.AreEqual(3, count);
+            }
+        }
+
+
+        [TestMethod]
+        public void ToExecuteString()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var executeString = userQuery.OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).ToExecuteString();
+                Assert.IsNotNull(executeString);
+            }
+
+            // Group
+            {
+                var executeString = userQuery.GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).ToExecuteString();
+                Assert.IsNotNull(executeString);
+            }
+
+            // PlainDistinctSearch
+            {
+                var executeString = userQuery.Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().ToExecuteString();
+                Assert.IsNotNull(executeString);
+            }
+        }
+
+
+
+        [TestMethod]
+        public void FirstOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var item = userQuery.Where(m => m.fatherId != null).OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).FirstOrDefault();
+                Assert.AreEqual(4, item.fatherId);
+            }
+
+            // Group
+            {
+                var item = userQuery.Where(m => m.fatherId != null).GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).FirstOrDefault();
+                Assert.AreEqual(4, item.fatherId);
+            }
+
+            // PlainDistinctSearch
+            {
+                var item = userQuery.Where(m => m.fatherId != null).Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().FirstOrDefault();
+                Assert.AreEqual(4, item.fatherId);
+            }
+        }
+
+
+        [TestMethod]
+        public void LastOrDefault()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            // PlainQuery
+            {
+                var item = userQuery.Where(m => m.fatherId != null).OrderBy(m => m.fatherId).Select(m => new { fatherId = m.fatherId }).LastOrDefault();
+                Assert.AreEqual(5, item.fatherId);
+            }
+
+            // Group
+            {
+                var item = userQuery.Where(m => m.fatherId != null).GroupBy(m => m.fatherId).OrderBy(g => g.Key).Select(g => new { fatherId = g.Key }).LastOrDefault();
+                Assert.AreEqual(5, item.fatherId);
+            }
+
+            // PlainDistinctSearch
+            {
+                var item = userQuery.Where(m => m.fatherId != null).Select(m => new { fatherId = m.fatherId }).OrderBy(m => m.fatherId).Distinct().LastOrDefault();
+                Assert.AreEqual(5, item.fatherId);
+            }
+        }
+
+
+    }
+}

+ 13 - 2
test/Vitorm.MongoDB.MsTest/DataSource.cs

@@ -29,15 +29,20 @@ namespace Vitorm.MsTest
 
 
         //[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
         //[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
         public DateTime? birth { get; set; }
         public DateTime? birth { get; set; }
-
+        [System.ComponentModel.DataAnnotations.Schema.Column("userFatherId")]
         public int? fatherId { get; set; }
         public int? fatherId { get; set; }
 
 
-
+        [System.ComponentModel.DataAnnotations.Schema.Column("userMotherId")]
         public int? motherId { get; set; }
         public int? motherId { get; set; }
 
 
+        [System.ComponentModel.DataAnnotations.Schema.Column("userClassId")]
         // [BsonIgnoreIfNull]
         // [BsonIgnoreIfNull]
         public int? classId { get; set; }
         public int? classId { get; set; }
 
 
+        [System.ComponentModel.DataAnnotations.Schema.Column("userFather")]
+        public User father { get; set; }
+
+
         [System.ComponentModel.DataAnnotations.Schema.NotMapped]
         [System.ComponentModel.DataAnnotations.Schema.NotMapped]
         [BsonIgnore]
         [BsonIgnore]
         public string test { get; set; }
         public string test { get; set; }
@@ -105,12 +110,18 @@ namespace Vitorm.MsTest
                     new User { id=5, name="u500" },
                     new User { id=5, name="u500" },
                     new User { id=6, name="u600" },
                     new User { id=6, name="u600" },
                 };
                 };
+
                 users.ForEach(user =>
                 users.ForEach(user =>
                 {
                 {
                     user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id);
                     user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id);
                     user.classId = user.id % 2 + 1;
                     user.classId = user.id % 2 + 1;
                 });
                 });
 
 
+                users.ForEach(user =>
+                {
+                    user.father = users.FirstOrDefault(m => m.id == user.fatherId);
+                });
+
                 dbContext.AddRange(users);
                 dbContext.AddRange(users);
             }
             }
             #endregion
             #endregion

+ 5 - 5
test/Vitorm.MongoDB.MsTest/Vitorm.MongoDB.MsTest.csproj

@@ -5,7 +5,7 @@
     </PropertyGroup>
     </PropertyGroup>
 
 
     <PropertyGroup>
     <PropertyGroup>
-        <TargetFramework>net6.0</TargetFramework>
+        <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <ImplicitUsings>enable</ImplicitUsings>
 
 
         <IsPackable>false</IsPackable>
         <IsPackable>false</IsPackable>
@@ -14,10 +14,10 @@
     </PropertyGroup>
     </PropertyGroup>
 
 
     <ItemGroup>
     <ItemGroup>
-        <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
-        <PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
-        <PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
+        <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="3.7.0" />
+        <PackageReference Include="MSTest.TestFramework" Version="3.7.0" />
 
 
         <PackageReference Include="Vit.Core" Version="2.3.0" />
         <PackageReference Include="Vit.Core" Version="2.3.0" />
     </ItemGroup>
     </ItemGroup>

+ 1 - 1
test/Vitorm.MongoDB.MsTest/appsettings.json

@@ -1,7 +1,7 @@
 {
 {
   "Vitorm": {
   "Vitorm": {
     "MongoDB": {
     "MongoDB": {
-      "connectionString": "mongodb://mongoadmin:mongoadminsecret@localhost:27017",
+      "connectionString": "mongodb://localhost:27017",
       "database": "db_orm"
       "database": "db_orm"
     }
     }
   }
   }