Bläddra i källkod

support Count

Lith 4 månader sedan
förälder
incheckning
64d903248f
30 ändrade filer med 831 tillägg och 534 borttagningar
  1. 3 0
      doc/ReleaseNotes.md
  2. 32 42
      src/Vitorm.MongoDB/DbContext.cs
  3. 6 3
      src/Vitorm.MongoDB/EntityReader/EntityReader.cs
  4. 37 0
      src/Vitorm.MongoDB/EntityReader/ModelReader.cs
  5. 3 1
      src/Vitorm.MongoDB/QueryExecutor/Async/ToListAsync.cs
  6. 111 9
      src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.cs
  7. 11 73
      src/Vitorm.MongoDB/QueryExecutor/Sync/Count.cs
  8. 8 42
      src/Vitorm.MongoDB/QueryExecutor/Sync/ToExecuteString.cs
  9. 3 1
      src/Vitorm.MongoDB/QueryExecutor/Sync/ToList.cs
  10. 1 1
      src/Vitorm.MongoDB/QueryExecutor/TranslateService.cs
  11. 53 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.Count.cs
  12. 33 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToExecuteString.cs
  13. 3 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToList.cs
  14. 4 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.ToListAsync.cs
  15. 220 0
      src/Vitorm.MongoDB/SearchExecutor/GroupExecutor.cs
  16. 4 4
      src/Vitorm.MongoDB/SearchExecutor/ISearchExecutor.cs
  17. 57 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.Count.cs
  18. 36 0
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToExecuteString.cs
  19. 3 2
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToList.cs
  20. 9 6
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToListAsync.cs
  21. 3 2
      src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.cs
  22. 51 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.Count.cs
  23. 74 0
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToExecuteString.cs
  24. 6 3
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToList.cs
  25. 4 2
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToListAsync.cs
  26. 4 1
      src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.cs
  27. 0 251
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_MethodAsync_Test.cs
  28. 0 12
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_Method_Test.cs
  29. 0 79
      test/Vitorm.MongoDB.MsTest/CommonTest/Query_ToListAndTotalCount_Test.cs
  30. 52 0
      test/Vitorm.MongoDB.MsTest/CustomTest/Query_Method_Test.cs

+ 3 - 0
doc/ReleaseNotes.md

@@ -5,3 +5,6 @@
 - support Transaction
 - upgrade net to 8.0
 - support group query
+- support PlainDistinctSearch
+- support Count
+

+ 32 - 42
src/Vitorm.MongoDB/DbContext.cs

@@ -85,9 +85,9 @@ namespace Vitorm.MongoDB
 
         #region SearchExecutor
         public static List<ISearchExecutor> defaultSearchExecutor = new() {
-            new PlainSearchExecutor(),
+            new PlainExecutor(),
             new GroupExecutor(),
-            new PlainDistinctSearchExecutor(),
+            new PlainDistinctExecutor(),
         };
         public List<ISearchExecutor> searchExecutor = defaultSearchExecutor;
 
@@ -102,25 +102,10 @@ namespace Vitorm.MongoDB
 
         public virtual BsonDocument Serialize(object entity, IEntityDescriptor entityDescriptor)
         {
-            return SerializeObject(entity, entityDescriptor.propertyType) as BsonDocument;
+            return Serialize(entity, entityDescriptor.propertyType) as BsonDocument;
         }
 
-        protected virtual BsonValue SerializeObject(object entity, IPropertyObjectType objectType)
-        {
-            if (entity == null) return BsonValue.Create(null);
-
-            var doc = new BsonDocument();
-
-            objectType.properties.ForEach(propertyDescriptor =>
-            {
-                var value = propertyDescriptor.GetValue(entity);
-                doc.Set(propertyDescriptor.columnName, SerializeProperty(value, propertyDescriptor.propertyType));
-            });
-
-            return doc;
-        }
-
-        protected virtual BsonValue SerializeProperty(object value, IPropertyType propertyType)
+        public virtual BsonValue Serialize(object value, IPropertyType propertyType)
         {
             switch (propertyType)
             {
@@ -131,15 +116,23 @@ namespace Vitorm.MongoDB
                         var bsonArray = new BsonArray();
                         foreach (var item in enumerable)
                         {
-                            bsonArray.Add(SerializeProperty(item, arrayType.elementPropertyType));
+                            bsonArray.Add(Serialize(item, arrayType.elementPropertyType));
                         }
                         return bsonArray;
                     }
                 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:
                     {
@@ -155,31 +148,16 @@ namespace Vitorm.MongoDB
 
         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)
         {
-            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)
             {
@@ -188,7 +166,7 @@ namespace Vitorm.MongoDB
                         if (bsonValue?.BsonType != BsonType.Array) return null;
 
                         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);
                     }
                 case IPropertyObjectType objectType:
@@ -196,7 +174,19 @@ namespace Vitorm.MongoDB
                         if (bsonValue?.BsonType != BsonType.Document) return null;
 
                         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:
                     {

+ 6 - 3
src/Vitorm.MongoDB/EntityReader/EntityReader.cs

@@ -8,6 +8,9 @@ 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>
@@ -72,7 +75,7 @@ namespace Vitorm.MongoDB.EntityReader
 
         protected string GetArgument(DbContext dbContext, ExpressionNode_Member member)
         {
-            var fieldPath = dbContext.translateService.GetFieldPath(new() { dbContext = dbContext }, (ExpressionNode)member);
+            var fieldPath = QueryExecutorArgument.GetFieldPath(dbContext, (ExpressionNode)member, out Type t);
 
             IArgReader argReader = entityArgReaders.FirstOrDefault(reader => reader.fieldPath == fieldPath);
 
@@ -91,9 +94,9 @@ namespace Vitorm.MongoDB.EntityReader
                 else
                 {
                     // Entity arg
-                    //var entityDescriptor = config.queryTranslateArgument.dbContext.GetEntityDescriptor(argType);
+                    var fieldPath2 = QueryExecutorArgument.GetFieldPath(dbContext, (ExpressionNode)member, out IPropertyType propertyType);
 
-                    //argReader = new ModelReader(config.sqlColumns, config.sqlTranslateService, tableName, argUniqueKey, argName, entityDescriptor);
+                    argReader = new ModelReader(dbContext, propertyType, argType, fieldPath ?? "$ROOT", argName);
                 }
                 entityArgReaders.Add(argReader);
             }

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

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

+ 3 - 1
src/Vitorm.MongoDB/QueryExecutor/Async/ToListAsync.cs

@@ -50,7 +50,9 @@ namespace Vitorm.MongoDB.QueryExecutor
 
         public static Task<List<ResultEntity>> Execute<Entity, ResultEntity>(QueryExecutorArgument arg)
         {
-            return arg.dbContext.GetSearchExecutor(arg)?.ToListAsync<Entity, ResultEntity>(arg);
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToListAsync<Entity, ResultEntity>(arg);
         }
 
 

+ 111 - 9
src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.cs

@@ -4,13 +4,11 @@ using System.Linq.Expressions;
 
 using Vit.Linq.ExpressionNodes.ComponentModel;
 
+using Vitorm.Entity.PropertyType;
 using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.QueryExecutor
 {
-
-
-
     public class QueryExecutorArgument : IDisposable
     {
         public CombinedStream combinedStream;
@@ -25,9 +23,9 @@ namespace Vitorm.MongoDB.QueryExecutor
         {
             dispose?.Invoke();
         }
-        public virtual string GetFieldPath(ExpressionNode member) => GetFieldPath(this, member, out var propertyType);
+        public virtual string GetFieldPath(ExpressionNode member) => GetFieldPath(this.dbContext, member, out Type propertyType);
 
-        public static string GetFieldPath(QueryExecutorArgument arg, ExpressionNode member, out Type propertyType)
+        public static string GetFieldPath(DbContext dbContext, ExpressionNode member, out Type propertyType)
         {
             switch (member?.nodeType)
             {
@@ -36,7 +34,7 @@ namespace Vitorm.MongoDB.QueryExecutor
                         if (member.objectValue != null)
                         {
                             // nested field
-                            var parentPath = GetFieldPath(arg, member.objectValue, out var parentPropertyType);
+                            var parentPath = GetFieldPath(dbContext, member.objectValue, out Type parentPropertyType);
 
                             var memberType = member.objectValue.Member_GetType();
                             // bool?.Value
@@ -49,7 +47,7 @@ namespace Vitorm.MongoDB.QueryExecutor
 
                             if (!string.IsNullOrWhiteSpace(member.memberName))
                             {
-                                var parentEntityDescriptor = arg.dbContext.GetEntityDescriptor(parentPropertyType);
+                                var parentEntityDescriptor = dbContext.GetEntityDescriptor(parentPropertyType);
                                 var propertyDescriptor = parentEntityDescriptor?.properties?.FirstOrDefault(property => property.propertyName == member.memberName);
 
                                 if (propertyDescriptor != null)
@@ -78,7 +76,7 @@ namespace Vitorm.MongoDB.QueryExecutor
                         ExpressionNode_ArrayIndex arrayIndex = member;
 
                         var index = arrayIndex.right.value;
-                        var parentPath = GetFieldPath(arg, arrayIndex.left, out var parentPropertyType);
+                        var parentPath = GetFieldPath(dbContext, arrayIndex.left, out Type parentPropertyType);
 
                         var elementType = TypeUtil.GetElementTypeFromArray(parentPropertyType);
                         if (elementType != null)
@@ -102,7 +100,7 @@ namespace Vitorm.MongoDB.QueryExecutor
                             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);
+                                    var parentPath = GetFieldPath(dbContext, methodCall.@object, out Type parentPropertyType);
 
                                     var elementType = TypeUtil.GetElementTypeFromArray(parentPropertyType);
                                     if (elementType != null)
@@ -128,6 +126,110 @@ namespace Vitorm.MongoDB.QueryExecutor
 
             throw new InvalidOperationException($"Can not get fieldPath from member:{member?.nodeType}");
         }
+
+
+
+        public static string GetFieldPath(DbContext dbContext, ExpressionNode member, out IPropertyType propertyType)
+        {
+            switch (member?.nodeType)
+            {
+                case NodeType.Member:
+                    {
+                        if (member.objectValue != null)
+                        {
+                            // nested field
+                            var parentPath = GetFieldPath(dbContext, member.objectValue, out IPropertyType parentPropertyType);
+
+                            var memberType = member.objectValue.Member_GetType();
+                            // bool?.Value
+                            if (member.memberName == nameof(Nullable<bool>.Value) && TypeUtil.IsNullable(memberType))
+                            {
+                                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}");
+        }
     }
 
 }

+ 11 - 73
src/Vitorm.MongoDB/QueryExecutor/Sync/Count.cs

@@ -1,11 +1,6 @@
 using System;
 using System.Linq;
 
-using MongoDB.Bson;
-using MongoDB.Driver;
-
-using Vitorm.Entity;
-using Vitorm.MongoDB.SearchExecutor;
 using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.QueryExecutor
@@ -19,12 +14,12 @@ namespace Vitorm.MongoDB.QueryExecutor
 
         public string methodName => nameof(Queryable.Count);
 
-        public object ExecuteQuery(QueryExecutorArgument execArg)
+        public object ExecuteQuery(QueryExecutorArgument arg)
         {
-            using var _ = execArg;
+            using var _ = arg;
 
-            CombinedStream combinedStream = execArg.combinedStream;
-            var dbContext = execArg.dbContext;
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
             IQueryable query = null;
             if (combinedStream.source is SourceStream sourceStream)
             {
@@ -35,45 +30,15 @@ namespace Vitorm.MongoDB.QueryExecutor
                 query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
             }
 
-            var queryEntityType = query?.ElementType;
-            var entityDescriptor = dbContext.GetEntityDescriptor(queryEntityType);
-
-
-            if (execArg.combinedStream.isGroupedStream) return ExecuteGroup(execArg, entityDescriptor);
-
-            return ExecutePlain(execArg, entityDescriptor);
-        }
-
-
-        public static int ExecutePlain(QueryExecutorArgument execArg, IEntityDescriptor entityDescriptor)
-        {
-            CombinedStream combinedStream = execArg.combinedStream;
-            var dbContext = execArg.dbContext;
-            var translateService = dbContext.translateService;
-
-            // #2 filter
-            var filter = translateService.TranslateFilter(execArg, combinedStream);
-
+            var skipAndTake = (combinedStream.skip, combinedStream.take);
+            (combinedStream.skip, combinedStream.take) = (null, null);
 
+            var entityType = query?.ElementType;
 
-            // Event_OnExecuting
-            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
-                dbContext: dbContext,
-                executeString: filter.ToJson(),
-                extraParam: new()
-                {
-                    ["entityDescriptor"] = entityDescriptor,
-                    ["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);
-
+            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))
@@ -88,35 +53,8 @@ namespace Vitorm.MongoDB.QueryExecutor
         }
 
 
-        public static int ExecuteGroup(QueryExecutorArgument execArg, IEntityDescriptor entityDescriptor)
-        {
-            var dbContext = execArg.dbContext;
-            var database = dbContext.dbConfig.GetDatabase();
-            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
-
-            var pipeline = GroupExecutor.GetPipeline(execArg);
-            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"] = "Count",
-                    ["combinedStream"] = execArg.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;
-        }
     }
 }

+ 8 - 42
src/Vitorm.MongoDB/QueryExecutor/Sync/ToExecuteString.cs

@@ -1,9 +1,6 @@
 using System;
 using System.Linq;
 
-using MongoDB.Bson;
-using MongoDB.Driver;
-
 using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.QueryExecutor
@@ -14,13 +11,12 @@ namespace Vitorm.MongoDB.QueryExecutor
 
         public string methodName => nameof(Orm_Extensions.ToExecuteString);
 
-        public object ExecuteQuery(QueryExecutorArgument execArg)
+        public object ExecuteQuery(QueryExecutorArgument arg)
         {
-            using var _ = execArg;
+            using var _ = arg;
 
-            // #1
-            CombinedStream combinedStream = execArg.combinedStream;
-            var dbContext = execArg.dbContext;
+            CombinedStream combinedStream = arg.combinedStream;
+            var dbContext = arg.dbContext;
             var translateService = dbContext.translateService;
 
             IQueryable query = null;
@@ -33,41 +29,11 @@ namespace Vitorm.MongoDB.QueryExecutor
                 query = (baseStream.source as SourceStream)?.GetSource() as IQueryable;
             }
 
-            var queryEntityType = query?.ElementType;
-            var entityDescriptor = dbContext.GetEntityDescriptor(queryEntityType);
-
-
-            // #2 filter
-            var filter = translateService.TranslateFilter(execArg, combinedStream);
-
-            // #3 sortDoc
-            BsonDocument sortDoc = null;
-            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));
-                });
-            }
-
-
-            // #4 Event_OnExecuting
-            dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
-                dbContext: dbContext,
-                executeString: filter.ToJson(),
-                extraParam: new()
-                {
-                    ["entityDescriptor"] = entityDescriptor,
-                    ["Method"] = "ToExecuteString",
-                    ["sortDoc"] = sortDoc,
-                    ["combinedStream"] = combinedStream,
-                }))
-            );
-
+            var entityType = query?.ElementType;
 
-            return filter.ToJson();
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToExecuteString(arg, entityType);
         }
 
     }

+ 3 - 1
src/Vitorm.MongoDB/QueryExecutor/Sync/ToList.cs

@@ -47,7 +47,9 @@ namespace Vitorm.MongoDB.QueryExecutor
 
         public static List<ResultEntity> Execute<Entity, ResultEntity>(QueryExecutorArgument arg)
         {
-            return arg.dbContext.GetSearchExecutor(arg)?.ToList<Entity, ResultEntity>(arg);
+            var executor = arg.dbContext.GetSearchExecutor(arg);
+            if (executor == null) throw new NotImplementedException();
+            return executor.ToList<Entity, ResultEntity>(arg);
         }
 
 

+ 1 - 1
src/Vitorm.MongoDB/QueryExecutor/TranslateService.cs

@@ -268,7 +268,7 @@ namespace Vitorm.MongoDB.QueryExecutor
                     }
             }
 
-            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;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 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();
+        }
+
+    }
+}

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

@@ -75,11 +75,14 @@ namespace Vitorm.MongoDB.SearchExecutor
                 }))
             );
 
+            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)

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

@@ -76,6 +76,8 @@ namespace Vitorm.MongoDB.SearchExecutor
                 }))
             );
 
+            if (arg.combinedStream.take == 0) return null;
+
             // Execute aggregation
             return dbContext.session == null ? collection.AggregateAsync<BsonDocument>(pipeline) : collection.AggregateAsync<BsonDocument>(dbContext.session, pipeline);
         }
@@ -83,6 +85,8 @@ namespace Vitorm.MongoDB.SearchExecutor
         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)

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

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

+ 4 - 4
src/Vitorm.MongoDB/SearchExecutor/ISearchExecutor.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 
 using Vitorm.MongoDB.QueryExecutor;
@@ -11,8 +12,7 @@ namespace Vitorm.MongoDB.SearchExecutor
         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);
     }
 }

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

+ 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();
+        }
+
+    }
+}

+ 3 - 2
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctSearchExecutor.ToList.cs → src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToList.cs

@@ -11,7 +11,7 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainDistinctSearchExecutor
+    public partial class PlainDistinctExecutor
     {
 
         public List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg)
@@ -30,7 +30,7 @@ namespace Vitorm.MongoDB.SearchExecutor
             entityReader.Init(dbContext, typeof(Entity), combinedStream.select.fields);
 
 
-            var pipeline = GetPipeline<Entity, ResultEntity>(arg, entityReader);
+            var pipeline = GetPipeline(arg, entityReader);
 
             // Event_OnExecuting
             dbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
@@ -44,6 +44,7 @@ namespace Vitorm.MongoDB.SearchExecutor
                 }))
             );
 
+            if (arg.combinedStream.take == 0) yield break;
 
             var database = dbContext.dbConfig.GetDatabase();
             var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);

+ 9 - 6
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctSearchExecutor.ToListAsync.cs → src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.ToListAsync.cs

@@ -10,7 +10,7 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainDistinctSearchExecutor
+    public partial class PlainDistinctExecutor
     {
         public async Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg)
         {
@@ -21,7 +21,7 @@ namespace Vitorm.MongoDB.SearchExecutor
             var entityReader = new EntityReader.EntityReader();
             entityReader.Init(dbContext, typeof(Entity), combinedStream.select.fields);
 
-            var pipeline = GetPipeline<Entity, ResultEntity>(arg, entityReader);
+            var pipeline = GetPipeline(arg, entityReader);
 
 
             // Event_OnExecuting
@@ -43,12 +43,15 @@ namespace Vitorm.MongoDB.SearchExecutor
 
 
             var list = new List<ResultEntity>();
-            while (await cursor.MoveNextAsync())
+            if (cursor != null)
             {
-                foreach (BsonDocument document in cursor.Current)
+                while (await cursor.MoveNextAsync())
                 {
-                    var group = document["_id"].AsBsonDocument;
-                    list.Add((ResultEntity)entityReader.ReadEntity(group));
+                    foreach (BsonDocument document in cursor.Current)
+                    {
+                        var group = document["_id"].AsBsonDocument;
+                        list.Add((ResultEntity)entityReader.ReadEntity(group));
+                    }
                 }
             }
             return list;

+ 3 - 2
src/Vitorm.MongoDB/SearchExecutor/PlainDistinctSearchExecutor.cs → src/Vitorm.MongoDB/SearchExecutor/PlainDistinctExecutor.cs

@@ -12,8 +12,9 @@ using static Vitorm.MongoDB.SearchExecutor.GroupExecutor;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainDistinctSearchExecutor : ISearchExecutor
+    public partial class PlainDistinctExecutor : ISearchExecutor
     {
+
         public virtual bool IsMatch(QueryExecutorArgument arg)
         {
             CombinedStream combinedStream = arg.combinedStream;
@@ -31,7 +32,7 @@ namespace Vitorm.MongoDB.SearchExecutor
 
 
 
-        public static BsonDocument[] GetPipeline<Entity, ResultEntity>(QueryExecutorArgument arg, EntityReader.EntityReader entityReader)
+        public static BsonDocument[] GetPipeline(QueryExecutorArgument arg, EntityReader.EntityReader entityReader)
         {
             // #1
             CombinedStream combinedStream = arg.combinedStream;

+ 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, combinedStream);
+
+
+            // 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;
+        }
+
+
+
+
+
+
+
+    }
+}

+ 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, combinedStream);
+
+
+            // #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();
+        }
+
+    }
+}

+ 6 - 3
src/Vitorm.MongoDB/SearchExecutor/PlainSearchExecutor.ToList.cs → src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToList.cs

@@ -12,7 +12,7 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainSearchExecutor : ISearchExecutor
+    public partial class PlainExecutor : ISearchExecutor
     {
 
         public List<ResultEntity> ToList<Entity, ResultEntity>(QueryExecutorArgument arg)
@@ -26,7 +26,7 @@ namespace Vitorm.MongoDB.SearchExecutor
 
             List<ResultEntity> result;
 
-            using var cursor = fluent.ToCursor();
+            using var cursor = fluent?.ToCursor();
             if (combinedStream.select?.resultSelector != null)
             {
                 // Select
@@ -41,7 +41,8 @@ namespace Vitorm.MongoDB.SearchExecutor
             }
             else
             {
-                result = ReadList<ResultEntity>(dbContext, entityDescriptor, cursor).ToList();
+                var entities = ReadList<ResultEntity>(dbContext, entityDescriptor, cursor);
+                result = entities.ToList();
             }
 
             return result;
@@ -49,6 +50,8 @@ namespace Vitorm.MongoDB.SearchExecutor
 
         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;

+ 4 - 2
src/Vitorm.MongoDB/SearchExecutor/PlainSearchExecutor.ToListAsync.cs → src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.ToListAsync.cs

@@ -13,7 +13,7 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainSearchExecutor : ISearchExecutor
+    public partial class PlainExecutor : ISearchExecutor
     {
 
         public async Task<List<ResultEntity>> ToListAsync<Entity, ResultEntity>(QueryExecutorArgument arg)
@@ -27,7 +27,7 @@ namespace Vitorm.MongoDB.SearchExecutor
 
             List<ResultEntity> result;
 
-            using var cursor = await fluent.ToCursorAsync();
+            using var cursor = await fluent?.ToCursorAsync();
             if (combinedStream.select?.resultSelector != null)
             {
                 // Select
@@ -49,6 +49,8 @@ namespace Vitorm.MongoDB.SearchExecutor
         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)

+ 4 - 1
src/Vitorm.MongoDB/SearchExecutor/PlainSearchExecutor.cs → src/Vitorm.MongoDB/SearchExecutor/PlainExecutor.cs

@@ -11,7 +11,7 @@ using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB.SearchExecutor
 {
-    public partial class PlainSearchExecutor : ISearchExecutor
+    public partial class PlainExecutor : ISearchExecutor
     {
         public virtual bool IsMatch(QueryExecutorArgument arg)
         {
@@ -37,6 +37,8 @@ namespace Vitorm.MongoDB.SearchExecutor
             var dbContext = arg.dbContext;
             var translateService = dbContext.translateService;
 
+
+
             // #2 filter
             var filter = translateService.TranslateFilter(arg, combinedStream);
 
@@ -66,6 +68,7 @@ namespace Vitorm.MongoDB.SearchExecutor
                 }))
             );
 
+            if (combinedStream.take == 0) return null;
 
             // #5 execute query
             var database = dbContext.dbConfig.GetDatabase();

+ 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);
-            }
-        }
-
-
-
-
-    }
-}

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

@@ -200,19 +200,7 @@ namespace Vitorm.MsTest.CommonTest
         }
 
 
-        [TestMethod]
-        public void Test_ToListAndTotal()
-        {
-            using var dbContext = DataSource.CreateDbContext();
 
-            // ToListAndTotalCount
-            {
-                var query = dbContext.Query<User>().Where(user => user.id > 2).Skip(1).Take(2);
-                var (list, totalCount) = query.ToListAndTotalCount();
-                Assert.AreEqual(2, list.Count);
-                Assert.AreEqual(4, totalCount);
-            }
-        }
 
 
 

+ 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);
-            }
-        }
-
-
-
-    }
-}

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

@@ -70,6 +70,58 @@ namespace Vitorm.MsTest.CustomTest
         }
 
 
+        [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);
+            }
+        }
+
+
 
     }
 }