基于MongoDB官方驱动封装ORM

因新项目框架升级为 .Net 5.0,原Framework的MongoDB的ORM,不再适用,且旧ORM使用不便,写查询还需要编写BosnDocument,不易于理解,便有了更新ORM的想法。

于是翻看了一下MongoDB的官方文档,发现官方驱动功能本身已经非常强大,且更新迅速,从2.3版本之后就已经支持 .Net 5.0,方法都已支持Task ,可以配合async , await.使用 ,同时也支持Lambda表达式及表达式树。所以便有了创建一个简易版基于MongoDB官方驱动的ORM的想法。.

基于MongoDB官方驱动封装ORM
1677984339841.jpg

设计思路

  1. 对象实体基类

    为什么要创建实体对象基类?是因为官方驱动支持的实体类与Collection得映射,必须要有id字段,对应数据库中得"_id",并且这个字段是ObjectIDl类型,像这样:

publicclassPerson
    {
        [BsonId]
        [BsonElement("_id")]
        public ObjectId ID { get; set; }
    }
  1. 实现实体类与Collection得自动映射 自动创建数据库连接

    我们需要自定义一个Attribute,用于获取获取集合名称,然后创建一个管理器实现一些自动映射的初始化操作;

  2. 实现Repository仓储类.提供简单得CRUD方法

    通过封装直接调用官方的驱动提供的API,实现CURD操作;

具体实现

创建对象实体基类

    [DataContract]
    [Serializable]
    [BsonIgnoreExtraElements(Inherited = true)]  //当BSON文档被反序列化时,每个元素的名称用于在类映射中查找匹配的成员。通常,如果没有找到匹配的成员,将抛出异常。如果要在反序列化期间忽略其他元素 使用这个特性
    publicabstractclassMongoEntityBase : IMongoEntityBase<string>
    {
        protected MongoEntityBase()
        {
            DB_ID = ObjectId.GenerateNewId().ToString();  //对id进行初始化
        }

        [DataMember]     
        [BsonElement("_id")]
        [BsonRepresentation(BsonType.ObjectId)]  //因为 ObjectId 这个结构体是不能序列化的,所以使用  [BsonRepresentation(BsonType.ObjectId)] 标记为这个字符串ID在mongo中代表ObjectId
        publicvirtualstring DB_ID { get; set; }        
    }

    publicinterfaceIMongoEntityBase<TKey>
    {
        [BsonId]
        TKey DB_ID { get; set; }
    }
    publicinterfaceIMongoEntityBase : IMongoEntityBase<string>
    {
    }

实现实体类与Collection的自动映射

  1. 我们需要先创建一个Attribute类,用于标记实体类来获取实体类对应的集合名称,如下:
    [AttributeUsage(AttributeTargets.Class, Inherited = true)]
    publicclassCollectionNameAttribute : Attribute
    {
        public CollectionNameAttribute(string name)

        {
            if (string.IsNullOrEmpty(name)) thrownew ArgumentException("Empty collectionname not allowed", "name");

            this.Name = name;
        }

        publicstring Name { get; privateset; } //定义一个属性 用于获取Collection名称
    }
  1. App.Config中添加配置信息,用于连接数据库
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="MongoServerSettings" connectionString="mongodb://localhost/Person" />
  </connectionStrings>
</configuration>
  1. 实现一个管理器,用于自动映射

:::tip{title="Tip"} 数据库连接的自动映射,官方驱动其实已经提供了实体类的自动映射

基于MongoDB官方驱动封装ORM:::

internalclassGlobleManage<T>
    {
        publicreadonlystatic GlobleManage<T> Instance = new GlobleManage<T>();

        privatestring _tableName;
        
        privatestring _dateBaseName;
        
        privatestring _mongoServerSettings;
        
        private IMongoCollection<T> _mongoCollection;

        public IMongoCollection<T> MongoCollection
        {
            get => _mongoCollection;

        }
        publicstring DateBaseName
        {
            get => _dateBaseName;
        }

        publicstring MongoServerSettings
        {
            get => _mongoServerSettings;
        }
        publicstring TableName
        {
            get => _tableName;
            set => _tableName = value;
        }

        internal GlobleManage()
        {
            Init();
        }

        private void Init()
        {
            try
            {
                //初始化连接字符串
                string[] parm = ConfigurationManager.ConnectionStrings["MongoServerSettings"].ConnectionString.Split('/');

                _dateBaseName = parm.Last();
                
                _mongoServerSettings = ConfigurationManager.ConnectionStrings["MongoServerSettings"].ConnectionString.Replace(@"/" + _dateBaseName, ":27017");


                //根据实体类标注好的Attribute获取表名
                var entitytype = typeof(T);
                
                var attr = Attribute.GetCustomAttribute(entitytype, typeof(CollectionNameAttribute));
                //若Attribute不为空  获取标注的表名
                if (attr != null)
                {
                    _tableName = ((CollectionNameAttribute)attr).Name;
                }
                else
                {
                    //否则  如果类型是MongoEntityBase的派生类 获取类名作为表名
                    if (typeof(MongoEntityBase).IsAssignableFrom(entitytype))
                    {
                        // No attribute found, get the basetype
                        while (!entitytype.BaseType.Equals(typeof(MongoEntityBase)))
                        {
                            entitytype = entitytype.BaseType;
                        }
                    }
                    _tableName = entitytype.Name;
                }
                //添加实体类映射
                if(!BsonClassMap.IsClassMapRegistered(entitytype))
                    BsonClassMap.RegisterClassMap<T>(cm => cm.AutoMap());

                _mongoCollection = new MongoClient(_mongoServerSettings).GetDatabase(_dateBaseName).GetCollection<T>(_tableName);
            }
            catch (Exception ex)
            {
                _mongoCollection = default;
            }
        }

        public IMongoCollection<T> GetConnection(string tableStr)
        {
            returnnew MongoClient(_mongoServerSettings).GetDatabase(_dateBaseName).GetCollection<T>(tableStr);
        }
    }

实现Repository仓储类.提供简单的CRUD方法

  1. 创建仓储类的泛型接口
publicinterfaceIRepository<T> whereT : IMongoEntityBase<string>
    {
        IMongoCollection<T> Collection { get; }

        event Action<object, Exception> ExecutedExceptionEventHandler;
        
        bool Add(T entity);
        
        bool Delete(T delete, Expression<Func<T, bool>> conditions = null);
        
        bool Update(T update, Expression<Func<T, bool>> conditions = null);
        
        List<T> Find(Expression<Func<T, bool>> conditions = null);
    }
  1. 泛型仓储类实现接口,通过管理器获取自动映射得到的 IMongoCollection
publicclassRepository<T> : IRepository<T> whereT : IMongoEntityBase<string>
    {
        privatereadonly IMongoCollection<T> _mongoCollection;
        
        public IMongoCollection<T> Collection => _mongoCollection;
        
        publicevent Action<object, Exception> ExecutedExceptionEventHandler;
        
        public Repository()
        {
            _mongoCollection = GlobleManage<T>.Instance.MongoCollection;
        }
        
        public Repository(string tableStr)
        {
            _mongoCollection = GlobleManage<T>.Instance.GetConnection(tableStr);
        }

        public bool Add(T entity)
        {
            try
            {
                _mongoCollection.InsertOne(entity);
                returntrue;
            }
            catch (Exception ex)
            {
                ExecutedExceptionEventHandler?.Invoke($"ExecutedException Add({typeof(T).FullName})", ex);
                throw ex;

            }
        }

        public bool Delete(T delete, Expression<Func<T, bool>> conditions = null)
        {
            try
            {
                string _id = string.Empty;
                if (conditions == null)
                {
                    foreach (var item in delete.GetType().GetProperties())
                    {
                        if (item.Name == "DB_ID" && item.GetValue(delete) != null)
                        {
                            _id = item.GetValue(delete).ToString();
                            var result = _mongoCollection.DeleteOne(new BsonDocument("_id", BsonValue.Create(new ObjectId(_id))));
                            return result.IsAcknowledged;
                        }
                    }
                }
                var res = _mongoCollection.DeleteOne(conditions);
                return res.IsAcknowledged;
            }
            catch (Exception ex)
            {
                ExecutedExceptionEventHandler?.Invoke($"ExecutedException Delete({typeof(T).FullName})", ex);
                throw ex;
            }
        }

        public bool Update(T update, Expression<Func<T, bool>> conditions = null)
        {
            try
            {
                ObjectId _id;
                var options = new ReplaceOptions() { IsUpsert = true };
                if (conditions == null)
                {
                    foreach (var item in update?.GetType().GetProperties())
                    {
                        if (item.Name == "DB_ID" && item.GetValue(update) != null)
                        {
                            _id = new ObjectId(item.GetValue(update).ToString());
                            var result = _mongoCollection.ReplaceOne(new BsonDocument("_id", BsonValue.Create(_id)), update, options);
                            return result.IsAcknowledged;
                        }
                    }
                }
                var res = _mongoCollection.ReplaceOne(conditions, update, options);
                return res.IsAcknowledged;
            }
            catch (Exception ex)
            {
                ExecutedExceptionEventHandler?.Invoke($"ExecutedException Update({typeof(T).FullName},{conditions})", ex);
                throw ex;
            }
        }

        public List<T> Find(Expression<Func<T, bool>> conditions = null)
        {
            try
            {
                if (conditions == null)
                {
                    conditions = t => true;
                }
                return _mongoCollection.Find(conditions).ToList() ?? new List<T>();
            }
            catch (Exception ex)
            {
                ExecutedExceptionEventHandler?.Invoke($"ExecutedException Find({typeof(T).FullName},{conditions})", ex);
                throw ex;
            }
        }
    }

至此,一个简易版基于MongoDB官方驱动的ORM就完成了。

示例代码

核心调用:

 IRepository<Person> _IRepository = new Repository<Person>();

创建一个实体类,并且继承 MongoEntityBase

    [Serializable]
    publicclassPerson : MongoEntityBase
    {
        [BsonConstructor]
        public Person(string name, int age, EnumGender gender)
        {
            Name = name;
            Age = age;
            Gender = gender;
        }
    }
    
    publicenum EnumGender
    {
        男,
        女
    }

使用

static void Main(string[] args)
    {
        IRepository<Person> _IRepository = new Repository<Person>()
        
        //...do something
        Person person = new Person("张三", 8, EnumGender.男);
        _IRepository.Add(person);
        
        //...do something
        var personList = _IRepository.Find(t => t.Name.Equals("张三"));
        
        //...do something
        var p = _IRepository.Find(t => t.Name.Equals("张三")).FirstOrDefault();
        if(p != null)
        {
            p.Name = "李四";
            var res = _IRepository.Update(p);
        }
        
        //...do something
        var p1 = _IRepository.Find(t => t.Name.Equals("李四")).FirstOrDefault();
        if(p1 != null)
        {
            var res = _IRepository.Delete(p1);
        }
        
    }