求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
谈谈MongoDB的三层操作
 
火龙果软件    发布于 2013-9-6
 

我们今天要写的是MongoDB的三层操作,包括Module层、DAL层、BLL层。希望对大家有所帮助。

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>
      { 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)
          
          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&lt;TDAL, TDocument&gt; 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>();

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

相关文章

基于EA的数据库建模
数据流建模(EA指南)
“数据湖”:概念、特征、架构与案例
在线商城数据库系统设计 思路+效果
 
相关文档

Greenplum数据库基础培训
MySQL5.1性能优化方案
某电商数据中台架构实践
MySQL高扩展架构设计
相关课程

数据治理、数据架构及数据标准
MongoDB实战课程
并发、大容量、高性能数据库设计与优化
PostgreSQL数据库实战培训
 
分享到
 
 


MySQL索引背后的数据结构
MySQL性能调优与架构设计
SQL Server数据库备份与恢复
让数据库飞起来 10大DB2优化
oracle的临时表空间写满磁盘
数据库的跨平台设计
更多...   


并发、大容量、高性能数据库
高级数据库架构设计师
Hadoop原理与实践
Oracle 数据仓库
数据仓库和数据挖掘
Oracle数据库开发与管理


GE 区块链技术与实现培训
航天科工某子公司 Nodejs高级应用开发
中盛益华 卓越管理者必须具备的五项能力
某信息技术公司 Python培训
某博彩IT系统厂商 易用性测试与评估
中国邮储银行 测试成熟度模型集成(TMMI)
中物院 产品经理与产品管理
更多...