C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架
C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架
如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongodb.com/drivers/csharp/)
看了看文档发现官方的驱动功能已经相当强大了并且更新速度很快

2.3之后得驱动版本已经支持 .Net 5,而且方法都已支持Task ,可以配合async , await.使用 ,同时也支持Lambda表达式及表达式树 官方是这么说的(https://mongodb.github.io/mongo-csharp-driver/2.12/what_is_new/)

官方得驱动如此强大了,还找什么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如下:

有一部分准备工作要做,那就是需要在配置文件添加一个数据库连接的配置,用于连接数据库;

接下实现我们的管理器,这一部分是核心,实现了类与数据库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";
}
}
}

简易版本的功能基本都实现,实际上,一个成熟的ORM框架还有好多工作要做
源码链接:https://pan.baidu.com/s/1dE2UgR4EXyIPQNmR0Q6Efw
提取码:s9k5
以上代码为本人原创,如有错误之处,望大家不吝赐教,感谢(抱拳~)
C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架的更多相关文章
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 一
本人系新接触MongoDB不久,属于MongoDB的菜鸟范畴.在使用MongoDB的过程中,总结了一些认识,在此总结跟大家分享.欢迎拍砖. 关于MongoDB的内容,在此就不做介绍了,网上有浩如烟海的 ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 三
本次改动的主要内容是实现MongoDB.Repository在MongoDB中建立索引. 建立索引主要使用MongoDB的官方驱动中EnsureIndex方法. 在MongoDB.Repository ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 二
本次改动的主要内容是实现MongoDB.Repository对MongoDBRef的支持. MongoDB对一对一,一对多,多对多关系的维护,官方推荐文档嵌入方式,反映到模型的设计如下: public ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 六:支持多数据库操作
本次主要内容:修正MongoDB.Repository框架对多数据库的支持. 在之前的五篇文章中对MongoDB.Repository框架做了简单的介绍是实现思路.之前是考虑MongoDB.Repos ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 五 --- 为List<MongoDBRef>增加扩展方法
本次改动主要内容:为List<MongoDBRef>增加扩展方法 在MongoDB.Repository的使用过程中,发现在一个类中只定义一个List<MongoDBRef>是 ...
- MongoDB:利用官方驱动改装为EF代码风格的MongoDB.Repository框架 四
本次改动主要实现MongoGridFS功能.实现方式主要使用了MongoGridFS和MongoGridFSFileInfo两个类. 设计思路:定义一个IMongoFile接口并继承IEntity,以 ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- Golang:手撸一个支持六种级别的日志库
Golang标准日志库提供的日志输出方法有Print.Fatal.Panic等,没有常见的Debug.Info.Error等日志级别,用起来不太顺手.这篇文章就来手撸一个自己的日志库,可以记录不同级别 ...
随机推荐
- Codeforces Round #575 (Div. 3) D2. RGB Substring (hard version) 【递推】
一.题目 D2. RGB Substring (hard version) 二.分析 思路一开始就想的对的,但是,用memset给数组初始化为0超时了!超时了! 然后我按照题解改了个vector初始化 ...
- python-给一个参数n,例如3:先输出1,2,3,4,5,6,7,8,9,每三个数后换行,后输出1,4,7,2,5,8,3,6,9
""" 2 定义一个函数,fn(n)其中n表示输入n行n列的矩阵,需要满足的要求是在n为 3时先输出 3 1 2 3 4 4 5 6 5 7 8 9 6 后输出 7 1 ...
- P1036_选数(JAVA语言)
题目描述 已知 n 个整数x1,x2,-,xn,以及1个整数k(k<n).从n个整数中任选k个整数相加,可分别得到一系列的和.例如当n=4,k=3,4个整数分别为3,7,12,19时,可得 ...
- ASPOSE.Cells & ASPOSE.Words 操纵Excel和Word文档的 .NET Core 实例
Aspose.Total是Aspose公司旗下的最全的一套office文档管理方案,它提供的原生API可以对Word.Excel.PDF.Powerpoint.Outlook.CAD.图片.3D.ZI ...
- 在Ubuntu上安装TensorFlow-GPU开发环境
深度学习是一个比较复杂的体系,今天记录一下开发环境的搭建步骤. 全新安装Ubuntu 20.10,系统默认安装的是python3,查看python的版本: mango@ubuntu:~$ python ...
- 文件查找工具 find 详解(附:生产示例)
1. 文件查找:在文件系统上查找符合条件的文件 命令 解释 which 查看可执行文件的位置,只能寻找执行文件,并在PATH变量里面寻找 whereis 查看文件的位置:只能查二进制文件,说明文档,源 ...
- Jmeter(四十一) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 - 下篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- day-03-基础数据类型
基础数类型总览 10203 123 3340 int +- * / 等等 '今天吃了没?' str 存储少量的数据,+ *int 切片, 其他操作方法 True False bool 判断真假 [12 ...
- python基础(补充):python三大器之生成器
生成器的定义 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后 ...
- H5 离线存储-使用 serverWorker 实现
H5 离线存储-使用 serverWorker 实现 离线存储所用技术 serverWorker serviceWorker 生命周期 install 事件会在 serviceWorker 注册成功时 ...