标签:
第一部分 基本设计
目前最新版本的C#驱动MongoDB-CSharpDriver-2.2.3,比之前的版本更新比较大,在网上很难找到这个版本的相关C#操作资料,以下都是个人自发研究、测试的,如有雷同,不胜荣幸;如觉不妥,留言喷射;如有错误,还请赐教;如获帮助,示意欣赏。新版中有很多异步操作,本人对此没作研究,怕会产生数据安全问题,所以全部用的是同步方法。
1. 模型设计,使用GUID类型做为Id属性,在初始化时给一个随机值,基类代码,不作解释
using System;
using System.Collections.Generic;
public interface IEntityBase<T>
{
T Id { get; }
}
public abstract class AggregateBase:IEntityBase<Guid>
{
public Guid Id { get; private set; }
public AggregateBase()
{
Id = Guid.NewGuid();
}
}
2.设计MongoDb的数据上下文类 主要是几个静态方法 代码
using MongoDB.Driver;
using System;
namespace EFAndMongoRepostory
{
public class MongoDbContext
{
private static IMongoDatabase db;
/// <summary>
/// 设置并获取数据库
/// </summary>
/// <param name="urls">连接地址</param>
/// <param name="databaseName">数据库名称</param>
/// <returns></returns>
public static IMongoDatabase SetMongoDatabase(string urls,string databaseName)
{
MongoClient client = new MongoClient(urls);
db = client.GetDatabase(databaseName);
return client.GetDatabase(databaseName);
}
/// <summary>
/// 获取数据库中的集合(相当于表)
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
public static IMongoCollection<TEntity> GetMongoCollection<TEntity>(string name)
{
IsDbNull();//检测数据库是否存在
return db.GetCollection<TEntity>(name);
}
private static void IsDbNull()
{
if (db != null)
return;
throw new Exception("the mongodb is null,plese set it on method SetMongoDatabase");
}
/// <summary>
/// 删除集合
/// </summary>
/// <param name="name">集合名称</param>
public static void DropCollection(string name)
{
db.DropCollection(name);
}
}
}
3.Repostory类设计
1) 使用泛型约束,模拟事务控制,设计相关字段和方法。由于事务控制的是写入操作,我在这里借用MongoDB.Driver中的WriteModel<T>类,将所有写入操作记录在一个list集合中,只要没有发生回滚,并且list中有记录,就可以将所有的写入操作一次性提交,从而达到只与数据库交互一次的目的。具体字段作用看注释
using EFAndMongoRepostory.Entity;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace EFAndMongoRepostory
{
public class MongoRepostory<TAggregate> where TAggregate :AggregateBase
{
/// <summary>
/// 获取集合
/// </summary>
protected IMongoCollection<TAggregate> Collection;
/// <summary>
/// 初始化,以类名作为集合名称
/// </summary>
/// <param name="collection"></param>
public MongoRepostory()
{
this.Collection = MongoDbContext.GetMongoCollection<TAggregate>(typeof(TAggregate).Name);
}
private List<WriteModel<TAggregate>> writers = new List<WriteModel<TAggregate>>();//写入模型集合
/// <summary>
/// 指示是否起用事务,默认true
/// </summary>
public bool IsUseTransaction { get; set; } = true;
private bool isRollback = false;//回滚控制
#region 事务控制
public void Commit()
{
if (!isRollback && writers.Count > 0)//如果不回滚,并且writers有数据
Collection.BulkWrite(writers);
Rollback();
}
public void Rollback()
{
writers.Clear();//清空writers
}
#endregion
}
3)添加查询数据的方法,由于查询数据基本跟事务没什么关系,可以直接设计一些读数据的方法 代码
using EFAndMongoRepostory.Entity;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace EFAndMongoRepostory
{
public class MongoRepostory<TAggregate> where TAggregate :AggregateBase
{
/// <summary>
/// 获取集合
/// </summary>
protected IMongoCollection<TAggregate> Collection;
/// <summary>
/// 初始化,以类名作为集合名称
/// </summary>
/// <param name="collection"></param>
public MongoRepostory()
{
this.Collection = MongoDbContext.GetMongoCollection<TAggregate>(typeof(TAggregate).Name);
}
private List<WriteModel<TAggregate>> writers = new List<WriteModel<TAggregate>>();//写入模型集合
/// <summary>
/// 指示是否起用事务,默认true
/// </summary>
public bool IsUseTransaction { get; set; } = true;
private bool isRollback = false;//回滚控制
#region 事务控制
public void Commit()
{
if (!isRollback && writers.Count > 0)//如果不回滚,并且writers有数据
Collection.BulkWrite(writers);
Rollback();
}
public void Rollback()
{
writers.Clear();//清空writers
}
#endregion
#region 查询
/// <summary>
/// 查找所有数据集合
/// </summary>
/// <returns></returns>
public IQueryable<TAggregate> FindAll()
{
return Collection.AsQueryable();
}
/// <summary>
/// 根据Id查找一条数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TAggregate FindById(Guid id)
{
var find = Collection.Find(o => o.Id == id);
if (!find.Any())
return null;
return find.FirstOrDefault();
}
/// <summary>
/// 根据过滤条件找出符合条件的集合
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public List<TAggregate> FindByFilter(Expression<Func<TAggregate, bool>> filter)
{
var find = Collection.Find(filter);
if (!find.Any())
return null;
return find.ToList();
}
/// <summary>
/// 根据过滤条件找出一条数据
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public TAggregate FindOne(Expression<Func<TAggregate, bool>> filter)
{
return Collection.Find(filter).FirstOrDefault();
}
#endregion
}
4)在每个写入操作的方法中,添加事务控制逻辑,包括是否启用事务逻辑,只要有一个环节发生错误,在提交时便发生回滚,所有操作无效,并不更改数据库中原有的数据。
这里特别提一下的是update操作,这被这个方法困扰了好长好长的时间,企图能在调用端不用引入想关的DLL,只需传入基本的参数就能完成。初步结论是我能力有限,对于lambd表达式还是不够熟,无奈之下,找到一个replace方法代替,发现可用,并且好用,唯一不爽的,你并不能用new()出来的数据作为参数传递,因为你一new,id值就会变,而mongodb中应该也有对于特殊字段名id不能更改的限制。其实再仔细想想逻辑,也应该是不能改变ID值的,因为ID代表着一条数据 的标识,你改变了对于这个数据来说就没有意义了。最后的解决方案是,第一,在方法注释中标注,添加关于这个问题的自定义异常信息。第二,在Update方法注释中,添加提示信息和示例,以防后期忘记相关mongodb的API,两个方法都能用就行。
具体流程控制在部分方法中作了详细注释,发现如果有什么不理解或异议,请留言,将在第一时间作出答复。完整Repostory代码
using EFAndMongoRepostory.Entity;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace EFAndMongoRepostory
{
public class MongoRepostory<TAggregate> where TAggregate :AggregateBase
{
/// <summary>
/// 获取集合
/// </summary>
protected IMongoCollection<TAggregate> Collection;
/// <summary>
/// 初始化,以类名作为集合名称
/// </summary>
/// <param name="collection"></param>
public MongoRepostory()
{
this.Collection = MongoDbContext.GetMongoCollection<TAggregate>(typeof(TAggregate).Name);
}
private List<WriteModel<TAggregate>> writers = new List<WriteModel<TAggregate>>();//写入模型
/// <summary>
/// 指示是否起用事务,默认true
/// </summary>
public bool IsUseTransaction { get; set; } = true;
private bool isRollback = false;//回滚控制
#region 添加
/// <summary>
/// 添加一条数据
/// </summary>
/// <param name="entity"></param>
public void Add(TAggregate entity)
{
if (entity == null)
return;
if (IsUseTransaction)
{
try
{
writers.Add(new InsertOneModel<TAggregate>(entity));
isRollback = false;//控制是否回滚
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.InsertOne(entity);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 添加数据集合
/// </summary>
/// <param name="entities"></param>
public void Add(IEnumerable<TAggregate> entities)
{
if (entities.Count() <= 0)
return;
if(IsUseTransaction)
{
try
{
entities.ToList().ForEach(o =>
{
writers.Add(new InsertOneModel<TAggregate>(o));
});
isRollback = false;
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.InsertMany(entities);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
#endregion
#region 替换
/// <summary>
/// 替换一条过滤的数据(请确保此方法Id属性是不能变)
/// </summary>
/// <param name="filter">过滤条件</param>
/// <param name="enity">目标数据(目标数据的Id值必为源数据的Id)</param>
public void ReplaceOne(Expression<Func<TAggregate, bool>> filter, TAggregate enity)
{
if (enity == null)
return;
if (IsUseTransaction)
{
try
{
writers.Add(new ReplaceOneModel<TAggregate>(Builders<TAggregate>.Filter.Where(filter), enity));
isRollback = false;
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.ReplaceOne(filter, enity);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 替换一条数据(请确保此方法Id属性是不能变)
/// </summary>
/// <param name="id">目标id</param>
/// <param name="enity">目标数据(目标数据的Id值必为源数据的Id)</param>
public void ReplaceById(Guid id, TAggregate enity)
{
if (enity == null)
return;
if(enity.Id!=id)
{
isRollback = true;
throw new Exception("the id can not change");
}
if(IsUseTransaction)
{
try
{
writers.Add(new ReplaceOneModel<TAggregate>(Builders<TAggregate>.Filter.Eq(o=>o.Id, id), enity));
isRollback = false;
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.ReplaceOne(o => o.Id == id, enity);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 查找一条数据并且替换
/// </summary>
/// <param name="id">目标数据的id</param>
/// <param name="enity">更改后的数据</param>
/// <returns>更改前的数据</returns>
public TAggregate FindOneAndReplace(Guid id, TAggregate enity)
{
if (enity == null)
return null;
if (enity.Id != id)
{
throw new Exception("the id can not change");
}
return Collection.FindOneAndReplace(o => o.Id == id, enity);
}
/// <summary>
/// 查找一条数据并且替换
/// </summary>
/// <param name="filter">条件</param>
/// <param name="enity">更改后的数据</param>
/// <returns>更改前的数据</returns>
public TAggregate FindOneAndReplace(Expression<Func<TAggregate,bool>>filter, TAggregate enity)
{
if (enity == null)
return null;
return Collection.FindOneAndReplace(filter, enity);
}
#endregion
#region 移除
/// <summary>
/// 根据过滤删除数据
/// </summary>
/// <param name="filter"></param>
public void RemoeMany(Expression<Func<TAggregate, bool>> filter)
{
if (IsUseTransaction)
{
try
{
writers.Add(new DeleteOneModel<TAggregate>(filter));
isRollback = false;
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.DeleteMany(filter);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
public void RemoveById(Guid id)
{
if (IsUseTransaction)
{
try
{
writers.Add(new DeleteOneModel<TAggregate>(Builders<TAggregate>.Filter.Eq(o => o.Id, id)));
isRollback = false;
return;
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.DeleteOne(o => o.Id == id);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
#endregion
#region 更新
/// <summary>
/// 过滤数据,执行更新操作(如不便使用,请用Replace相关的方法代替)
///
/// 一般用replace来代替这个方法。其实这个功能还算强大的,可以很自由修改多个属性
/// 关健是set参数比较不好配置,并且如果用此方法,调用端必须引用相关的DLL,set举例如下
/// set = Builders<TAggregate>.Update.Update.Set(o => o.Number, 1).Set(o => o.Description, "002.thml");
/// set作用:将指定TAggregate类型的实例对象的Number属性值更改为1,Description属性值改为"002.thml"
/// 说明:Builders<TAggregate>.Update返回类型为UpdateDefinitionBuilder<TAggregate>,这个类有很多静态
/// 方法,Set()是其中一个,要求传入一个func的表达示,以指示当前要修改的,TAggregate类型中的属性类型,
/// 另一个参数就是这个属性的值。
///
/// Builders<TAggregate>类有很多属性,返回很多如UpdateDefinitionBuilder<TAggregate>的很有用帮助类型
/// 可以能参CSharpDriver-2.2.3.chm文件 下载MongoDB-CSharpDriver时带有些文件
/// 或从官网https://docs.mongodb.com/ecosystem/drivers/csharp/看看
///
/// </summary>
/// <param name="filter">过滤条件</param>
/// <param name="set">修改设置</param>
public void Update(Expression<Func<TAggregate, bool>> filter, UpdateDefinition<TAggregate> set)
{
if (set == null)
return;
if (IsUseTransaction)//如果启用事务
{
try
{
writers.Add(new UpdateManyModel<TAggregate>(filter, set));
isRollback = false;//不回滚
return;//不执行后继操作
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.UpdateMany(filter, set);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
/// <summary>
/// 过滤数据,执行更新操作(如不便使用,请用Replace相关的方法代替)
///
/// 一般用replace来代替这个方法。其实这个功能还算强大的,可以很自由修改多个属性
/// 关健是set参数比较不好配置,并且如果用此方法,调用端必须引用相关的DLL,set举例如下
/// set = Builders<TAggregate>.Update.Update.Set(o => o.Number, 1).Set(o => o.Description, "002.thml");
/// set作用:将指定TAggregate类型的实例对象的Number属性值更改为1,Description属性值改为"002.thml"
/// 说明:Builders<TAggregate>.Update返回类型为UpdateDefinitionBuilder<TAggregate>,这个类有很多静态
/// 方法,Set()是其中一个,要求传入一个func的表达示,以指示当前要修改的,TAggregate类型中的属性类型,
/// 另一个参数就是这个属性的值。
///
/// Builders<TAggregate>类有很多属性,返回很多如UpdateDefinitionBuilder<TAggregate>的很有用帮助类型
/// 可以能参CSharpDriver-2.2.3.chm文件 下载MongoDB-CSharpDriver时带有些文件
/// 或从官网https://docs.mongodb.com/ecosystem/drivers/csharp/看看
///
/// </summary>
/// <param name="id">找出指定的id数据</param>
/// <param name="set">修改设置</param>
public void Update(Guid id, UpdateDefinition<TAggregate> set)
{
if (set == null)
return;
if (IsUseTransaction)//如果启用事务
{
try
{
writers.Add(new UpdateManyModel<TAggregate>(Builders<TAggregate>.Filter.Eq(o => o.Id, id), set));
isRollback = false;//不回滚
return;//不执行后继操作
}
catch (Exception ex)
{
isRollback = true;
throw new Exception(ex.Message);
}
}
try
{
Collection.UpdateMany(o => o.Id == id, set);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
#endregion
#region 事务控制
public void Commit()
{
if (!isRollback && writers.Count > 0)//如果不回滚,并且writers有数据
Collection.BulkWrite(writers);
Rollback();
}
public void Rollback()
{
writers.Clear();//清空writers
}
#endregion
#region 查询
/// <summary>
/// 查找所有数据集合
/// </summary>
/// <returns></returns>
public IQueryable<TAggregate> FindAll()
{
return Collection.AsQueryable();
}
/// <summary>
/// 根据Id查找一条数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TAggregate FindById(Guid id)
{
var find = Collection.Find(o => o.Id == id);
if (!find.Any())
return null;
return find.FirstOrDefault();
}
/// <summary>
/// 根据过滤条件找出符合条件的集合
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public List<TAggregate> FindByFilter(Expression<Func<TAggregate, bool>> filter)
{
var find = Collection.Find(filter);
if (!find.Any())
return null;
return find.ToList();
}
/// <summary>
/// 根据过滤条件找出一条数据
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public TAggregate FindOne(Expression<Func<TAggregate, bool>> filter)
{
return Collection.Find(filter).FirstOrDefault();
}
#endregion
/// <summary>
/// 根据聚合类ID添加导航数据到 导航集合(中间表)
/// </summary>
/// <typeparam name="TNav">导航类</typeparam>
/// <param name="nav">提供参数时直接new一个具体的nav类就行了</param>
/// <param name="filter"></param>
/// <param name="foreignKey"></param>
public void AddByAggregate<TNav>(TNav nav, Expression<Func<TAggregate, bool>> filter, Guid foreignKey)
where TNav : NavgationBase
{
//导航类的集合
var navCollection = MongoDbContext.GetMongoCollection<TNav>(typeof(TNav).Name);
//遍历当前集合中所有符合条件的数据
Collection.Find(filter).ToList().ForEach(o =>
{
//将导航类的属性赋相应的值
nav.AggregateId = foreignKey;
nav.ValueObjectId = o.Id;
//插入到数据库
navCollection.InsertOne(nav);
});
}
}
}
5)截个图
6)Repostory类完成 是的,你可能在最后部分发现了一个额外的方法,这个方法是需要一个泛型参数,我理想的作用是用来配置集合(数据表)之关的多对多关系的,这也是仿EF中的通过一个中间表来实现,那么Model的设计就要改造一下了。另外我还有一个想法就是想设计专门针对聚合根中值类型的操作,但是这些还没有完成,时间太晚了,以后再慢慢搞起。可以看看现在model的设计,这里面没有分层,因我是在控制台下做测试的。另外添加了几个实现类,以备下次测试 ,没有注释,接口设计都还不完善,但是对事务部分的CURD没影响,现阶段model代码
using System;
using System.Collections.Generic;
namespace EFAndMongoRepostory.Entity
{
public interface IEntityBase<T>
{
T Id { get; }
}
public abstract class AggregateBase:IEntityBase<Guid>
{
public Guid Id { get; private set; }
public AggregateBase()
{
Id = Guid.NewGuid();
}
}
public interface IValueObject
{
Guid Id { get; }
}
public abstract class ValueObjectBase
{
public Guid Id { get;}
public ValueObjectBase()
{
Id = Guid.NewGuid();
}
}
public abstract class NavgationBase
{
public virtual Guid AggregateId { get; set; }
public virtual Guid ValueObjectId { get; set; }
}
public class User : AggregateBase
{
public string Name { get; set; }
public string Pwd { get; set; }
public int Number { get; set; }
public ICollection<Role> Roles { get; set; } = new List<Role>();
public ICollection<Permission> Permissions { get; set; } = new List<Permission>();
}
public class Role :AggregateBase, IValueObject
{
public int Number { get; set; }
public Guid UserGId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Permission> Permissions { get; set; } = new List<Permission>();
}
public class Permission:ValueObjectBase
{
public Guid UserId { get; set; }
public Guid RoleId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool HavePermission { get; set; } = true;
}
public class User_Role : NavgationBase
{
}
public class Role_Permission : NavgationBase
{
}
public class User_Permission : NavgationBase
{
}
}
4.客户端测试 这里用的是控制台,而没有写测试代码,感觉这样跟写单元测试也没多大的区别。当然了,还是应该写单元测试的,起码代码复用高。Repostory设计好了,调用就简单了,我已经做过了部分测试,所以代码都是注释状态,如果你也想做测试,一步步的释放注释试试。代码
using EFAndMongoRepostory;
using EFAndMongoRepostory.Entity;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MongoTest
{
class Program
{
static void Main(string[] args)
{
#region 初始化
//MongoClient client = new MongoClient("mongodb://localhost:27017"); //mongo客户端
//var db = client.GetDatabase("MongoTest"); //数据库
var db = MongoDbContext.SetMongoDatabase("mongodb://localhost:27017", "MongoTest");
#endregion
#region 准备数据
List<Role> rList = new List<Role>
{
new Role
{
Name="r001", Description="rd001"
},
new Role
{
Name="r002",Description="rd002"
},
new Role
{
Name="r003",Description="rd003"
}
};
List<User> uList = new List<User>
{
new User
{
Name="001", Pwd="pwd001"
},
new User
{
Name="002", Pwd="pwd002"
}
,
new User
{
Name="003", Pwd="pwd003"
}
,
new User
{
Name="004", Pwd="pwd004"
}
};
List<Permission> pList = new List<Permission>
{
new Permission { Name="001", Url="001.html" },
new Permission { Name="002", Url="002.html" },
new Permission { Name="003", Url="003.html" },
new Permission { Name="004", Url="004.html" },
new Permission { Name="005", Url="005.html" }
};
#endregion
var repostory = new MongoRepostory<User>();
//插入几条测试数据
//repostory.Add(uList);
//repostory.Commit();
//测试替换操作
//var user = repostory.FindOne(o => o.Name == "001");
//user.Pwd = "password001";
//repostory.ReplaceById(user.Id, user);
//repostory.Commit();
//Console.WriteLine(user.Name + ":" + user.Pwd);
//测试更新操作
//var set = Builders<User>.Update.Set(o => o.Pwd, "pwd001").Set(o => o.Name, "01111");
//repostory.Update(user.Id, set);
//repostory.Commit();
//Console.WriteLine(user.Name + ":" + user.Pwd);
//测试替换前后数据
//var user = repostory.FindOne(o => o.Name == "01111");
//user.Name = "001";
//user = repostory.FindOneAndReplace(user.Id, user);
//var u2 = repostory.FindById(user.Id);
//Console.WriteLine(user.Name + ":" + user.Pwd);
//Console.WriteLine("替换后的");
//Console.WriteLine(u2.Name + ":" + u2.Pwd);
var query = repostory.FindAll().ToList();
query.ForEach(o => {
Console.WriteLine(o.Name + ":" + o.Pwd);
});
Console.ReadKey();
}
}
}
5.关于门外汉和OADemo 之所以说是门外汉,是因为我没有从事过软件开发工作,都是业余时间学习的。而正是基于此,之前写的OADemo我也觉得应该放了,毕竟没做过真实项目,现在园子里有一个很火的关于mvc的精彩连续剧 ,专业,详细。如果有关注我之前的OADemo可以移步关注。我写这个的目的除了很少一部分是备忘,更大一部分是来自虚荣心,我也希望能得到关注,肯定。其实本来是想在Domain层完成权限块的,也不知怎么的就关注到MongoDB这来了,还真是不专心啊。
打住了,再发到首页去看看,能不能通过,已经两篇心血被下架了,还过不了就严重受打击了。
看门外汉如何实现:C#操作 MongoDB基本CURD的事务控制
标签:
原文地址:http://www.cnblogs.com/yuzeyong/p/5496822.html