Ver código fonte

Merge pull request #7 from LithWang/main

implement query
Lith 5 meses atrás
pai
commit
2ed73ea8c0

+ 1 - 0
doc/TODO.md

@@ -1,2 +1,3 @@
 # Vitorm.MongoDB TODO
 
+https://www.mongodb.com/zh-cn/docs/manual/reference/operator/query/

+ 2 - 2
src/Versions.props

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

+ 3 - 1
src/Vitorm.MongoDB/DataProvider.cs

@@ -1,4 +1,6 @@
-namespace Vitorm.MongoDB
+using System.Collections.Generic;
+
+namespace Vitorm.MongoDB
 {
     public partial class DataProvider : Vitorm.DataProvider.DataProvider
     {

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

@@ -54,5 +54,12 @@ namespace Vitorm.MongoDB
 
 
         internal string dbHashCode => connectionString.GetHashCode().ToString();
+
+
+
+        /// <summary>
+        /// to identify whether contexts are from the same database
+        /// </summary>
+        public virtual string dbGroupName => "DbSet_" + dbHashCode;
     }
 }

+ 138 - 1
src/Vitorm.MongoDB/DbContext.cs

@@ -1,4 +1,16 @@
-using System.Data;
+using System;
+using System.Collections;
+using System.Data;
+using System.Linq;
+
+using MongoDB.Bson;
+
+using Vit.Linq;
+
+using Vitorm.Entity;
+using Vitorm.Entity.PropertyType;
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
 
 namespace Vitorm.MongoDB
 {
@@ -27,5 +39,130 @@ namespace Vitorm.MongoDB
         public virtual string databaseName => throw new System.NotImplementedException();
         public virtual void ChangeDatabase(string databaseName) => throw new System.NotImplementedException();
 
+
+        #region StreamReader
+        public static StreamReader defaultStreamReader = new StreamReader();
+        public StreamReader streamReader = defaultStreamReader;
+        #endregion
+
+        #region StreamReader
+        public static TranslateService defaultTranslateService = new TranslateService();
+        public TranslateService translateService = defaultTranslateService;
+        #endregion
+
+        #region Serialize
+
+        public virtual BsonDocument Serialize(object entity, IEntityDescriptor entityDescriptor)
+        {
+            return SerializeObject(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)
+        {
+            switch (propertyType)
+            {
+                case IPropertyArrayType arrayType:
+                    {
+                        if (value is not IEnumerable enumerable) break;
+
+                        var bsonArray = new BsonArray();
+                        foreach (var item in enumerable)
+                        {
+                            bsonArray.Add(SerializeProperty(item, arrayType.elementPropertyType));
+                        }
+                        return bsonArray;
+                    }
+                case IPropertyObjectType objectType:
+                    {
+                        if (value == null) break;
+
+                        return SerializeObject(value, objectType);
+                    }
+                case IPropertyValueType valueType:
+                    {
+                        return BsonValue.Create(value);
+                    }
+            }
+            return BsonValue.Create(null);
+        }
+
+        #endregion
+
+
+
+        #region Deserialize
+
+        public virtual Entity Deserialize<Entity>(BsonDocument doc, IEntityDescriptor entityDescriptor)
+        {
+            return (Entity)Deserialize(doc, entityDescriptor);
+        }
+
+        public virtual object Deserialize(BsonDocument doc, IEntityDescriptor entityDescriptor)
+        {
+            return DeserializeObject(doc, entityDescriptor.entityType, entityDescriptor.properties);
+        }
+
+        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)
+        {
+            switch (propertyType)
+            {
+                case IPropertyArrayType arrayType:
+                    {
+                        if (bsonValue?.BsonType != BsonType.Array) return null;
+
+                        var bsonArray = bsonValue.AsBsonArray;
+                        var elements = bsonArray.Select(m => DeserializeProperty(m, arrayType.elementPropertyType));
+                        return arrayType.CreateArray(elements);
+                    }
+                case IPropertyObjectType objectType:
+                    {
+                        if (bsonValue?.BsonType != BsonType.Document) return null;
+
+                        var bsonDoc = bsonValue.AsBsonDocument;
+                        return DeserializeObject(bsonDoc, objectType.type, objectType.properties);
+                    }
+                case IPropertyValueType valueType:
+                    {
+                        var value = BsonTypeMapper.MapToDotNetValue(bsonValue);
+                        value = TypeUtil.ConvertToType(value, valueType.type);
+                        return value;
+                    }
+            }
+            return null;
+        }
+
+        #endregion
+
     }
 }

+ 160 - 0
src/Vitorm.MongoDB/DbSet.Query.cs

@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+using Vit.Linq;
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.MongoDB.QueryExecutor;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB
+{
+    public partial class DbSet<Entity> : IDbSet<Entity>
+    {
+
+        public virtual IQueryable<Entity> Query()
+        {
+            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
+
+        public static Dictionary<string, IQueryExecutor> defaultQueryExecutors = CreateDefaultQueryExecutors();
+        public static Dictionary<string, IQueryExecutor> CreateDefaultQueryExecutors()
+        {
+            Dictionary<string, IQueryExecutor> defaultQueryExecutors = new();
+
+            #region AddDefaultQueryExecutor
+            void AddDefaultQueryExecutor(IQueryExecutor queryExecutor, string methodName = null)
+            {
+                defaultQueryExecutors[methodName ?? queryExecutor.methodName] = queryExecutor;
+            }
+            #endregion
+
+
+            #region Sync
+            //// Orm_Extensions
+            //AddDefaultQueryExecutor(ExecuteUpdate.Instance);
+            //AddDefaultQueryExecutor(ExecuteDelete.Instance);
+            //AddDefaultQueryExecutor(ToExecuteString.Instance);
+
+            // ToList
+            AddDefaultQueryExecutor(ToList.Instance);
+            //// Count TotalCount
+            //AddDefaultQueryExecutor(Count.Instance);
+            //AddDefaultQueryExecutor(Count.Instance, methodName: nameof(Queryable_Extensions.TotalCount));
+
+            //// ToListAndTotalCount
+            //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));
+            #endregion
+
+
+            #region Async
+            //// Orm_Extensions
+            //AddDefaultQueryExecutor(ExecuteUpdateAsync.Instance);
+            //AddDefaultQueryExecutor(ExecuteDeleteAsync.Instance);
+
+            //// ToList
+            //AddDefaultQueryExecutor(ToListAsync.Instance);
+            //// Count TotalCount
+            //AddDefaultQueryExecutor(CountAsync.Instance);
+            //AddDefaultQueryExecutor(CountAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.TotalCountAsync));
+
+            //// ToListAndTotalCount
+            //AddDefaultQueryExecutor(ToListAndTotalCountAsync.Instance);
+
+            //// FirstOrDefault First LastOrDefault Last
+            //AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance);
+            //AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.FirstAsync));
+            //AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.LastOrDefaultAsync));
+            //AddDefaultQueryExecutor(FirstOrDefaultAsync.Instance, methodName: nameof(Queryable_AsyncExtensions.LastAsync));
+            #endregion
+
+            return defaultQueryExecutors;
+        }
+
+        public Dictionary<string, IQueryExecutor> queryExecutors = defaultQueryExecutors;
+
+        #endregion
+
+
+        protected virtual bool QueryIsFromSameDb(object obj, Type elementType)
+        {
+            if (obj is not IQueryable query) return false;
+
+            if (DbContext.dbConfig.dbGroupName == QueryableBuilder.GetQueryConfig(query) as string) return true;
+
+            if (QueryableBuilder.BuildFrom(query))
+                throw new InvalidOperationException("not allow query from different data source , queryable type: " + obj?.GetType().FullName);
+
+            return false;
+        }
+
+        protected virtual object ExecuteQuery(Expression expression, Type expressionResultType, Action dispose)
+        {
+            // #1 convert to ExpressionNode 
+            ExpressionNode_Lambda node = DbContext.convertService.ConvertToData_LambdaNode(expression, autoReduce: true, isArgument: QueryIsFromSameDb);
+            //var strNode = Json.Serialize(node);
+
+
+            // #2 convert to Stream
+            var stream = DbContext.streamReader.ReadFromNode(node);
+            //var strStream = Json.Serialize(stream);
+
+            if (stream is not CombinedStream combinedStream) combinedStream = new CombinedStream("tmp") { source = stream };
+
+            var executorArg = new QueryExecutorArgument
+            {
+                combinedStream = combinedStream,
+                dbContext = DbContext,
+                expression = expression,
+                expressionResultType = expressionResultType,
+                dispose = dispose,
+            };
+
+
+
+
+            #region Execute by registered executor
+            {
+                var method = combinedStream.method;
+                if (string.IsNullOrWhiteSpace(method)) method = nameof(Enumerable.ToList);
+                if (queryExecutors.TryGetValue(method, out var queryExecutor))
+                {
+                    return queryExecutor.ExecuteQuery(executorArg);
+                }
+            }
+            #endregion
+
+            throw new NotSupportedException("not supported query method: " + combinedStream.method);
+        }
+
+
+
+
+    }
+}

+ 407 - 68
src/Vitorm.MongoDB/DbSet.cs

@@ -1,40 +1,15 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
 using System.Threading.Tasks;
 
+using MongoDB.Bson;
 using MongoDB.Driver;
 
-using Vit.Linq.FilterRules;
-using Vit.Linq.FilterRules.ComponentModel;
-
 using Vitorm.Entity;
 
 namespace Vitorm.MongoDB
 {
-    // https://www.mongodb.com/docs/drivers/csharp/current/
-    // https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/first-mongo-app?view=aspnetcore-8.0&tabs=visual-studio
-
-    public class DbSetConstructor
-    {
-        public static IDbSet CreateDbSet(IDbContext dbContext, IEntityDescriptor entityDescriptor)
-        {
-            return _CreateDbSet.MakeGenericMethod(entityDescriptor.entityType)
-                     .Invoke(null, new object[] { dbContext, entityDescriptor }) as IDbSet;
-        }
-
-        static readonly MethodInfo _CreateDbSet = new Func<DbContext, IEntityDescriptor, IDbSet>(CreateDbSet<object>)
-                   .Method.GetGenericMethodDefinition();
-        public static IDbSet<Entity> CreateDbSet<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor)
-        {
-            return new DbSet<Entity>(dbContext, entityDescriptor);
-        }
-
-    }
-
-
     public partial class DbSet<Entity> : IDbSet<Entity>
     {
         public virtual IDbContext dbContext { get; protected set; }
@@ -56,65 +31,321 @@ namespace Vitorm.MongoDB
         public virtual IEntityDescriptor ChangeTableBack() => _entityDescriptor = _entityDescriptor.GetOriginEntityDescriptor();
 
         public IMongoDatabase database => DbContext.dbConfig.GetDatabase();
-        public IMongoCollection<Entity> collection => database.GetCollection<Entity>(entityDescriptor.tableName);
+
+        public IMongoCollection<BsonDocument> collection => database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+
+
+        public virtual BsonDocument Serialize(Entity entity)
+        {
+            return DbContext.Serialize(entity, entityDescriptor);
+        }
+        public virtual Entity Deserialize(BsonDocument doc)
+        {
+            return (Entity)DbContext.Deserialize(doc, entityDescriptor);
+        }
+
+
 
 
         #region #0 Schema :  Create Drop
 
-        public virtual bool TableExist()
+        public virtual bool TableExists()
         {
-            var names = database.ListCollectionNames().ToList();
-            return names.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+            var collectionNames = database.ListCollectionNames().ToList();
+            var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TableExists",
+                    ["collectionNames"] = collectionNames,
+                    ["exists"] = exists
+                }))
+             );
+
+            return exists;
         }
-        public virtual async Task<bool> TableExistAsync()
+        public virtual async Task<bool> TableExistsAsync()
         {
-            var names = await (await database.ListCollectionNamesAsync()).ToListAsync();
-            return names.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+            var collectionNames = await (await database.ListCollectionNamesAsync()).ToListAsync();
+            var exists = collectionNames.Exists(name => entityDescriptor.tableName.Equals(name, StringComparison.OrdinalIgnoreCase));
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TableExistsAsync",
+                    ["collectionNames"] = collectionNames,
+                    ["exists"] = exists
+                }))
+            );
+
+            return exists;
         }
 
-        public virtual void TryCreateTable()
+
+
+        public virtual void CreateIndex(string field, bool ascending = true, bool unique = false)
         {
-            if (!TableExist())
-                database.CreateCollection(entityDescriptor.tableName);
+            // https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/indexes/
+            var options = new CreateIndexOptions { Unique = true };
+            var indexModel = new CreateIndexModel<BsonDocument>(Builders<BsonDocument>.IndexKeys.Ascending(entityDescriptor.key.columnName), options);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: field,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "CreateIndex",
+                    ["indexModel"] = indexModel
+                }))
+            );
+
+            collection.Indexes.CreateOne(indexModel);
         }
-        public virtual async Task TryCreateTableAsync()
+        public virtual async Task CreateIndexAsync(string field, bool ascending = true, bool unique = false)
         {
-            if (!await TableExistAsync())
-                await database.CreateCollectionAsync(entityDescriptor.tableName);
+            var options = new CreateIndexOptions { Unique = true };
+            var indexModel = new CreateIndexModel<BsonDocument>(Builders<BsonDocument>.IndexKeys.Ascending(entityDescriptor.key.columnName), options);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: field,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "CreateIndexAsync",
+                    ["indexModel"] = indexModel
+                }))
+            );
+
+            await collection.Indexes.CreateOneAsync(indexModel);
         }
 
-        public virtual void TryDropTable() => database.DropCollection(entityDescriptor.tableName);
-        public virtual Task TryDropTableAsync() => database.DropCollectionAsync(entityDescriptor.tableName);
 
 
-        public virtual void Truncate() => collection.DeleteMany(m => true);
+        public virtual void TryCreateTable()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TryCreateTable"
+                }))
+            );
+
+
+            if (TableExists()) return;
+
+            database.CreateCollection(entityDescriptor.tableName);
+
+            // create unique index
+            if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
+            {
+                CreateIndex(entityDescriptor.key.columnName, ascending: true, unique: true);
+            }
+        }
+
+
+        public virtual async Task TryCreateTableAsync()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TryCreateTableAsync"
+                }))
+            );
+
+            if (await TableExistsAsync()) return;
+
+            await database.CreateCollectionAsync(entityDescriptor.tableName);
+
+            // create unique index
+            if (entityDescriptor.key != null && entityDescriptor.key.columnName != "_id")
+            {
+                await CreateIndexAsync(entityDescriptor.key.columnName, ascending: true, unique: true);
+            }
+        }
 
-        public virtual Task TruncateAsync() => collection.DeleteManyAsync(m => true);
+        public virtual void TryDropTable()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TryDropTable"
+                }))
+            );
+
+            database.DropCollection(entityDescriptor.tableName);
+        }
+        public virtual async Task TryDropTableAsync()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TryDropTableAsync"
+                }))
+            );
+
+            await database.DropCollectionAsync(entityDescriptor.tableName);
+        }
 
+        public virtual void Truncate()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "Truncate"
+                }))
+            );
+
+            collection.DeleteMany(m => true);
+        }
+        public virtual async Task TruncateAsync()
+        {
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: entityDescriptor.tableName,
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "TruncateAsync"
+                }))
+            );
+
+            await collection.DeleteManyAsync(m => true);
+        }
         #endregion
 
 
         #region #1 Create :  Add AddRange
         public virtual Entity Add(Entity entity)
         {
-            collection.InsertOne(entity);
+            var doc = DbContext.Serialize(entity, entityDescriptor);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: doc?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "Add",
+                    ["entity"] = entity,
+                    ["doc"] = doc
+                }))
+            );
+
+            collection.InsertOne(doc);
             return entity;
         }
 
-        public virtual void AddRange(IEnumerable<Entity> entities)
-        {
-            collection.InsertMany(entities);
-        }
 
         public virtual async Task<Entity> AddAsync(Entity entity)
         {
-            await collection.InsertOneAsync(entity);
+            var doc = DbContext.Serialize(entity, entityDescriptor);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: doc?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "AddAsync",
+                    ["entity"] = entity,
+                    ["doc"] = doc
+                }))
+            );
+
+            await collection.InsertOneAsync(doc);
             return entity;
         }
 
-        public virtual Task AddRangeAsync(IEnumerable<Entity> entities)
+        public virtual void AddRange(IEnumerable<Entity> entities)
         {
-            return collection.InsertManyAsync(entities);
+            var docs = entities.Select(entity => DbContext.Serialize(entity, entityDescriptor));
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: new BsonArray(docs)?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "AddRange",
+                    ["entities"] = entities,
+                    ["docs"] = docs
+                }))
+            );
+
+            collection.InsertMany(docs);
+        }
+
+
+        public virtual async Task AddRangeAsync(IEnumerable<Entity> entities)
+        {
+            var docs = entities.Select(entity => DbContext.Serialize(entity, entityDescriptor));
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: new BsonArray(docs)?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "AddRangeAsync",
+                    ["entities"] = entities,
+                    ["docs"] = docs
+                }))
+            );
+
+            await collection.InsertManyAsync(docs);
         }
         #endregion
 
@@ -124,48 +355,99 @@ namespace Vitorm.MongoDB
         public virtual Entity Get(object keyValue)
         {
             var predicate = GetKeyPredicate(keyValue);
-            return collection.Find(predicate).FirstOrDefault();
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "Get"
+                }))
+            );
+
+            return Deserialize(collection.Find(predicate).FirstOrDefault());
         }
 
-        public virtual Task<Entity> GetAsync(object keyValue)
+        public virtual async Task<Entity> GetAsync(object keyValue)
         {
             var predicate = GetKeyPredicate(keyValue);
-            return collection.Find(predicate).FirstOrDefaultAsync();
-        }
 
-        public virtual IQueryable<Entity> Query()
-        {
-            return collection.AsQueryable();
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "GetAsync"
+                }))
+            );
+
+            return Deserialize(await collection.Find(predicate).FirstOrDefaultAsync());
         }
 
         #endregion
 
 
-        public virtual Expression<Func<Entity, bool>> GetKeyPredicate(object keyValue)
+        public virtual BsonDocument GetKeyPredicate(object keyValue)
         {
-            var filter = new FilterRule { field = entityDescriptor.key.propertyName, @operator = "=", value = keyValue };
-            var predicate = FilterService.Instance.ConvertToCode_PredicateExpression<Entity>(filter);
-            return predicate;
+            return new BsonDocument { [entityDescriptor.key.columnName] = BsonValue.Create(keyValue) };
         }
 
-        public virtual Expression<Func<Entity, bool>> GetKeyPredicate<Key>(IEnumerable<Key> keys)
+        public virtual BsonDocument GetKeyPredicate<Key>(IEnumerable<Key> keys)
         {
-            var filter = new FilterRule { field = entityDescriptor.key.propertyName, @operator = "In", value = keys };
-            var predicate = FilterService.Instance.ConvertToCode_PredicateExpression<Entity>(filter);
-            return predicate;
+            var values = keys.Select(key => BsonValue.Create(key));
+            return new BsonDocument { [entityDescriptor.key.columnName] = new BsonDocument("$in", new BsonArray(values)) };
         }
 
         #region #3 Update: Update UpdateRange
         public virtual int Update(Entity entity)
         {
             var predicate = GetKeyPredicate(entityDescriptor.key.GetValue(entity));
-            var result = collection.ReplaceOne(predicate, entity);
+            var doc = Serialize(entity);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: doc?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "Update",
+                    ["predicate"] = predicate,
+                    ["entity"] = entity,
+                    ["doc"] = doc,
+                })));
+
+
+            var result = collection.ReplaceOne(predicate, doc);
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
         }
         public virtual async Task<int> UpdateAsync(Entity entity)
         {
             var predicate = GetKeyPredicate(entityDescriptor.key.GetValue(entity));
-            var result = await collection.ReplaceOneAsync(predicate, entity);
+            var doc = Serialize(entity);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: doc?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "UpdateAsync",
+                    ["predicate"] = predicate,
+                    ["entity"] = entity,
+                    ["doc"] = doc,
+                })));
+
+            var result = await collection.ReplaceOneAsync(predicate, doc);
             return result.IsAcknowledged && result.IsModifiedCountAvailable ? (int)result.ModifiedCount : 0;
         }
 
@@ -188,6 +470,7 @@ namespace Vitorm.MongoDB
         public virtual int Delete(Entity entity)
         {
             var keyValue = entityDescriptor.key.GetValue(entity);
+
             return DeleteByKey(keyValue);
         }
         public virtual Task<int> DeleteAsync(Entity entity)
@@ -214,12 +497,40 @@ namespace Vitorm.MongoDB
         public virtual int DeleteByKey(object keyValue)
         {
             var predicate = GetKeyPredicate(keyValue);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "DeleteByKey",
+                    ["predicate"] = predicate,
+                    ["key"] = keyValue,
+                })));
+
             var result = collection.DeleteOne(predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         public virtual async Task<int> DeleteByKeyAsync(object keyValue)
         {
             var predicate = GetKeyPredicate(keyValue);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "DeleteByKeyAsync",
+                    ["predicate"] = predicate,
+                    ["key"] = keyValue,
+                })));
+
             var result = await collection.DeleteOneAsync(predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
@@ -229,12 +540,40 @@ namespace Vitorm.MongoDB
         public virtual int DeleteByKeys<Key>(IEnumerable<Key> keys)
         {
             var predicate = GetKeyPredicate<Key>(keys);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "DeleteByKeys",
+                    ["predicate"] = predicate,
+                    ["keys"] = keys,
+                })));
+
             var result = collection.DeleteMany(predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }
         public virtual async Task<int> DeleteByKeysAsync<Key>(IEnumerable<Key> keys)
         {
             var predicate = GetKeyPredicate<Key>(keys);
+
+            // Event_OnExecuting
+            DbContext.Event_OnExecuting(new Lazy<ExecuteEventArgument>(() => new ExecuteEventArgument(
+                dbContext: DbContext,
+                executeString: predicate?.ToJson(),
+                extraParam: new()
+                {
+                    ["dbSet"] = this,
+                    ["entityDescriptor"] = entityDescriptor,
+                    ["Method"] = "DeleteByKeysAsync",
+                    ["predicate"] = predicate,
+                    ["keys"] = keys,
+                })));
+
             var result = await collection.DeleteManyAsync(predicate);
             return result.IsAcknowledged ? (int)result.DeletedCount : 0;
         }

+ 27 - 0
src/Vitorm.MongoDB/DbSetConstructor.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Reflection;
+
+using Vitorm.Entity;
+
+namespace Vitorm.MongoDB
+{
+    // https://www.mongodb.com/docs/drivers/csharp/current/
+    // https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/first-mongo-app?view=aspnetcore-8.0&tabs=visual-studio
+
+    public class DbSetConstructor
+    {
+        public static IDbSet CreateDbSet(IDbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            return _CreateDbSet.MakeGenericMethod(entityDescriptor.entityType)
+                     .Invoke(null, new object[] { dbContext, entityDescriptor }) as IDbSet;
+        }
+
+        static readonly MethodInfo _CreateDbSet = new Func<DbContext, IEntityDescriptor, IDbSet>(CreateDbSet<object>)
+                   .Method.GetGenericMethodDefinition();
+        public static IDbSet<Entity> CreateDbSet<Entity>(DbContext dbContext, IEntityDescriptor entityDescriptor)
+        {
+            return new DbSet<Entity>(dbContext, entityDescriptor);
+        }
+
+    }
+}

+ 226 - 0
src/Vitorm.MongoDB/DbSet_Primitive.cs

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

+ 8 - 0
src/Vitorm.MongoDB/QueryExecutor/IQueryExecutor.cs

@@ -0,0 +1,8 @@
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public interface IQueryExecutor
+    {
+        public string methodName { get; }
+        object ExecuteQuery(QueryExecutorArgument execArg);
+    }
+}

+ 24 - 0
src/Vitorm.MongoDB/QueryExecutor/QueryExecutorArgument.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Linq.Expressions;
+
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public class QueryExecutorArgument : IDisposable
+    {
+        public CombinedStream combinedStream;
+        public DbContext dbContext;
+
+        public Expression expression;
+        public Type expressionResultType;
+
+        public Action dispose;
+
+        public void Dispose()
+        {
+            dispose?.Invoke();
+        }
+    }
+
+}

+ 17 - 0
src/Vitorm.MongoDB/QueryExecutor/QueryExecutor_Delegate.cs

@@ -0,0 +1,17 @@
+
+using FuncQueryExecutor = System.Func<Vitorm.MongoDB.QueryExecutor.QueryExecutorArgument, object>;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public class QueryExecutor_Delegate : IQueryExecutor
+    {
+        public QueryExecutor_Delegate(string methodName, FuncQueryExecutor queryExecutor)
+        {
+            this.methodName = methodName;
+            this.queryExecutor = queryExecutor;
+        }
+        public string methodName { get; set; }
+        FuncQueryExecutor queryExecutor;
+        public object ExecuteQuery(QueryExecutorArgument execArg) => queryExecutor(execArg);
+    }
+}

+ 149 - 0
src/Vitorm.MongoDB/QueryExecutor/Sync/ToList.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+using Vitorm.Entity;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public partial class ToList : IQueryExecutor
+    {
+        public static readonly ToList Instance = new();
+
+        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)
+        {
+            CombinedStream combinedStream = execArg.combinedStream;
+            var dbContext = execArg.dbContext;
+            var translateService = dbContext.translateService;
+
+            // #2 filter
+            var filter = translateService.TranslateQuery(execArg, 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 queryEntityType = query?.ElementType;
+            var entityDescriptor = dbContext.GetEntityDescriptor(queryEntityType);
+
+            // #3 execute query
+            var database = dbContext.dbConfig.GetDatabase();
+            var collection = database.GetCollection<BsonDocument>(entityDescriptor.tableName);
+
+            // #4 read entity
+            object result;
+
+
+
+            // 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();
+
+                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;
+        }
+
+
+        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);
+                }
+            }
+        }
+    }
+
+}

+ 305 - 0
src/Vitorm.MongoDB/QueryExecutor/TranslateService.cs

@@ -0,0 +1,305 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+using MongoDB.Bson;
+
+using Vit.Linq.ExpressionNodes.ComponentModel;
+
+using Vitorm.Entity.PropertyType;
+using Vitorm.StreamQuery;
+
+namespace Vitorm.MongoDB.QueryExecutor
+{
+    public class TranslateService
+    {
+        public virtual BsonDocument TranslateQuery(QueryExecutorArgument arg, CombinedStream combinedStream)
+        {
+            if (combinedStream?.where == null) return new();
+
+            return EvalExpression(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)
+        {
+            return GetFieldPath(arg, member, out _);
+        }
+
+
+        public virtual bool TryReadValue(QueryExecutorArgument arg, ExpressionNode node, out BsonValue value)
+        {
+            switch (node?.nodeType)
+            {
+                case NodeType.Constant:
+                    {
+                        ExpressionNode_Constant constant = node;
+
+                        var v = constant.value;
+                        if (v == null)
+                        {
+                            value = BsonValue.Create(null);
+                            return true;
+                        }
+
+                        var type = constant.Constant_GetType();
+                        if (TypeUtil.IsValueType(type))
+                        {
+                            value = BsonValue.Create(v);
+                            return true;
+                        }
+
+                        if (TypeUtil.IsArrayType(type))
+                        {
+                            if (v is ICollection collection)
+                            {
+                                var array = new BsonArray();
+                                foreach (var item in collection)
+                                {
+                                    array.Add(BsonValue.Create(item));
+                                }
+                                value = array;
+                                return true;
+                            }
+                        }
+
+                        value = null;
+                        return false;
+                    }
+
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = node;
+
+                        //switch (methodCall.methodName)
+                        //{ 
+                        //    // ##4 String.Format(format: "{0}_{1}_{2}", "0", "1", "2")
+                        //    case nameof(String.Format):
+                        //        {
+                        //            // convert to ExpressionNode.Add
+
+                        //            // "{0}_{1}_{2}"
+                        //            var format = methodCall.arguments[0].value as string;
+                        //            var args = methodCall.arguments.AsQueryable().Skip(1).ToArray();
+
+                        //            var nodeParts = SplitToNodeParts(format, args);
+
+                        //            ExpressionNode nodeForAdd = null;
+                        //            foreach (var child in nodeParts)
+                        //            {
+                        //                if (nodeForAdd == null) nodeForAdd = child;
+                        //                else nodeForAdd = ExpressionNode.Add(left: nodeForAdd, right: child, typeof(string));
+                        //            }
+
+                        //            return $"({EvalExpression(arg, nodeForAdd)})";
+
+
+                        //            static IEnumerable<ExpressionNode> SplitToNodeParts(string format, ExpressionNode[] args)
+                        //            {
+                        //                string pattern = @"(\{\d+\})|([^{}]+)";
+                        //                var matches = Regex.Matches(format, pattern);
+
+                        //                foreach (Match match in matches)
+                        //                {
+                        //                    var str = match.Value;
+                        //                    if (str.StartsWith("{") && str.EndsWith("}"))
+                        //                    {
+                        //                        var argIndex = int.Parse(str.Substring(1, str.Length - 2));
+                        //                        yield return args[argIndex];
+                        //                    }
+                        //                    else
+                        //                    {
+                        //                        yield return ExpressionNode.Constant(str, typeof(string));
+                        //                    }
+                        //                }
+                        //            }
+                        //        }
+
+                        //}
+                        break;
+                    }
+
+            }
+            value = null;
+            return false;
+        }
+
+        public virtual BsonDocument EvalExpression(QueryExecutorArgument arg, ExpressionNode node)
+        {
+            switch (node?.nodeType)
+            {
+                case NodeType.AndAlso:
+                    {
+                        ExpressionNode_Binary binary = node;
+                        return new BsonDocument("$and", new BsonArray { EvalExpression(arg, binary.left), EvalExpression(arg, binary.right) });
+                    }
+                case NodeType.OrElse:
+                    {
+                        ExpressionNode_Binary binary = node;
+                        return new BsonDocument("$or", new BsonArray { EvalExpression(arg, binary.left), EvalExpression(arg, binary.right) });
+                    }
+                case NodeType.Not:
+                    {
+                        ExpressionNode_Not not = node;
+                        return new BsonDocument("$not", EvalExpression(arg, not.body));
+                    }
+                case NodeType.Equal:
+                case NodeType.NotEqual:
+                case NodeType.LessThan:
+                case NodeType.LessThanOrEqual:
+                case NodeType.GreaterThan:
+                case NodeType.GreaterThanOrEqual:
+                    {
+                        ExpressionNode_Binary binary = node;
+
+                        string fieldPath;
+                        BsonValue value;
+                        bool fieldInLeft = true;
+                        string operate;
+
+                        if (TryReadValue(arg, binary.right, out value))
+                        {
+                            fieldPath = GetFieldPath(arg, binary.left);
+                        }
+                        else if (TryReadValue(arg, binary.left, out value))
+                        {
+                            fieldInLeft = false;
+                            fieldPath = GetFieldPath(arg, binary.right);
+                        }
+                        else break;
+
+                        operate = node.nodeType switch
+                        {
+                            NodeType.Equal => "$eq",
+                            NodeType.NotEqual => "$ne",
+                            NodeType.LessThan => fieldInLeft ? "$lt" : "$gte",
+                            NodeType.LessThanOrEqual => fieldInLeft ? "$lte" : "$gt",
+                            NodeType.GreaterThan => fieldInLeft ? "$gt" : "$lte",
+                            NodeType.GreaterThanOrEqual => fieldInLeft ? "$gte" : "$lt",
+                        };
+
+                        return new BsonDocument(fieldPath, new BsonDocument(operate, value));
+                    }
+                case NodeType.MethodCall:
+                    {
+                        ExpressionNode_MethodCall methodCall = node;
+
+                        switch (methodCall.methodName)
+                        {
+                            // ##1 in
+                            case nameof(List<string>.Contains) when methodCall.@object is not null && methodCall.arguments.Length == 1:
+                                {
+                                    var values = methodCall.@object;
+                                    var member = methodCall.arguments[0];
+
+                                    var fieldPath = GetFieldPath(arg, member);
+                                    if (!TryReadValue(arg, values, out var array)) break;
+
+                                    return new BsonDocument(fieldPath, new BsonDocument("$in", array));
+                                }
+                            case nameof(Enumerable.Contains) when methodCall.arguments.Length == 2:
+                                {
+                                    var values = methodCall.arguments[0];
+                                    var member = methodCall.arguments[1];
+
+                                    var fieldPath = GetFieldPath(arg, member);
+                                    if (!TryReadValue(arg, values, out var array)) break;
+
+                                    return new BsonDocument(fieldPath, new BsonDocument("$in", array));
+                                }
+                        }
+                        throw new NotSupportedException("[TranslateService] not supported MethodCall: " + methodCall.methodName);
+                    }
+            }
+            throw new NotSupportedException("[TranslateService] not supported notType: " + node?.nodeType);
+        }
+
+    }
+}

+ 11 - 5
test/Vitorm.MongoDB.MsTest/CommonTest/EntityLoader_CustomLoader_Test.cs

@@ -4,6 +4,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 
 using Vitorm.Entity;
 using Vitorm.Entity.Loader.DataAnnotations;
+using Vitorm.Entity.PropertyType;
 
 namespace Vitorm.MsTest.CommonTest
 {
@@ -120,7 +121,7 @@ namespace Vitorm.MsTest.CommonTest
             {
                 if (!GetTableName(entityType, out var tableName, out var schema)) return default;
 
-                IColumnDescriptor[] allColumns = entityType?.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                var propertyDescriptors = entityType?.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                     .Select(propertyInfo =>
                     {
                         var labels = propertyInfo?.GetCustomAttributes<LabelAttribute>();
@@ -152,15 +153,20 @@ namespace Vitorm.MsTest.CommonTest
                             }
                         }
 
-                        return new ColumnDescriptor(
-                            propertyInfo, columnName: columnName,
+                        return new PropertyDescriptor(
+                            propertyInfo, propertyType: new PropertyValueType(propertyInfo.PropertyType),
+                            columnName: columnName,
                             isKey: isKey, isIdentity: isIdentity, isNullable: isNullable,
                             columnDbType: columnDbType,
                             columnOrder: columnOrder
                             );
-                    }).Where(column => column != null).ToArray();
+                    }).Where(column => column != null);
 
-                return (true, new EntityDescriptor(entityType, allColumns, tableName, schema));
+
+                var propertyType = new PropertyObjectType(entityType);
+                propertyType.properties = propertyDescriptors.Select(m => (IPropertyDescriptor)m).ToArray();
+
+                return (true, new EntityDescriptor(propertyType, tableName, schema));
             }
         }
         #endregion

+ 44 - 0
test/Vitorm.MongoDB.MsTest/CommonTest/Query_Where_In_Test.cs

@@ -0,0 +1,44 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Vit.Linq;
+
+namespace Vitorm.MsTest.CommonTest
+{
+
+    [TestClass]
+    public class Query_Where_In_Test
+    {
+
+        [TestMethod]
+        public void Test_In()
+        {
+            using var dbContext = DataSource.CreateDbContext();
+            var userQuery = dbContext.Query<User>();
+
+            {
+                var ids = new[] { 1, 2 };
+                var userList = userQuery.Where(m => ids.Contains(m.id)).OrderBy(m => m.id).ToList();
+
+                var strIds = String.Join(',', userList.Select(m => m.id));
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual("1,2", strIds);
+            }
+
+
+            {
+                var ids = new List<int> { 1, 2 };
+                var userList = userQuery.Where(m => ids.Contains(m.id)).OrderBy(m => m.id).ToList();
+
+                var strIds = String.Join(',', userList.Select(m => m.id));
+                Assert.AreEqual(2, userList.Count);
+                Assert.AreEqual("1,2", strIds);
+            }
+
+        }
+
+
+
+    }
+}

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

@@ -0,0 +1,222 @@
+using System.Data;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using MongoDB.Driver;
+
+namespace Vitorm.MsTest.CustomTest
+{
+
+    [TestClass]
+    public class Linq_Test
+    {
+        [System.ComponentModel.DataAnnotations.Schema.Table("User2")]
+        public class User2 : User
+        {
+
+            public string[] nickNames { get; set; }
+            public List<string> nickNameList { get; set; }
+            public SortedSet<string> nickNameList2 { get; set; }
+
+            public float?[] scores { get; set; }
+            public int[] scoreRanks { get; set; }
+
+            public List<float?> scoreList { get; set; }
+            public List<int> scoreRankList { get; set; }
+
+            public User2 father { get; set; }
+            public User2[] parents { get; set; }
+            public List<User2> parentList { get; set; }
+
+        }
+
+
+        static Vitorm.MongoDB.DbContext CreateDbContext()
+        {
+            using var dbContext = DataSource.CreateDbContext(autoInit: false);
+
+            //return dbContext;
+
+            dbContext.TryDropTable<User2>();
+            dbContext.TryCreateTable<User2>();
+
+            var users = new List<User2> {
+                    new User2 { id=1, name="u146", fatherId=4, motherId=6 },
+                    new User2 { id=2, name="u246", fatherId=4, motherId=6 },
+                    new User2 { id=3, name="u356", fatherId=5, motherId=6 },
+                    new User2 { id=4, name="u400" },
+                    new User2 { id=5, name="u500" },
+                    new User2 { id=6, name="u600" },
+                };
+
+            users.ForEach(user =>
+            {
+                user.birth = DateTime.Parse("2021-01-01 00:00:00").AddHours(user.id);
+                user.classId = user.id % 2 + 1;
+
+
+                if (user.fatherId.HasValue || user.motherId.HasValue)
+                {
+                    user.parents = new User2[] {
+                            user.fatherId.HasValue? new (){ id=user.fatherId.Value}:null,
+                             user.motherId.HasValue? new (){ id=user.motherId.Value}:null
+                    };
+                    user.parentList = new(user.parents);
+
+                    user.father = user.parents?.FirstOrDefault();
+                }
+
+                user.nickNames = new[] { user.name, user.fatherId?.ToString(), user.motherId?.ToString() };
+                user.nickNameList = new(user.nickNames);
+                user.nickNameList2 = new(user.nickNames);
+
+                user.scores = new float?[] { user.id * 10.1f, null, user.id * 10.2f };
+                user.scoreList = new(user.scores);
+
+                user.scoreRanks = new[] { user.id, user.id + 100 };
+                user.scoreRankList = new(user.scoreRanks);
+
+            });
+
+            dbContext.AddRange(users);
+
+            return dbContext;
+        }
+
+
+
+        [TestMethod]
+        public async Task NestedEntity()
+        {
+            using var dbContext = CreateDbContext();
+
+            {
+                var list = dbContext.Query<User2>().ToList();
+                Assert.AreEqual(6, list.Count);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.id != 1).ToList();
+                Assert.AreEqual(5, list.Count);
+            }
+        }
+
+
+        [TestMethod]
+        public async Task Test_Select()
+        {
+            using var dbContext = CreateDbContext();
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.id != 1).Select(m => m).ToList();
+                Assert.AreEqual(5, list.Count);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.id != 1).Select(m => m.id).ToList();
+                Assert.AreEqual(5, list.Count);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.id != 1).Select(m => m.fatherId + 1).ToList();
+                Assert.AreEqual(5, list.Count);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.id != 1).Select(m => new { m.id, m.father, m.name, id2 = m.id + 1 }).ToList();
+                Assert.AreEqual(5, list.Count);
+            }
+
+        }
+
+        [TestMethod]
+        public async Task Test_OrderSkipTake()
+        {
+            using var dbContext = CreateDbContext();
+
+            {
+                var list = dbContext.Query<User2>().OrderBy(m => m.id).ThenByDescending(m => m.fatherId).Select(m => new { m.id, m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(6, list.Count);
+                Assert.AreEqual("1,2,3,4,5,6", ids);
+            }
+
+            {
+                var list = dbContext.Query<User2>().OrderBy(m => m.fatherId).ThenByDescending(m => m.id).Select(m => new { m.id, m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(6, list.Count);
+                Assert.AreEqual("6,5,4,2,1,3", ids);
+            }
+            {
+                var list = dbContext.Query<User2>().OrderBy(m => m.father.id).ThenBy(m => m.id).Select(m => new { m.id, m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(6, list.Count);
+                Assert.AreEqual("4,5,6,1,2,3", ids);
+            }
+
+
+
+            {
+                var list = dbContext.Query<User2>().OrderBy(m => m.father.id).ThenBy(m => m.id).Skip(1).Take(3).Select(m => new { m.id, m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(3, list.Count);
+                Assert.AreEqual("5,6,1", ids);
+            }
+
+            {
+                var list = dbContext.Query<User2>().OrderBy(m => m.father.id).ThenBy(m => m.id).Skip(0).Take(10).Select(m => new { m.id, m.fatherId }).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(6, list.Count);
+                Assert.AreEqual("4,5,6,1,2,3", ids);
+            }
+
+        }
+
+
+
+        [TestMethod]
+        public async Task Test_Where()
+        {
+            using var dbContext = CreateDbContext();
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.fatherId == 4).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual("1,2", ids);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.father.id == 4).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual("1,2", ids);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.father.id != 4).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(4, list.Count);
+                Assert.AreEqual("3,4,5,6", ids);
+            }
+
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.parents[0].id == 4).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual("1,2", ids);
+            }
+
+            {
+                var list = dbContext.Query<User2>().Where(m => m.parentList[0].id == 4).OrderBy(m => m.id).ToList();
+                var ids = String.Join(',', list.Select(m => m.id));
+                Assert.AreEqual(2, list.Count);
+                Assert.AreEqual("1,2", ids);
+            }
+
+        }
+
+
+    }
+}

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

@@ -0,0 +1,87 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+namespace Vitorm.MsTest.CustomTest
+{
+
+    [TestClass]
+    public class Primitive_Test
+    {
+
+        [TestMethod]
+        public async Task Test()
+        {
+            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");
+
+
+            // #2 Add
+            {
+                var user = new BsonDocument
+                {
+                    ["userId"] = BsonValue.Create(1),
+                    ["userName"] = BsonValue.Create("u146"),
+                    ["fatherId"] = BsonValue.Create(4),
+
+                    ["father"] = new BsonDocument
+                    {
+                        ["userId"] = BsonValue.Create(4),
+                        ["userName"] = BsonValue.Create("u400"),
+                    },
+                    ["parents"] = new BsonArray
+                        {
+                           new BsonDocument
+                            {
+                                ["userId"] = BsonValue.Create(4),
+                                ["userName"] = BsonValue.Create("u400"),
+                            }
+                        },
+                };
+
+                await collection.InsertOneAsync(user);
+            }
+
+
+            // #3 query by FilterBuilder
+            {
+                var builder = Builders<BsonDocument>.Filter;
+
+                {
+                    var filter = builder.Eq("userId", 1);
+                    var json = collection.Find(filter).FirstOrDefault()?.ToJson();
+                    Assert.IsNotNull(json);
+                }
+
+                {
+                    var filter = builder.And(builder.Eq("userId", 1), builder.Regex("userName", ",*146"));
+                    var json = collection.Find(filter).FirstOrDefault()?.ToJson();
+                    Assert.IsNotNull(json);
+                }
+
+                {
+                    var filter = builder.Eq("parents.0.userId", 4);
+                    var json = collection.Find(filter).FirstOrDefault()?.ToJson();
+                    Assert.IsNotNull(json);
+                }
+            }
+
+        }
+
+
+
+
+
+    }
+}