谈谈MongoDB的三层操作

NOSQL近来势头不错,MongoDB更是其中的娇娇者,自己学NoSQL的时候也是参考了大量的资料,最终决定要从MongoDB入手的,最重要的原因有两点:1自己是简单的爱好者,一切问题我都在想是否有简单的方法解决,宁可停下来去思考大量时间,也不愿用笨方法马上去做,而MongoDB的操作大都很简单,2自己是JS的爱好者,没事就喜欢拿一本js的本从头到尾看一边,也不管记住多少,也不管用不用得到,就是喜欢,MongoDB以BSON格式存储,所以操作也起来也算得心应手!现在做一个项目正是用MongoDB做为数据库的,一开始没有DAL,BLL直接访问数据库,然后就到UI了,而且BLL是全静态的(我喜欢静态方法的调用简单,但狠静态类的不能继承!),当时考虑的是用MongoDB的驱动去操作太直白了!感觉没必要再写个DAL!,后来知道我想法还是很天真的,哈哈!下面就说现在的操作方式吧~

一、Module层

[Serializable]

public sealed class user

{

public ObjectId id;

public string n;

public int age;

public Birthday birth;

public sealed class Birthday

{

public int y;

public int m;

public int d;

}

}

咋一看,有几个地方不规范,1类名首字母和公开字段没有大写,2公开的字段,而没有用属性,3字段命名没表达它的意思。现在逐个解释一下,类名和字段没大写首字母主要是数据库里的命名是遵循js的,用js表示时大家一般会这样写:

var user={id:ObjectId("123456"),n:"loogn",age:23,birth:{y:1989,m:7,d:7}}

然而,可能有人会说可以用MongoDB.Bson.Serialization.Attributes.BsonElement这样一个Attribute关联呢!不过我不会那样做,原因就是太麻烦了!这里可能还是有疑问,留到第3个问题说。

公开字段而没有用属性也是不合常理的,学校老师都交过,不管你能不能理解,反正就是要私有化字段,想公开请用属性,哈哈!不过我想了很久还是不走寻常路了,目前为止用字段没有出现过缺陷问题,我不保证以后不会,但我感觉几率十分小,就算真的有什么问题必需要用属性,后面加上{get;set;}也不麻烦吧!属性毕竟还是方法,用属性有多余的方法调用开销,而且实体类本来就是不寻常的类,一般只表示对象状态(用字段),属性里如果有逻辑(就像老师常说的年龄不能小于0且不能大于150等等!),你会放到这里做吗?显然你都是放在BLL里做!字段命名太短了没有表达它的意思,其实这个可以和第一个一起来说,MongoDB是无模式的,同一个合集可以保多个不规则的文档,如user集合:

{id:1,n:"user1",desc:"我的描述"}

{id:2,n:"user2"}

所以数据库必须保存文档的每一个元素的name(即id,name,desc,id,name),所以元素name越短越节省空间,本来是用name更能表达的,这里用了n,其实只要把常用的约定一下,绝大部分人都是可以接受的。

在user里还有个内嵌类Birthday,而这个类大写了首字母,我是这样考虑的,内嵌类名全部按C#命名规范,因为容器类有一个该内嵌类类型的字段,这里是birth,但如果找不到合适的缩写怎么办呢,直接小写内嵌类名就可以了,如内嵌城市类City,字段名为city就不会重复了。

二、DAL层

在这一层要写一个基类,完成这个基类后,其他的各各DAL类都是浮云了~,在写基类之前有一个MongoHelper,MongoHelper很简单,直接给出代码且不写解释:

MongoServer

完了后就可以写BaseDAL了,如果没有泛型,DAL的工作还真是索然无味,但现在用泛型DAL的工作有趣多了,先承上代码:

/// <summary>

/// 数据访问层基类

/// </summary>

/// <typeparam name="T">文档实体类</typeparam>

public abstract class BaseDAL<TDocument>

{

protected internal string CollectionName {  set; get; }

/// <summary>

/// 设置集合名

/// </summary>

protected abstract string SetCollectionName();

private MongoCollection<TDocument> m_collection;

/// <summary>

/// 根据CollectionName得到MongoCollection对象

/// </summary>

protected internal MongoCollection<TDocument> Collection

{

get

{

if (m_collection == null)

{

CollectionName = SetCollectionName();

m_collection = MongoHelper.GetDatabase().GetCollection<TDocument>(CollectionName);

}

return m_collection;

}

}

/// <summary>

/// 根据query条件得到一个文档对象

/// </summary>

/// <param name="query">查询条件</param>

/// <param name="preprocess">预处理方法</param>

/// <returns></returns>

public TDocument FindOne(IMongoQuery query, PreprocessHandler<TDocument> preprocess)

{

var document = Collection.FindOne(query);

if (preprocess != null)

{

preprocess(ref document);

}

return document;

}

/// <summary>

/// 把MongoCursor转换成IList类型

/// </summary>

/// <param name="cursor">文档游标</param>

/// <param name="preprocess">预处理方法</param>

/// <returns></returns>

protected internal IList<TDocument> CursorToList(MongoCursor<TDocument> cursor, PreprocessHandler<TDocument> preprocess)

{

IList<TDocument> list = new List<TDocument>(30);

bool isPreprocess = preprocess != null;

foreach (TDocument document in cursor)

{

var doc = document;

if (isPreprocess)

preprocess(ref doc);

list.Add(doc);

}

return list;

}

/// <summary>

/// 根据query查询集合

/// </summary>

/// <param name="query">条件</param>

/// <param name="preprocess">预处理方法</param>

/// <returns></returns>

public IList<TDocument> Find(IMongoQuery query, MongoCursorSettings cursorSettings, PreprocessHandler<TDocument> preprocess)

{

var cursor = Collection.Find(query);

if (cursorSettings != null)

{

cursorSettings.Set(cursor);

}

var list = CursorToList(cursor, preprocess);

return list;

}

}

最上面的代码就是设置操作哪个集合,这里有一点感觉不爽,为什么属性的get和set不能分别为抽象的呢?!虽然可以把整个属性标记为abstract,但在实现类中也要写get和set的实现(set可以是空代码块),所以这里回归原本用了一个SetCollectionName的抽象方法让子类去设置自己对应的集合名。

当你得到MongoCollection对象,特别是MongoCollection<TDocument>这样的强类型对象,BaseDAL剩下的工作也成浮云了!(都是对驱动方法的封装和个性化处理),如FindOne方法,用到一个委托:

public delegate void PreprocessHandler<T>(ref T document);

有很多这样的情况,得到一个实体后总是要先处理一下才可被UI方便的使用,如用户头像为空时,给个默认的,PreprocessHandler就是给BLL处理留个方便的接口啦!

这里选择委托而不是其他的元素使程序更灵活(有匿名委托嘛,I like it!),大家注意到了吧,实体类是按引用传递的,其实这里有个坑,我跳进去了,但又爬上来了!然后这里立了个牌:"此处有坑,请绕道而行",以免匆忙赶路的你也栽个跟头儿。

有这样一个情况,如果你把一个null实体对象传入处理方法,又在处理方法里判断如果是null就实体化,这样是得不到预期效果的,此null非彼null呀,实体化后方法里的型参是指向新对象了,但传过来的实参还是指向null呢,这不是我们想要的,用ref便可以解决了,也许你会说可以把实体对象返回呀,是的,但个人不喜欢那种写法,那样处理方法最后还要写reurn代码,调用方法可能还得写代码接收,麻烦!

FindOne例子完了,但FindOne远远没完,你可以做各种你喜欢的重载。

再看Find得到多个文档的方法,这里我选择IList<实体>做为返回值,在BLL决不去操作MongoCursor,但有这样一个问题,设置Fields、Limit、排序等都是在MongoCursor上操作的呀,而且这些操作很可能都是从UI传过来的,所以这里用了一个MongoCursorSettings类封装了这些设置:

MongoCursorSettings

代码不用解释,你可以扩展此类做更多设置而不用修改Find方法。

CursorToList方法也很简单,其实把PreprocessHandler写在DAL层的原因就是这个方法啦,心细的你肯定发现了把PreprocessHandler写在BLL更合理,但那样文档集合就要多遍历一遍,MongoCursor到IList一遍,PreprocessHandler处理IList又一遍!唉,程序员容易嘛~~~

当然,你也可以对Find做各种你喜欢的重载,更要写其他方面(Insert,Update,Remove...)的方法对BaseDAL类进行完善。

最后让亲看一下User浮云(其他浮云也是这个样):

public class User:BaseDAL<user>

{

protected override string SetCollectionName()

{

return "user";

}

}

三、BLL层

有泛型就是意思,看BaseBLL:

/// <summary>

/// 业务逻辑层基类

/// </summary>

/// <typeparam name="TDAL">数据访问类型</typeparam>

/// <typeparam name="TDocument">文档模型类型</typeparam>

public abstract class BaseBLL<TDAL, TDocument> where TDAL : DAL.BaseDAL<TDocument>,new()

{

protected TDAL dal = new TDAL();

public TDocument FindOne(IMongoQuery query, PreprocessHandler<TDocument> preprocess)

{

return dal.FindOne(query,preprocess);

}

}

基本上是对DAL的一个调用,无他!直接到它的子类,因为是逻辑层,比浮云多一点,可以算是个神马吧:

public sealed class User : BLL.BaseBLL<DAL.User, user>

{

public user FindOneByName(string name)

{

var doc = base.FindOne(Query.EQ("u", name), P1);

return doc;

}

/// <summary>

/// 保证不为null

/// </summary>

/// <param name="doc"></param>

private void P1(ref user doc)

{

if (doc == null)

{

doc = new user();

}

P2(ref doc);

}

/// <summary>

/// 也许可以处理婴儿,哈哈

/// </summary>

/// <param name="doc"></param>

private void P2(ref user doc)

{

if (doc != null)

{

doc.age = 0;

}

}

}

代码也是很简单,其实这里有一个想法,很多实体类总是只有一种处理方法,可以在BaseBLL里写一个PreprocessHandler委托签名的虚方法做为默认处理方法, 在BaseBLL里就调用该方法,子类需要就可重写它,这样又简单了,为了方面查看,两个类的代码写在一块了:

/// <summary>

/// BaseBLL的默认处理方法

/// </summary>

/// <param name="doc"></param>

protected virtual void Preprocess(ref TDocument doc)

{

return;

}

/// <summary>

/// LG.BLL.User重写基类方法

/// </summary>

/// <param name="doc"></param>

protected override void Preprocess(ref user doc)

{

if (doc == null)

doc = new user();

if (doc.birth == null)

doc.birth = new user.Birthday();

}

到此,BLL事例也完了,当然,还要做更多的工作去完善。

四、UI层

这里其实没啥说的,为了文章完整性,简单提一下。

一般情况下UI是不会引用DAL的,但因为BaseBLL用了泛型参数,而泛型类型在DAL里,所以UI也要引用DAL,但永远不要用DAL。

还有一点,就是逻辑层实例化的问题,比如在同一次Http请求中,很可能不小心或者避免不了new了LG.BLL.User多次,这样做只是白白浪费了内存,增加GC压力,没一点好处,所以,再回到BLL层,添加这样一个类:

/// <summary>

/// 为了在一次请求中同类型逻辑对象只实例化一次,

/// 只能在http请求上下文中使用

/// </summary>

public static class B

{

public static T Entity<T>() where T : class, new()

{

string key = typeof(T).Name;

if (HttpContext.Current != null)

{

var bll = HttpContext.Current.Items[key] as T;

lock (key)

{

if (bll == null)

{

bll = new T();

HttpContext.Current.Items[key] = bll;

}

}

return bll;

}

else

{

return new T();

}

}

}

在UI或确定是有HTTP请求的上下文中都可以这样调用了(当然,如果基于其他上下文,也可以写一个类似上面的实例化方法):

User u = B.Entity<User>();

到止完结,平时写文章不多,表达欠佳,望亲们见谅!

时间: 2024-08-04 12:54:59

谈谈MongoDB的三层操作的相关文章

Spring整合Mongodb,Maven的依赖,Spring配置,MongoDB的公共操作类,使用SpringMVC的Controller进行测试并返回结果的案例

在和Spring和MongoDB进行整合的时候需要如下三个jar,分别是: spring-data-commons spring-data-mongodb mongo-java-driver 下面讲解Spring和MongoDB2.x进行整合的Spring配置(下面案例以下面的方式进行说明:) Maven的Pom文件的配置如下: <dependency> <groupId>org.springframework.data</groupId> <artifactId

mongodb复杂更新操作update group by

问题描述 mongodb复杂更新操作update group by mongodb复杂的更新操作: 把一张表如果group by某个字段的数量 大于5的数据对应的另外一张表进行更新. 类似以下的sql语句功能: update from 表1 set 表1.字段1 = 'yes' where 表1.code in ( select 表2.code from 表2 group by 表2.code having count(表2.code) >5 ) 解决方案 先说mongodb不支持SQL,第二,

从炉石传说数据库故障谈谈MongoDB的数据库备份和恢复手段

看到这个消息,我的第一反应是重新翻出尘封已久的ipad,装上炉石准备上线领补偿.等等,作为一个数据库行业从业人员,是不是还应该干点什么?恩,很有必要再重新审视一下我们的数据库有没有做好容灾,否则,今天你看别人热闹,明天可能就别人看你热闹了.借此机会我想给大家普及一下MongoDB数据库的备份和恢复手段(当然炉石传说应该不一定是使用MongoDB作为数据库),以帮助大家做好容灾,过个好年.同时,我也为我们阿里云MongoDB服务做下广告,我们的MongoDB服务拥有完善的自动备份/恢复功能,灵活的

php中的mongodb select常用操作代码示例_php实例

前面说到了mongodb安装,配置,集群,以及php的插入与更新等,请参考:mongodb. 下面说一下,mongodb select的常用操作 测试数据: 复制代码 代码如下: { "_id" : 1, "title" : "红楼梦", "auther" : "曹雪芹", "typeColumn" : "test", "money" : 80,

【mongodb系统学习之八】mongodb shell常用操作

八.mongodb  shell常用基础操作(每个语句后可以加分号,也可以不加,看情况定(有的工具中可以不加),最好是加):    1).进入shell操作界面:mongo,上边已有演示:     2).查看当前使用的数据库:db,上边已有演示:     3).查看当前所有存在的数据库:showdbs:查看当前数据中所有集合,showcollections如图:                 4).切换数据库:usedbname:需要注意的是,如果已经存在这个数据库,则会切换到该数据库:如果不

谈谈Mongodb注入攻击实录【多图】

前言 关于mongodb的基本安装运行操作以及php操作mongodb,请参考我以前的文章 php下操作mongodb的帖子国内已经有了,但是基于php下注入攻击mongodb的文章似乎还比较少.本文是笔者在学习.查阅了大量资料后的一些总结,文中涉及的攻击手法及其知识产权全部归原作者所有,我只是大自然的搬运工.未征得笔者同意,请勿转载. 概括 php下操作mongodb大致有以下两种方式 1.用mongo类中相应的方法执行增查减改 比如: <?php   $mongo = new mongocl

ThinkJS中如何使用MongoDB的CURD操作_javascript类库

前言 众所周知目前使用Node.js + mongodb已经成为很多公司的技术栈.ThinkJS其实也提供了对mongo的支持,虽然官方文档较少,但是保证了ORM的API的一致性,所以用起来需要查看基本的>Model api 基本的模型文件放在common/model下 获取列表 getList(q, page) { return this.select(); } 分页加条件搜索 search(q, page) { if(q) { q = new RegExp(q,'i'); } return

ThinkJS中MongoDB的CURD操作是什么?CURD操作的使用教程

前言 众所周知目前使用Node.js + mongodb已经成为很多公司的技术栈.ThinkJS其实也提供了对mongo的支持,虽然官方文档较少,但是保证了ORM的API的一致性,所以用起来需要查看基本的>Model api 基本的模型文件放在common/model下 获取列表 getList(q, page) { returnthis.select(); } 分页加条件搜索 search(q, page) { if(q) { q =newRegExp(q,'i'); } returnthis

mongodb gridfs 删除操作例子

前面写了一篇关于gridfs文章,但是没有关于gridfs删除的信息,现在补上,有点标题党的感觉. 1,命令方式 [root@localhost ~]# mongofiles delete 111.jpg  //删除111.jpg  connected to: 127.0.0.1  done!    [root@localhost ~]# mongofiles delete test.flv -d video    //删除video数据库下的test.flv文件  connected to: