zoukankan      html  css  js  c++  java
  • .net平台的MongoDB使用

    前言

      最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。

      本篇会设计到Lambda表达式的解析,有兴趣的同学也看看我之前写的《表达式树的解析》。

      文章最后会给出源码下载地址。

    MongoDB简介

      MongoDB是一个基于分布式文件存储的非关系型数据库,相比于其他NoSql它支持复杂的查询。

      文本是类似JSON的BSON格式,BSON是在JSON的基础上进化:更快的遍历、操作更简易、更多的数据类型。因此MongoDB可以存储比较复杂的数据类型,同样也支持建立索引。

      MongoDB的概念有:

    • DataBase(库)
    • Collections(集合),类似于关系型数据库的表
    • Document(文档),类似于关系型数据库的一条数据

      

    MongoDB优缺点

    • 优点

    1. 高效性,内置GridFS,从而达到海量数据存储,并且满足大数据集的快速范围查询。
    2. 高扩展性,分片使MongoDB的有更高的吞吐量,复制使MongoDB更高的可用性。
    3. BSON文档,易于理解、查看,
    4. 免费
    • 缺点

    1. 不支持事务
    2. 不支持表关联
    3. 不耗CPU却耗内存
    4. 没有成熟的管理工具

    MongoDB使用场景

      拥有高效的存储的特点,让MongoDB用在操作日志记录是非常流行的做法。

      随着版本的升级提供更加强大的功能,产品逐渐成熟用在主业务也很多,例如电商行业的订单系统与包裹跟踪模块,海量的主订单与订单明细,包裹的状态变更信息。

      然而因为BSON文档的存储方式,使平常的开发的思维模式有所变更。举个栗子,传统用关系型数据库,订单模块就会分主订单表和订单明细表,创建订单就会用事务同时添加两表的数据,查找订单也会通过两表关联查询出来。但是使用MongoDB,主订单表与其明细,将会以一个完整的对象保存为文档。

      也因为不支持事务、表关联的原因,它更加适合用作于一个完整的业务模块。

      部分朋友会带着一个问题,非关系型数据库和关系型数据库哪个更好。我认为,谁都无法代替谁,一般情况下,非关系型数据库更多的作为关系型数据库扩展,用好了效果甚佳,滥用了只会寸步难行。

      

    MongoDB安装

      本来想写的,相应的文章在园子太多了,借用一位仁兄的博文,传送门

      MongoDB下载地址:https://www.mongodb.com/download-center#community

      管理工具:Robomongo,传送门

    MongoDB.Driver的使用

      

      创建一个控制台,到Nuget下载MongoDB.Driver。写入以下代码:

     1 using System;
     2 using FrameWork.MongoDB.MongoDbConfig;
     3 using MongoDB.Bson.Serialization.Attributes;
     4 using MongoDB.Driver;
     5 
     6 namespace FrameWork.MongoDb.Demo
     7 {
     8     class Program
     9     {
    10         static void Main(string[] args)
    11         {
    12             var database = "testdatabase";
    13             var collection = "TestMongo";
    14             var db = new MongoClient("您的地址").GetDatabase(database);
    15             var coll = db.GetCollection<TestMongo>(collection);
    16 
    17             var entity = new TestMongo
    18             {
    19                 Name = "SkyChen",
    20                 Amount = 100,
    21                 CreateDateTime = DateTime.Now
    22             };
    23 
    24             coll.InsertOneAsync(entity).ConfigureAwait(false);
    25 
    26         }
    27     }
    28 
    29     public class TestMongo : MongoEntity
    30     {
    31 
    32         [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
    33         public DateTime CreateDateTime { get; set; }
    34 
    35         public decimal Amount { get; set; }
    36 
    37         public string Name { get; set; }
    38 
    39     }
    40 }
    View Code

      第一个demo:添加数据就完成了。F12可以看到IMongoCollection这个接口,增删改查都有,注意分One和Many。基础的使用就不扯过多,在文章尾部的代码已经提供增删改查的封装。

      增删查的封装相对简单,但是MongoDB.Driver提供的update的稍微比较特殊。通过Builders<T>.Update.Set(_fieldname, value)更新指定字段名,有多个字段名需要修改,就要通过new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList)去完成

      然而,这种方式并不适用于我们实际开发,因此需要对Update方法进行 实体更新封装Lambda更新封装

    实体更新封装

      通过ID作为过滤条件更新整个实体在实际工作中是常有的。既然通过ID作为条件,那么只能通过UpdateOneAsync进行约束更新一条数据。更新的字段可以通过反射实体对象进行遍历属性。下边是实现代码:

    /// <summary>
        /// mongodb扩展方法
        /// </summary>
        internal static class MongoDbExtension
        {
            /// <summary>
            /// 获取更新信息
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="entity"></param>
            /// <returns></returns>
            internal static UpdateDefinition<T> GetUpdateDefinition<T>(this T entity)
            {
                var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
                var updateDefinitionList = GetUpdateDefinitionList<T>(properties, entity);
    
                var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList);
    
                return updateDefinitionBuilder;
            }
    
            /// <summary>
            /// 获取更新信息
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="propertyInfos"></param>
            /// <param name="entity"></param>
            /// <returns></returns>
            internal static List<UpdateDefinition<T>> GetUpdateDefinitionList<T>(PropertyInfo[] propertyInfos, object entity)
            {
                var updateDefinitionList = new List<UpdateDefinition<T>>();
    
                propertyInfos = propertyInfos.Where(a => a.Name != "_id").ToArray();
    
                foreach (var propertyInfo in propertyInfos)
                {
                    if (propertyInfo.PropertyType.IsArray || typeof(IList).IsAssignableFrom(propertyInfo.PropertyType))
                    {
                        var value = propertyInfo.GetValue(entity) as IList;
    
                        var filedName = propertyInfo.Name;
    
                        updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value));
                    }
                    else
                    {
                        var value = propertyInfo.GetValue(entity);
                        if (propertyInfo.PropertyType == typeof(decimal))
                            value = value.ToString();
    
                        var filedName = propertyInfo.Name;
    
                        updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value));
                    }
                }
    
                return updateDefinitionList;
            }
        }
    View Code

    Lambda表达式更新封装

      曾经用过其他ORM都清楚Lambda表达式使用是非常频繁的,MongoDB.Driver已经支持Lambda表达式的过滤条件,但没支持部分字段更新,因此由我们自己来写解析。下边是现实代码:

    #region Mongo更新字段表达式解析
        /// <summary>
        /// Mongo更新字段表达式解析
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class MongoDbExpression<T> : ExpressionVisitor
        {
            #region 成员变量
            /// <summary>
            /// 更新列表
            /// </summary>
            internal List<UpdateDefinition<T>> UpdateDefinitionList = new List<UpdateDefinition<T>>();
            private string _fieldname;
    
            #endregion
    
            #region 获取更新列表
            /// <summary>
            /// 获取更新列表
            /// </summary>
            /// <param name="expression"></param>
            /// <returns></returns>
            public static List<UpdateDefinition<T>> GetUpdateDefinition(Expression<Func<T, T>> expression)
            {
                var mongoDb = new MongoDbExpression<T>();
    
                mongoDb.Resolve(expression);
                return mongoDb.UpdateDefinitionList;
            }
            #endregion
    
            #region 解析表达式
            /// <summary>
            /// 解析表达式
            /// </summary>
            /// <param name="expression"></param>
            private void Resolve(Expression<Func<T, T>> expression)
            {
                Visit(expression);
            }
            #endregion
    
            #region 访问对象初始化表达式
    
            /// <summary>
            /// 访问对象初始化表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitMemberInit(MemberInitExpression node)
            {
                var bingdings = node.Bindings;
    
                foreach (var item in bingdings)
                {
                    var memberAssignment = (MemberAssignment)item;
                    _fieldname = item.Member.Name;
    
                    if (memberAssignment.Expression.NodeType == ExpressionType.MemberInit)
                    {
                        var lambda = Expression.Lambda<Func<object>>(Expression.Convert(memberAssignment.Expression, typeof(object)));
                        var value = lambda.Compile().Invoke();
                        UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value));
                    }
                    else
                    {
                        Visit(memberAssignment.Expression);
                    }
                }
                return node;
            }
    
            #endregion
    
            #region 访问二元表达式
    
            /// <summary>
            /// 访问二元表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitBinary(BinaryExpression node)
            {
                UpdateDefinition<T> updateDefinition;
    
                var value = ((ConstantExpression)node.Right).Value;
                if (node.Type == typeof(int))
                {
                    var realValue = (int)value;
                    if (node.NodeType == ExpressionType.Decrement)
                        realValue = -realValue;
    
                    updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue);
                }
                else if (node.Type == typeof(long))
                {
                    var realValue = (long)value;
                    if (node.NodeType == ExpressionType.Decrement)
                        realValue = -realValue;
    
                    updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue);
                }
                else if (node.Type == typeof(double))
                {
                    var realValue = (double)value;
                    if (node.NodeType == ExpressionType.Decrement)
                        realValue = -realValue;
    
                    updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue);
                }
                else if (node.Type == typeof(decimal))
                {
                    var realValue = (decimal)value;
                    if (node.NodeType == ExpressionType.Decrement)
                        realValue = -realValue;
    
                    updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue);
                }
                else if (node.Type == typeof(float))
                {
                    var realValue = (float)value;
                    if (node.NodeType == ExpressionType.Decrement)
                        realValue = -realValue;
    
                    updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue);
                }
                else
                {
                    throw new Exception(_fieldname + "不支持该类型操作");
                }
    
                UpdateDefinitionList.Add(updateDefinition);
    
                return node;
            }
            #endregion
    
            #region 访问数组表达式
    
            /// <summary>
            /// 访问数组表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitNewArray(NewArrayExpression node)
            {
                var listLambda = Expression.Lambda<Func<IList>>(node);
                var list = listLambda.Compile().Invoke();
                UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list));
    
                return node;
            }
    
            /// <summary>
            /// 访问集合表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitListInit(ListInitExpression node)
            {
                var listLambda = Expression.Lambda<Func<IList>>(node);
                var list = listLambda.Compile().Invoke();
                UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list));
    
                return node;
            }
            #endregion
    
            #region 访问常量表达式
    
            /// <summary>
            /// 访问常量表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitConstant(ConstantExpression node)
            {
                var value = node.Type.IsEnum ? (int)node.Value : node.Value;
    
                UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value));
    
                return node;
            }
            #endregion
    
            #region 访问成员表达式
    
            /// <summary>
            /// 访问成员表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitMember(MemberExpression node)
            {
                if (node.Type.GetInterfaces().Any(a => a.Name == "IList"))
                {
                    var lambda = Expression.Lambda<Func<IList>>(node);
                    var value = lambda.Compile().Invoke();
    
                    UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value));
                }
                else
                {
                    var lambda = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object)));
                    var value = lambda.Compile().Invoke();
    
                    if (node.Type.IsEnum)
                        value = (int)value;
    
                    UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value));
                }
    
                return node;
            }
            #endregion
        }
        #endregion
    View Code

    表达式树的解析

      对于Lambda表达式的封装,我侧重讲一下。假如有一段这样的更新代码:  

    new MongoDbService().Update<User>(a => a._id == "d99ce40d7a0b49768b74735b91f2aa75", a => new User
                {
                    AddressList = new List<string>
                    {
                        "number1",
                        "number2"
                    },
                    Age = 10,
                    BirthDateTime = DateTime.Now,
                    Name = "skychen",
                    NumList = new List<int>
                    {
                        1211,23344
                    },
                    Sex = Sex.Woman,
                    Son = new User
                    {
                        Name = "xiaochenpi",
                        Age = 1
                    }
                });

      那么,我们可以调试监视看看(下图),我们可以得出两个重要信息:

      1.Expression<Func<T, T>>解析出来Body的NodeType是MemberInit

      2.Bindings里有需要修改的字段信息。

      再调试进去看看Bindings的第一项,我们又可以了解了几个重要信息。

      1.Bindings里的元素是MemberAssignment类型。

      2.Member能取到Name属性,也就是字段名

      3.Expression属性,使用 Expression.Lambda,进行Compile().Invoke()就能得到我们需要的值。

      fileName和Value都能取到了,那么更新自然能解决了。

      上图是源码的部分核心代码,奇怪的是,我并没有在VisitMemberInit里进行遍历Bindings后进行Update.Set,而是将item的Expression属性再一次访问。那是因为我需要针对不同的数据类型进行处理。例如:

      常量,我可以定义一个object value进行去接收,如果遇到枚举我需要强转成整型。

      集合与数组,假如草率的使用object类型,object value = Expression.Lambda<Func<object>>(node).Compile().Invoke(),那么更新到MongoDB里就会有bug,奇怪的_t,_v就会出现。以此我需要定义为IList才能解决这个问题。

      此外,工作中还会遇到金额或者数量自增的情况。Amount = a.Amount+9.9M,Count =a.Count-1。 MongoDB.Driver提供了Builders<T>.Update.Inc方法,因此重写二元表达式进行封装。

    附加

      经过测试,官方驱动2.4.3和2.4.4版本对类型IList支持有问题,如下图,所以现在封装版本最高支持到2.4.2。

      

    结束

      不知道有多少朋友直接拖到文章尾部直接下载源码的。。。。。。

      如果对您有用,麻烦您推荐一下。

      此外还要感谢非非大哥哥,率先做了我的小白鼠给我提出了可贵的BUG,不然我还真不敢放出源码。

      如果有什么问题和建议,可以在下方评论,我会及时回复。

      双手奉上源码:https://github.com/SkyChenSky/Framework.MongoDB.git

  • 相关阅读:
    linq to sql的性能和reader相比只是差一点点吗
    Win11删除右键菜单open in windows Terminal
    jdk1.8
    mvcc read view
    javascript 跨域双向通信方案,通过postMessage和window.name实现
    [原创]如何加载动态库、获取方法委托、卸载动态库
    awseks创建与使用
    aiops 调研
    consul调研
    机器学习调研
  • 原文地址:https://www.cnblogs.com/skychen1218/p/6595759.html
Copyright © 2011-2022 走看看