C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

Peter`Pan 發表於 2021-05-14

C#基於Mongo的官方驅動手擼一個簡易版MongoDB-ORM框架

  如題,在GitHub上找了一圈想找一個MongoDB的的ORM框架,未償所願,就去翻了翻官網(https://docs.mongodb.com/drivers/csharp/)

看了看文件發現官方的驅動功能已經相當強大了並且更新速度很快

  C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

 

 

    2.3之後得驅動版本已經支援 .Net 5,而且方法都已支援Task ,可以配合async , await.使用 ,同時也支援Lambda表示式及表示式樹 官方是這麼說的(https://mongodb.github.io/mongo-csharp-driver/2.12/what_is_new/)

 

  C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

 

 

    官方得驅動如此強大了,還找什麼ORM框架,我們自己基於官方驅動手擼一個簡易版的,首先簡單講一下設計思路

    要求1:首先要有一個物件實體基類,為什麼要建立實體物件基類?是因為官方驅動支援的實體類與Collection得對映 必須要有id欄位,對應資料庫中得"_id",並且這個欄位是ObjectIDl型別,像這樣

    public class Person
    {
        [BsonId]
        [BsonElement("_id")]
        public ObjectId ID { get; set; }
    }

所以建立實體基類是為了免去每個實體類都要建立這個id的冗餘程式碼.

    要求2:實現實體類與Collection得自動對映 自動建立資料庫連線.這一部分實現就稍微複雜一些,首先我們需要自定義一個Attribute,用於獲取獲取集合名稱,然後建立一個管理器實現一些自動對映的初始化操作

    要求3:實現Repository倉儲類.提供簡單得CRUD方法. 這一部分就比較簡單了,通過封裝直接呼叫官方的驅動提供的API,實現CURD操作

    開始實現之前記得新增一下官方的驅動包直接在Nuget搜尋MongoDB.Driver 安裝就可以了 ,我這裡使用的是2.12.3版本

第一步:建立物件實體基類

 

   [DataContract]
    [Serializable]
    [BsonIgnoreExtraElements(Inherited = true)]  //當BSON文件被反序列化時,每個元素的名稱用於在類對映中查詢匹配的成員。通常,如果沒有找到匹配的成員,將丟擲異常。如果要在反序列化期間忽略其他元素 使用這個特性
    public abstract class MongoEntityBase : IMongoEntityBase<string>
    {
        protected MongoEntityBase()
        {
            DB_ID = ObjectId.GenerateNewId().ToString();  //對id進行初始化
        }

        [DataMember]
     [BsonElement("_id")] [BsonRepresentation(BsonType.ObjectId)]
//因為 ObjectId 這個結構體是不能序列化的,所以使用 [BsonRepresentation(BsonType.ObjectId)] 標記為這個字串ID在mongo中代表ObjectId public virtual string DB_ID { get; set; } } public interface IMongoEntityBase<TKey> { [BsonId] TKey DB_ID { get; set; } } public interface IMongoEntityBase : IMongoEntityBase<string> { }

 

 

 

第二步:實現實體類與Collection的自動對映;

  我們需要先建立一個Attribute類,用於標記實體類來獲取實體類對應的集合名稱,如下:

 

    [AttributeUsage(AttributeTargets.Class, Inherited = true)]
    public class CollectionNameAttribute : Attribute
    {
        public CollectionNameAttribute(string name)

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

            this.Name = name;
        }

        public string Name { get; private set; } //定義一個屬性 用於獲取Collection名稱
    }

 

  接下來實現一個管理器,用於自動對映,資料庫連線的自動對映,官方驅動其實已經提供了實體類的自動對映,我們只需要接著稍微封裝一下,官方自動對映demo如下:

   C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

 

  有一部分準備工作要做,那就是需要在配置檔案新增一個資料庫連線的配置,用於連線資料庫;

   C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

 

  接下實現我們的管理器,這一部分是核心,實現了類與資料庫Collection的自動對映,並自動建立出了mongo連線

 internal static class GlobleManage<T>
    {
        private static string _tableName;
        private static string _dateBaseName;
        private static string _mongoServerSettings;
        private static IMongoCollection<T> _mongoCollection;

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

        }
        public static string DateBaseName
        {
            get => _dateBaseName;
        }

        public static string MongoServerSettings
        {
            get => _mongoServerSettings;
        }
        public static string TableName
        {
            get => _tableName;
        }

        static GlobleManage()
        {
            Init();
        }

        private static void Init()
        {
            //初始化連線字串
            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;
            }

            //新增實體類對映
            BsonClassMap.RegisterClassMap<T>(cm => cm.AutoMap());

        
            _mongoCollection = new MongoClient(_mongoServerSettings).GetDatabase(_dateBaseName).GetCollection<T>(_tableName);
        }
    }

第三步:實現Repository倉儲類.提供簡單的CRUD方法

   首先,先建立倉儲類的泛型介面 

    public interface IRepository<T> where T : IMongoEntityBase<string>
    {
        IMongoCollection<T> Collection { get; }

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

  泛型倉儲類實現介面,通過管理器獲取自動對映得到的 IMongoCollection

public class Repository<T> : IRepository<T> where T : IMongoEntityBase<string>
    {

        private IMongoCollection<T> _mongoCollection = GlobleManage<T>.MongoCollection;
        public IMongoCollection<T> Collection => _mongoCollection;

        public bool Add(T entity)
        {
            try
            {
                _mongoCollection.InsertOne(entity);
                return true;
            }
            catch (Exception)
            {
                throw;
            }

        }
        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)
            {
                throw;
            }
        }

        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)
            {

                throw;
            }
        }

        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)
            {
                throw;
            }
        }
    }

 

簡易版的ORM框架就算是基本完成,接下來使用這個框架完成一些CRUD操作

首先,建立一個實體類,並且繼承 MongoEntityBase

    [Serializable]
    public class Person : MongoEntityBase
    {
        [BsonConstructor]
        public Person(string name, int age, string guid, EnumGender gender)
        {

            Name = name;
            Age = age;
            Guid = guid;
            Gender = gender;
        }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Guid { get; set; }
        public EnumGender Gender { get; set; }
        public List<Person> Students { get => students; set => students = value; }
        public Pet Pet { get => pet; set => pet = value; }

        private Pet pet;

        public override string ToString()
        {
            return "DB_ID:" + this.DB_ID + "  " + "user:" + Name + "  " + "age:" + Age + "  " + "guid:" + Guid + "  " + "Gender:" + Gender.ToString() + "  " + "寵物叫" + Pet.Name + "," + Pet.Age + "歲了";
        }
        private List<Person> students;

    }
    public enum EnumGender
    {
        男,
        女
    }

    public class Pet
    {
        private string name;
        private int age;

        public string Name { get => name; set => name = value; }
        public int Age { get => age; set => age = value; }
    }

然後建立一個窗體 測試一下我們的CRUD功能,呼叫很簡單 只需要一句  IRepository<Person> _IRepository = new Repository<Person>();

 public partial class Form1 : Form
    {
        private IRepository<Person> _IRepository = new Repository<Person>();
        private Random random = new Random();
        public Form1()
        {
            InitializeComponent();
        }

        //ADD
        private void button1_Click(object sender, EventArgs e)
        {
            Person person = new Person("張三", 8, Guid.NewGuid().ToString(), EnumGender.男);
            person.Students = new List<Person>() { new Person("張小三1", 8, Guid.NewGuid().ToString(), EnumGender.男),
                new Person("張小三2", 8, Guid.NewGuid().ToString(), EnumGender.男)
                ,new Person("張小三3", 8, Guid.NewGuid().ToString(), EnumGender.男)
                ,new Person("張小三4", 8, Guid.NewGuid().ToString(), EnumGender.男)};
            person.Pet = new Pet() { Name = "旺財", Age = 3 };
            _IRepository.Add(person);
            richTextBox1.Text += "新增成功!\r\n";
        }
        //Find
        private void button2_Click(object sender, EventArgs e)
        {
            var id = textBox1.Text.Trim();
            var list = _IRepository.Find(t => t.DB_ID.Equals(id));
            richTextBox1.Text += "Find成功:" + "\r\n ";
            foreach (var item in list)
            {
                richTextBox1.Text += item.ToString() + "\r\n ";
            }
        }

        //Delete
        private void button3_Click(object sender, EventArgs e)
        {
            var id = textBox1.Text.Trim();
            //var res = _IRepository.Delete(t => t.DB_ID.Equals(id));
            var rese = _IRepository.Find(t => t.DB_ID.Equals(id)).FirstOrDefault();
            var res = _IRepository.Delete(rese);
            richTextBox1.Text += id + "刪除:" + res;/*res.IsAcknowledged + res.DeletedCount;*/
        }
        //Update
        private void button4_Click(object sender, EventArgs e)
        {
            var guid = textBox1.Text.Trim();
            Person person = _IRepository.Find(t => t.DB_ID.Equals(guid)).FirstOrDefault();
            person.Name = "改過之後的名字" + random.Next(1, 10);
            var res = _IRepository.Update(person);
            richTextBox1.Text += guid + "更新:" + res;

        }
        //Clear
        private void button5_Click(object sender, EventArgs e)
        {
            textBox1.Clear();
            richTextBox1.Clear();
        }

        //FindAll
        private void button6_Click(object sender, EventArgs e)
        {
            var list = _IRepository.Find();
            richTextBox1.Text += "FindAll成功:" + "\r\n ";
            foreach (var item in list)
            {
                richTextBox1.Text += item.ToString() + "\r\n";
            }
        }
    }

 

C#基於Mongo的官方驅動手擼一個Super簡易版MongoDB-ORM框架

 

 

 

 簡易版本的功能基本都實現,實際上,一個成熟的ORM框架還有好多工作要做

 

原始碼連結:https://pan.baidu.com/s/1dE2UgR4EXyIPQNmR0Q6Efw
提取碼:s9k5

以上程式碼為本人原創,如有錯誤之處,望大家不吝賜教,感謝(抱拳~)