2023年2月

一个老客户提出这样的需求,希望将SQLServer中的某个表的数据快速复制到SQLite数据库里面以便进行定期的备份处理,数据表的记录大概有50多万条记录,表有100个字段左右,除了希望能够快速做好外,效率是第一位的,他自己测试总是在一两个小时的时间以上。客户提出这样的需求,我我觉得肯定是没有很好的利用事务的特性,否则速度应该会快得多,但是具体能快到什么程度,心里也不太确定。于是按照这个要求,把这样大的表数据复制作为一个案例来进行研究,最终大数据的复制处理,不到20分钟就可以完成全部数据的复制更新处理。本文主要介绍这个需求如何结合实际开发的需要进行处理,达到快速高效的复制数据的目的,并提供相关的实现思路和代码供参考学习。

1、复制数据的需求及开发思路

由于客户是需要做定期的数据备份,因此这样的复制是进行的,因此大数据的复制效率肯定是很重要的,应该尽可能的短时间完成。数据表的记录大概有50多万条记录,表有100个字段左右的需要也是比常规的表数据会多一些,因此需要做好很好的测试,我们根据这样的需求背景,使用一个测试案例来对性能进行测试。

这样多字段的表,数据字段的一一对应,手工肯定是很累的,所以我们使用代码生成工具Database2Sharp来进行快速开发,这样底层的处理我们就可以不用太过关注,而且可以为不同的数据处理,生成不同的数据访问层即可。

在底层我们主要是采用了微软的Enterprise Library的数据库访问模块,因此它能够很好抽象各种数据库的事务,以适应各种不同数据库的事务处理。使用微软的Enterprise Library模块,可以很好支持SQLSever、Oracle、Mysql、Access、SQLite等数据库。

开发框架,常见的分层模式,可以分为UI层、BLL层、DAL层、IDAL层、Entity层、公用类库层等等

框架的基类我们封装了大量的通用性处理函数,包括数据访问层、业务逻辑层的基类,所有的基类函数基本上都带有一个
DbTransaction trans = null
的定义,就是我们可以采用事务,也可以默认不采用事务,是一个可选性的事务参数。

如数据访问接口和基于SQLServer的数据访问类的实现图示如下所示。

在最高级的抽象基类AbstractBaseDAL的数据访问层里面,都有大量关于数据操作和相关事务的接口可以使用,因此我们在底层继承的子类,如果我们处理数据的增删改查等操作,基本上就不需要做任何扩展性代码了,这样很符合我们快速开发的目的。

在框架的整个数据访问层,我们都定义了很多公用的、带有事务参数的接口,如果我们在常规的数据处理里面,使用事务的话,那么也是很方便的事情。使用事务的批量处理,对于SQLite的操作来说,效率是非常明显的,具体可以在我之前的随笔里《
使用事务操作SQLite数据批量插入,提高数据批量写入速度,源码讲解
》可以了解到,他们之间的处理效率是很大差距的。

2、使用代码生成工具生成所需的代码

上面讲到,开发这样的数据复制处理程序,这样多字段的表,数据字段的一一对应,手工肯定是很累的,所以我们使用代码生成工具Database2Sharp来进行快速开发。

因此使用代码生成工具来快速生成所需要的代码,展开数据库后,从数据库节点上,右键选择【代码生成】【Enterprise Library代码生成】就可以生成标准的界面层一下的代码了,由于我们整个案例是非标准的数据复制处理,界面部分不需要利用代码生成工具进行Winform界面的生成的。

生成代码的一步步操作,最后确认一下就可以生成相关的底层代码了

最后我们生成这样的BLL、DAL、IDAL、Entity几个层的项目代码,整个项目的代码各种继承关系已经处理好了,也就具有了基类拥有的增删改查等基础操作了。

我们做两个不同数据库的复制处理操作,关键还是要生成两个不同数据库访问类的代码(也就是生成一个标准的SQLServer后,复制一份代码,修改下继承基类即可实现),如下代码是两个数据访问类的代码,不用增加任何接口即可满足当前项目的需要的了。

最终我们的项目结构如下所示。

3、进行数据复制处理的Winform界面代码逻辑

为了方便整个复制过程的进度展示(很重要),我们设计了进度条以及文字内容,展示处理过程的进度和耗时等信息,最终界面设计如下所示。

整个界面设计利用后台线程的方式对数据复制进行处理,方便及时在界面显示进度而不阻塞界面线程。

具体的界面代码如下所示。

    public partial classFrmMain : Form
{
privateTimeSpan ExecuteTime;private int currentCount = 0;private BackgroundWorker work = new BackgroundWorker();//使用后台线程进行处理,不阻塞界面显示 publicFrmMain()
{
InitializeComponent();
//定义后台线程的处理 work.DoWork +=work_DoWork;
work.WorkerReportsProgress
= true;
work.ProgressChanged
+=work_ProgressChanged;
work.RunWorkerCompleted
+=work_RunWorkerCompleted;
}
//线程完成后通知结束 void work_RunWorkerCompleted(objectsender, RunWorkerCompletedEventArgs e)
{
this.toolStripProgressBar1.Value = 100;this.toolStripProgressBar1.Visible = false;
MessageUtil.ShowTips(
"操作完成");

ShowMessage(
this.toolStripProgressBar1.Value);//完成 }/// <summary> ///在界面显示文本信息/// </summary> /// <param name="percent">完成百分比</param> private void ShowMessage(intpercent)
{
if (this.ExecuteTime != null)
{
this.lblTips.Text = string.Format("[当前完成数量:{0},完成百分比:{1}, 执行耗时:{2}毫秒 | {3}分钟{4}秒]",this.currentCount, percent, this.ExecuteTime.TotalMilliseconds, this.ExecuteTime.Minutes, this.ExecuteTime.Seconds);
}
}
/// <summary> ///报告进度的时候,显示相关的数量、耗时等内容/// </summary> void work_ProgressChanged(objectsender, ProgressChangedEventArgs e)
{
this.toolStripProgressBar1.Value =e.ProgressPercentage;this.statusStrip1.Refresh();

ShowMessage(e.ProgressPercentage);
}
/// <summary> ///后台线程执行的逻辑代码/// </summary> void work_DoWork(objectsender, DoWorkEventArgs e)
{
CopyDataUtil util
= new CopyDataUtil();//使用一个Action的Lamda表达式,执行通知界面处理 util.Start((percent, ts, current) =>{
work.ReportProgress(percent);
this.ExecuteTime =ts;this.currentCount =current;
});
}
private void btnCopy_Click(objectsender, EventArgs e)
{
if(!work.IsBusy)
{
//如果每次要求使用空白数据库测试,那么先删除旧数据库,再复制备份过去即可 string dbfile = Path.Combine(Environment.CurrentDirectory, "localdb.db");string bakfile = Path.Combine(Environment.CurrentDirectory, "db.db");if (this.chkCopyEmptyDb.Checked &&File.Exists(dbfile))
{
File.Delete(dbfile);
File.Copy(bakfile, dbfile,
true);
}
//显示进度条,并异步执行线程 this.toolStripProgressBar1.Visible = true;
work.RunWorkerAsync();
}
}
private void FrmMain_FormClosing(objectsender, FormClosingEventArgs e)
{
//取消注册的相关事件,防止退出的时候出现异常 if(work != null &&work.IsBusy)
{
work.ProgressChanged
-= work_ProgressChanged; //取消通知事件 work.RunWorkerCompleted -= work_RunWorkerCompleted;//取消完成事件 work.Dispose();
}
}
}

在上面的窗体界面代码里面,最为关键的代码就是具体后台进程的处理逻辑,如下代码所示。

        /// <summary>
        ///后台线程执行的逻辑代码/// </summary>
        void work_DoWork(objectsender, DoWorkEventArgs e)
{
CopyDataUtil util
= new CopyDataUtil();//使用一个Action的Lamda表达式,执行通知界面处理 util.Start((percent, ts, current) =>{
work.ReportProgress(percent);
this.ExecuteTime =ts;this.currentCount =current;
});
}

上面的处理逻辑为了方便,把数据的复制内容放到了一个辅助类里面,并在辅助类的Start方法里面传入了界面通知的Action处理函数,这样我们在
CopyDataUtil
处理的时候就可以随时进行消息的通知了。

数据复制的Start方法定义如下所示。

        /// <summary>
        ///开始执行赋值/// </summary>
        public void Start(Action<int, TimeSpan, int>doFunc)
{
StartTime
= DateTime.Now;//计时开始 InternalCopry(doFunc);//处理数据复制逻辑,并执行外部的函数 EndTime= DateTime.Now;//计时结束 }

整个辅助类
CopyDataUtil
类里面定义了两个不同数据库类型的对象,方便数据库的赋值处理操作,并且定义了开始时间,结束时间,这样可以统计总共的耗时信息,如下代码所示。

    /// <summary>
    ///复制数据的处理类/// </summary>
    public classCopyDataUtil
{
//使用一个计时器,对操作记录进行计时 privateDateTime StartTime, EndTime;//SQLServer数据库表对象 private ProductSqlServer sqlserver = null;//SQLite数据表对象 private ProductSqlite sqlite = null;publicCopyDataUtil()
{
//构建对象,并指定SQLServer的数据库配置项 sqlserver = newProductSqlServer();
sqlserver.DbConfigName
= "sqlserver";//构建对象,并指定SQLite的数据库配置项 sqlite = newProductSqlite();
sqlite.DbConfigName
= "sqlite";
}

整个复制数据的逻辑,主要就是基于事务性的处理,按照分页规则,每次按照一定的数量,批量从SQLServer里面取出数据,然后插入SQLite数据库里面,使用事务可以是的SQLite的数据写入非常高效快速,具体代码如下所示。

        /// <summary>
        ///大数据复制的处理逻辑/// </summary>
        /// <param name="doFunc">外部调用的函数</param>
        private void InternalCopry(Action<int, TimeSpan, int>doFunc)
{
//设置主键,并指定分页数量大小,提高检索效率 string primaryKey = "h_id";int pageSize = 1000;
PagerInfo info
= new PagerInfo(){PageSize = pageSize, CurrenetPageIndex =1};//根据数据的总数,取得总页数 int totalPageCount = 1;int totalCount =sqlserver.GetRecordCount();if (totalCount % pageSize == 0)
{
totalPageCount
= totalCount /pageSize;
}
else{
totalPageCount
= totalCount / pageSize + 1;
}
totalPageCount
= (totalPageCount < 1) ? 1: totalPageCount;//利用事务进行SQLite数据写入,提高执行响应效率 DbTransaction trans =sqlite.CreateTransaction();if (trans != null)
{
//根据每页数量,依次从指定的页数取数据 for (int i = 1; i <= totalPageCount; i++)
{
info.CurrenetPageIndex
= i;//设定当前的页面,并进行数据获取 int j = 1;
List
<ProductInfo> list = sqlserver.FindWithPager("1=1", info, primaryKey, false);foreach (ProductInfo entity inlist)
{
//取得当前数量和进度百分比 int current = (i - 1) * pageSize +j;int percent =GetPercent(totalCount, current);//计算程序耗时,执行外部函数进行界面通知 TimeSpan ts = DateTime.Now -StartTime;
doFunc(percent, ts, current);
//执行通知处理//如果不存在主键记录,则写入,否则更新 if (!sqlite.IsExistKey(primaryKey, entity.H_id, trans))
{
sqlite.Insert(entity, trans);
}
else{
sqlite.Update(entity, entity.H_id, trans);
}
j
++;
}
}
trans.Commit();
}
}

至此,整个项目的代码就基本上介绍完毕了,测试整个复制过程,单表50多万的数据,100个字段左右,在开发机器上20分钟不到就复制完成,确实是很不错的成绩了,如果修改为服务器的环境专门做复制处理,肯定速度还会提高不少。

在花了不少时间研究学习了MongoDB数据库的相关知识,以及利用C#对MongoDB数据库的封装、测试应用后,决定花一些时间来总结一下最近的研究心得,把这个数据库的应用单独作为一个系列来介绍,希望从各个方面来总结并记录一下这个新型、看似神秘的数据库使用过程。本文是这个系列的开篇,主要介绍一些MongoDB数据库的基础知识、安装过程、基础使用等方面。

MongoDB是一款由C++编写的高性能、开源、无模式的常用非关系型数据库产品,是非关系数据库当中功能最丰富、最像关系数据库的数据库。它扩展了关系型数据库的众多功能,例如:辅助索引、范围查询、排序等。

MongoDB主要解决的是海量数据的访问效率问题,它作为分布式数据崛起后,使用较多的一款非结构数据库,必然有其值得称道之处,它的主要功能特性如下:

1)面向集合的存储,适合存储对象及JSON形式的数据。
2)动态查询,MongoDB支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
3)完整的索引支持,包括文档内嵌对象及数组。MongoDB的查询优化器会分析查询表达式,并生成一个高效的查询计划。
4)查询监视,MongoDB包含一个监视工具用于分析数据库操作的性能。
5)复制及自动故障转移,MongoDB数据库支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。复制的主要目标是提供冗余及自动故障转移。
6)高效的传统存储方式,支持二进制数据及大型对象(如图片或视频)。
7)自动分片以支持云级别的伸缩性,自动分片功能支持水平的数据库集群,可动态添加额外的机器。

1、
MongoDB数据库和传统关系数据库的对比

MongoDB数据库有几个简单的概念需要了解一下。

1)MongoDB中的
database
有着和我们熟知的"数据库"一样的概念 (对 Oracle 来说就是 schema)。一个 MongoDB 实例中,可以有零个或多个数据库,每个都作为一个高等容器,用于存储数据。

2)数据库中可以有零个或多个
collections
(集合)。集合和传统意义上的 table 基本一致,可以简单的把两者看成是一样的东西。

3)集合是由零个或多个
documents
(文档)组成。同样,一个文档可以看成是一
row

4)文档是由零个或多个
fields
(字段)组成。,对应的就是关系数据库的
columns

5)
Indexes
(索引)在 MongoDB 中扮演着和它们在 RDBMS 中一样的角色,都是为了提高查询的效率。

6)
Cursors (游标)和上面的五个概念都不一样,但是它非常重要,并且经常被忽视,其中最重要的你要理解的一点是,游标是当你问 MongoDB 拿数据的时候,它会给你返回一个结果集的指针而不是真正的数据,这个指针我们叫它游标,我们可以拿游标做我们想做的任何事情,比如说计数或者跨行之类的,而无需把真正的数据拖下来,在真正的数据上操作。

它们的对比关系图如下所示。

数据在Mongodb里面都是以Json格式方式进行存储的,如下所示是其中的一个记录内容。

{
_id: ObjectID(
'4bd9e8e17cefd644108961bb'),
name:
'Vivek',class : '12th',
subjects: [
'physics', 'chemistry', 'math', 'english', 'computer'],
address: {
house_no:
'12B',
block:
'B',
sector:
12,
city :
'noida',
},
grade: [
{
exam:
'unit test 1',
score:
'60%'},
{
exam:
'unit test 2',
score:
'70%'}

]
}

在过去的很长一段时间中,关系型数据库一直是最主流的数据库解决方案,他运用真实世界中事物与关系来解释数据库中抽象的数据架构。然而,在信息技术爆炸式发展的今天,大数据已经成为了继云计算,物联网后新的技术革命,关系型数据库在处理大数据量时已经开始吃力,开发者只能通过不断地优化数据库来解决数据量的问题,但优化毕竟不是一个长期方案,所以人们提出了一种新的数据库解决方案来迎接大数据时代的到来——
NoSQL(非关系型数据库)
,其中MongoDB数据库就是其中的NoSQL的杰出代表。在大数据时代中,大数据量的处理已经成了考量一个数据库最重要的原因之一。而MongoDB的一个主要目标就是尽可能的让数据库保持卓越的性能,这很大程度地决定了MongoDB的设计。

根据MongoDB官网的说明,
MongoDB的适用场景如下
:

1)网站实时数据:MongoDB非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。

2)数据缓存:由于性能很高,MongoDB也适合作为信息基础设施的缓存层。在系统重启之后,由MongoDB搭建的持久化缓存层可以避免下层的数据源过载。

3)大尺寸、低价值数据存储:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。

4)高伸缩性场景:MongoDB非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包含对MapReduce引擎的内置支持。


5)对象或JSON数据存储:MongoDB的BSON数据格式非常适合文档化格式的存储及查询。


MongoDB
不适合使用场景如下:
1)高度事务性系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。

2)传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。
3)需要复杂SQL查询的问题。

MongoDB大多数情况下,可以代替关系型数据库实现数据库业务。它更简单更直接、更快速并且通常对应用开发者的约束更少,不过缺乏事务支持需要慎重考虑业务需要。

2、MongoDB数据库的安装及基础使用

MongoDB数据的官网为:https://www.mongodb.org/,当前版本为3.2,可以直接下载安装版本在Linux或者Windows进行安装。

一般在Windows,我们默认安装的路径为C:\Program Files\MongoDB,安装后可以手动创建一个放置数据库和日志文件的目录,一般不要放在C盘就好,如下所示:

创建文件夹d:\mongodb\data\db、d:\mongodb\data\log,分别用来安装db和日志文件,我们以后运行数据库后,这个目录就用来放置我们创建的数据库和日志资源了。

一般我们安装后,为了在命令行方便调用Mongodb的命令,我们可以设置一个全局的路径变量,如下所示。

默认情况下,mongodb的工作模式,是启动一个DOS窗口,运行mongodb的数据库服务,一旦这个DOS窗口关闭,也就停止了相关的服务,在Windows平台,我们可以把它寄宿在Windows服务里面,让它随着系统的启动而启动,也不必因为误关闭窗口而停止了数据库服务了。

通过下面命令行执行数据库服务的处理。

mongod --dbpath "d:\mongodb\data\db" --logpath "d:\mongodb\data\log\MongoDB.log" --install --serviceName "MongoDB"

然后使用命令行启动服务

NET START MongoDB

创建服务并顺利启动成功后,然后就可以在系统的服务列表里查看到了,我们确认把它设置为自动启动的Windows服务即可。

启动后,我们可以在系统【运行】里面直接使用命令mongo打开窗口就可以进行相关的操作了。

上面用了一些常见的命令操作。

  • show dbs    显示数据库列表
  • use dbname    进入dbname数据库,大小写敏感,没有这个数据库也不要紧
  • show collections    显示数据库中的集合,相当于表格
  • db.<collection_name>.find(); 集合查找方法,参考上面的方式,使用pretty()函数是排版更好看的意思。

而其中find方法很强大,可以组合很多条件查询的方式,如下所示:

  • db.collection.find({ "key" : value })    查找key=value的数据
  • db.collection.find({ "key" : { $gt: value } })    key > value
  • db.collection.find({ "key" : { $lt: value } })    key < value
  • db.collection.find({ "key" : { $gte: value } })    key >= value
  • db.collection.find({ "key" : { $lte: value } })    key <= value
  • db.collection.find({ "key" : { $gt: value1 , $lt: value2 } })    value1 < key <value2
  • db.collection.find({ "key" : { $ne: value } })    key <> value
  • db.collection.find({ "key" : { $mod : [ 10 , 1 ] } })    取模运算,条件相当于key % 10 == 1 即key除以10余数为1的
  • db.collection.find({ "key" : { $nin: [ 1, 2, 3 ] } })    不属于,条件相当于key的值不属于[ 1, 2, 3 ]中任何一个
  • db.collection.find({ "key" : { $in: [ 1, 2, 3 ] } })    属于,条件相当于key等于[ 1, 2, 3 ]中任何一个
  • db.collection.find({ "key" : { $size: 1 } })    $size 数量、尺寸,条件相当于key的值的数量是1(key必须是数组,一个值的情况不能算是数量为1的数组)
  • db.collection.find({ "key" : { $exists : true|false } })    $exists 字段存在,true返回存在字段key的数据,false返回不存在字度key的数据
  • db.collection.find({ "key": /^val.*val$/i })    正则,类似like;“i”忽略大小写,“m”支持多行
  • db.collection.find({ $or : [{a : 1}, {b : 2} ] })    $or或 (注意:MongoDB 1.5.3后版本可用),符合条件a=1的或者符合条件b=2的数据都会查询出来
  • db.collection.find({ "key": value , $or : [{ a : 1 } , { b : 2 }] })    符合条件key=value ,同时符合其他两个条件中任意一个的数据
  • db.collection.find({ "key.subkey" :value })    内嵌对象中的值匹配,注意:"key.subkey"必须加引号
  • db.collection.find({ "key": { $not : /^val.*val$/i } })    这是一个与其他查询条件组合使用的操作符,不会单独使用。上述查询条件得到的结果集加上$not之后就能获得相反的集合。

当然还有插入更新的处理语句也是很特别的。

db.student.insert({name:'student1',subject:['arts','music']})

特别是更新操作需要说明一下,支持常规的$set方法(修改)、$unset方法(移除指定的键),还有原子级的$inc方法(数值增减),$rename方法(重命名字段名称)等等,

db.users.update({"_id" : ObjectId("51826852c75fdd1d8b805801")},  {"$set" : {"hobby" :["swimming","basketball"]}} )
db.users.update({"_id" : ObjectId("51826852c75fdd1d8b805801")},{"$unset" : {"hobby" :1 }} )
db.posts.update({"_id" : ObjectId("5180f1a991c22a72028238e4")}, {"$inc":{"pageviews":1}})
db.students.update( { _id: 1 }, { $rename: { 'nickname': 'alias', 'cell': 'mobile' } } 

upsert是一种特殊的更新操作,不是一个操作符。(upsert = up[date]+[in]sert),也就是如果存在则更新,否则就写入一条新的记录操作。这个参数是个布尔类型,默认是false。

db.users.update({age :25}, {$inc :{"age" :3}}, true)

另外,Update可以对Json的集合进行处理,如果对于subject对象是一个集合的话,插入或更新其中的字段使用下面的语句

db.student.update({name:'student5'},{$set:{subject:['music']}},{upsert:true});

如果是记录已经存在,我们可以使用索引数值进行更新其中集合里面的数据,如下所示。

db.student.update({name:'student3'},{$set:{'subject.0':'arts'}});

如果我们先在集合里面增加一个记录,而非替换的话,那么使用
$push
语句,如下面的语句所示。

db.student.update({name:'student3'},{$push:{'subject':'sports'}})

相反,如果要移除集合里面的某个值,使用
$pop
操作符,那么语句如下所示

db.student.update({name:'student3'},{$pop:{'subject':1}});

其中索引为1标识最右边的记录,-1标识为最左边的记录。

另外还可以使用
$pushAll
和$pullAll来增加/移除一个或多个集合记录,如下代码所示。

db.student.update({name:'student3'},{$pushAll:{'subject':['sports','craft']}})
db.student.update({name:
'student3'},{$pullAll:{'subject':['sports','craft']}})

mongodb的数据库的操作还是比较容易理解的,具体可以进一步参考官网里面的介绍。

https://docs.mongodb.org/manual/

https://docs.mongodb.org/getting-started/csharp/client/

http://mongodb.github.io/mongo-csharp-driver/2.2/

http://wiki.jikexueyuan.com/project/the-little-mongodb-book/

3、MongoDB数据库的C#驱动的使用

数据库的C#驱动使用介绍,可以参考:https://docs.mongodb.org/getting-started/csharp/ 当前已更改为
https://docs.mongodb.com/drivers/csharp
,可以下载相关的DLL然后在项目中引用,当前的驱动版本为2.2,一般引入下面几个DLL即可。

    • MongoDB.Bson.dll
    • MongoDB.Driver.dll
    • MongoDB.Driver.Core.dll

也可以使用VS工具的NugGet包进行下载管理,如下所示。

然后在弹出的NugGet程序包管理界面里面搜索mongo,然后添加MongoDB.Driver的数据库驱动就可以使用了。

MongoDB数据库驱动在2.2版本(或者是从2.0开始)好像完全改写了API的接口,因此目前这个版本同时支持两个版本的API处理,一个是基于MongoDatabase的对象接口,一个是IMongoDatabase的对象接口,前者中规中矩,和我们使用Shell里面的命令名称差不多,后者IMongoDatabase的接口是基于异步的,基本上和前者差别很大,而且接口都提供了异步的处理操作。后面我会分别对这两个部分进行详细的介绍,本文基于篇幅的原因,介绍一下两者的简单差异就可以了。

我们以Mongodb的数据库连接字符串mongodb://localhost/local来进行构建

1)旧接口MongoDatabase对象的构建

            var client =new MongoClient(connectionString);var database = client.GetServer().GetDatabase(new MongoUrl(connectionString).DatabaseName);

2)新接口IMongoDatabase对象的构建

            var client =new MongoClient(connectionString);var database = client.GetDatabase(new MongoUrl(connectionString).DatabaseName);

后者已经没有了
GetServer
的接口了。

3)旧接口的查找对象处理

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

MongoCollection
<T> collection =GetCollection();return collection.FindOneById(newObjectId(id));
}

3)新接口查找对象的处理

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();return collection.Find(s=> s.Id ==id).FirstOrDefault();
}

新接口已经没有了FindOneById等接口了,所有的操作基本上都通过Find方法进行处理。旧接口很多通过Query对象进行条件的查询,新接口又换了一个对象进行过滤条件处理了,总的来说,两个接口差异非常大。

例如旧版本接口的Query使用C#代码如下所示:

        private voidTestQuery()
{
Query.All(
"name", new List<BsonValue> { BsonValue.Create("a"), BsonValue.Create("b") });//通过多个元素来匹配数组 Query.And(Query.EQ("name", "a"), Query.EQ("title", "t"));//同时满足多个条件 Query.Or(Query.EQ("name", "a"), Query.EQ("title", "t"));//满足其中一个条件 Query.EQ("name", "a");//等于 Query.Exists("type");//判断键值是否存在 Query.GT("value", 2);//大于> Query.GTE("value", 3);//大于等于>= Query.In("name", new List<BsonValue> { BsonValue.Create("a"), BsonValue.Create("b") });//包括指定的所有值,可以指定不同类型的条件和值 Query.LT("value", 9);//小于< Query.LTE("value", 8);//小于等于<= Query.Mod("value", 3, 1);//将查询值除以第一个给定值,若余数等于第二个给定值则返回该结果 Query.NE("name", "c");//不等于 Query.Size("name", 2);//给定键的长度 Query.Type("_id", BsonType.ObjectId);//给定键的类型 Query.ElemMatch("children", Query.And( Query.EQ("name", "C3"), Query.EQ("value", "C")));//Query.Nor(Array);//不包括数组中的值//Query.Not("name");//元素条件语句//Query.NotIn("name", "a", 2);//返回与数组中所有条件都不匹配的文档//Query.Where(BsonJavaScript);//执行JavaScript//Query.Matches("Title", str);//模糊查询 相当于sql中like -- str可包含正则表达式 var keyword = "abc";
Query.Matches(
"Title", new BsonRegularExpression("/.*" + keyword + ".*/"));//模糊Like语法//通过正则表达式 1开头 第二位数0~9且只能一位数,也包含20 var queryName = Query.Matches("Name", new BsonRegularExpression("Donma1([0-9]{1,1})$|20"));//查找年龄 >=10 且<=20 var queryAge = Query.And(Query.GTE("Age", 10), Query.LTE("Age", 20));var entityQuery = Query<UserInfo>.EQ(e => e.Name, "wuhuacong");var entityQuery2 = Query<UserInfo>.EQ(e => e.Id, "4B414D000000011613CD");
}

新版本的条件查询,则丢弃了Query这个对象,提供了FilterDefinition<T> 对象的处理,估计是这个可以处理的更好吧,同时新接口全部支持异步的处理操作了。

如插入记录的异步操作代码如下所示。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        public virtual asyncTask InsertAsync(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

IMongoCollection
<T> collection =GetCollection();awaitcollection.InsertOneAsync(t);
}

好了,基于篇幅的原因,把后面介绍的C#开发留到下一篇进行介绍,希望本篇文章对大家了解mongodb数据库,以及如何在C#上面使用该数据库提供了一个简要的指引,希望大家多多支持。

在上篇博客《
基于C#的MongoDB数据库开发应用(1)--MongoDB数据库的基础知识和使用
》里面,我总结了MongoDB数据库的一些基础信息,并在最后面部分简单介绍了数据库C#驱动的开发 ,本文继续这个主题,重点介绍MongoDB数据库C#方面的使用和封装处理过程,利用泛型和基类对象针对数据访问层进行的封装处理。

前面介绍到,当前2.2版本的数据库C#驱动的API,支持两种不同的开发接口,一个是基于MongoDatabase的对象接口,一个是IMongoDatabase的对象接口,前者中规中矩,和我们使用Shell里面的命令名称差不多;后者IMongoDatabase的接口是基于异步的,基本上和前者差别很大,而且接口都提供了异步的处理操作。

本文主要介绍基于MongoDatabase的对象接口的封装处理设置。

1、数据访问层的设计

在结合MongoDB数据库的C#驱动的特点,使用泛型和继承关系,把常规的处理接口做了抽象性的封装,以便封装适合更多业务的接口,减少子类代码及统一API的接口名称。

首先我们来看看大概的设计思路,我们把实体类抽象一个实体基类,方便使用。

我们知道,在MongoDB数据库的集合里面,都要求文档有一个_id字段,这个是强制性的,而且这个字段的存储类型为ObjectId类型,这个值考虑了分布式的因素,综合了机器码,进程,时间戳等等方面的内容,它的构造如下所示。

ObjectId是一个12字节的
BSON
类型字符串。按照字节顺序,依次代表:

  • 4字节:UNIX时间戳
  • 3字节:表示运行MongoDB的机器
  • 2字节:表示生成此_id的进程
  • 3字节:由一个随机数开始的计数器生成的值

实体基类BaseEntity包含了一个属性Id,这个是一个字符串型的对象(也可以使用ObjectId类型,但是为了方便,我们使用字符型,并声明为ObjectId类型即可),由于我们声明了该属性对象为ObjectId类型,那么我们就可以在C#代码里面使用字符串的ID类型了,代码如下所示。

    /// <summary>
    ///MongoDB实体类的基类/// </summary>
    public classBaseEntity
{
/// <summary> ///基类对象的ID,MongoDB要求每个实体类必须有的主键/// </summary> [BsonRepresentation(BsonType.ObjectId)]public string Id { get; set; }
}

然后利用泛型的方式,把数据访问层的接口提出来,并引入了数据访问层的基类进行实现和重用接口,如下所示。

其中,上面几个类的定义如下所示。

数据访问层基类BaseDAL的类定义如下所示,主要就是针对上面的IBaseDAL<T>接口进行实现。

有了这些基类的实现,我们对于实体类的处理就非常方便、统一了,基本上不需要在复制大量的代码来实现基础的增删改查分页实现了。

例如上面的User集合(表对象)的数据访问类定义如下所示,在对象的定义的时候,指定对应的实体类,并在构造函数里面指定对应的集合名称就可以实例化一个数据访问类了。

    /// <summary>
    ///数据表User对应的具体数据访问类/// </summary>
    public class User : BaseDAL<UserInfo>, IBaseDAL<UserInfo>{/// <summary>
        ///默认构造函数/// </summary>
        publicUser() 
{
this.entitysName = "users";//对象在数据库的集合名称 }

.................

2、基类各种接口的实现

前面小节我们介绍了实体基类,数据访问层基类接口和基类实现,以及具体集合对象的实现类的定义关系,通过泛型和继承关系,我们很好的抽象了各种对象的增删改查、分页等操作,子类继承了BaseDAL基类后,就自然而然的具有了非常强大的接口处理功能了。下面我们来继续详细介绍基于C#驱动的MongoDB数据库是如何进行各种增删改查等封装的。

1)构造MongoDatabase对象

首先我们需要利用连接字符串来构建
MongoDatabase对象,因为所有的接口都是基于这个对象进行处理的,代码如下所示。

        /// <summary>
        ///根据数据库配置信息创建MongoDatabase对象,如果不指定配置信息,则从默认信息创建/// </summary>
        /// <param name="databaseName">数据库名称,默认空为local</param>
        /// <returns></returns>
        protected virtualMongoDatabase CreateDatabase()
{
string connectionString = null;if (!string.IsNullOrEmpty(dbConfigName))
{
//从配置文件中获取对应的连接信息 connectionString =ConfigurationManager.ConnectionStrings[dbConfigName].ConnectionString;
}
else{
connectionString
=defaultConnectionString;
}
var client = newMongoClient(connectionString);var database = client.GetServer().GetDatabase(newMongoUrl(connectionString).DatabaseName);returndatabase;
}

2)构建MongoCollection对象

上面构建了MongoDatabase对象后,我们需要基于这个基础上再创建一个对象的MongoCollection对象,这个就是类似我们关系数据库里面的表对象的原型了。

        /// <summary>
        ///获取操作对象的MongoCollection集合,强类型对象集合/// </summary>
        /// <returns></returns>
        protected virtual MongoCollection<T>GetCollection()
{
MongoDatabase database
=CreateDatabase();return database.GetCollection<T>(this.entitysName);
}

3)查询单个对象

利用MongoCollection对象,我们可以通过API接口获取对应的对象,单个对象的接口为FindOneById(也可以用FindOne接口,如注释部分的代码),我们具体的处理代码如下所示

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

MongoCollection
<T> collection =GetCollection();return collection.FindOneById(new ObjectId(id)); //FindOne(Query.EQ("_id", new ObjectId(id))); }

如果基于条件的单个记录查询,我们可以使用Expression<Func<T, bool>>和IMongoQuery的参数进行处理,如下代码所示。

        /// <summary>
        ///根据条件查询数据库,如果存在返回第一个对象/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>存在则返回指定的第一个对象,否则返回默认值</returns>
        public virtual T FindSingle(Expression<Func<T, bool>>match)
{
MongoCollection
<T> collection =GetCollection();returncollection.AsQueryable().Where(match).FirstOrDefault();
}
/// <summary> ///根据条件查询数据库,如果存在返回第一个对象/// </summary> /// <param name="query">条件表达式</param> /// <returns>存在则返回指定的第一个对象,否则返回默认值</returns> public virtualT FindSingle(IMongoQuery query)
{
MongoCollection
<T> collection =GetCollection();returncollection.FindOne(query);
}

4)IQueryable的接口利用

使用过EF的实体框架的话,我们对其中的IQueryable<T>印象很深刻,它可以给我提供很好的LINQ语法获取对应的信息,它可以通过使用Expression<Func<T, bool>>和IMongoQuery的参数来进行条件的查询操作,MongoCollection对象有一个AsQueryable()的API进行转换,如下所示。

        /// <summary>
        ///返回可查询的记录源/// </summary>
        /// <returns></returns>
        public virtual IQueryable<T>GetQueryable()
{
MongoCollection
<T> collection =GetCollection();
IQueryable
<T> query =collection.AsQueryable();return query.OrderBy(this.SortPropertyName, this.IsDescending);
}

如果是通过使用Expression<Func<T, bool>>和IMongoQuery的参数,那么处理的接口代码如下所示。

        /// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true)
{
MongoCollection
<T> collection =GetCollection();
IQueryable
<T> query =collection.AsQueryable();if (match != null)
{
query
=query.Where(match);
}
returnquery.OrderBy(sortPropertyName, isDescending);
}
        /// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IQueryable<T> GetQueryable(IMongoQuery query, string sortPropertyName, bool isDescending = true)
{
MongoCollection
<T> collection =GetCollection();
IQueryable
<T> queryable =collection.Find(query).AsQueryable();returnqueryable.OrderBy(sortPropertyName, isDescending);
}

5)集合的查询处理

通过利用上面的IQueryable<T>对象,以及使用Expression<Func<T, bool>>和IMongoQuery的参数,我们很好的进行集合的查询处理操作的了,具体代码如下所示

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> Find(Expression<Func<T, bool>>match)
{
returnGetQueryable(match).ToList();
}
/// <summary> ///根据条件查询数据库,并返回对象集合/// </summary> /// <param name="query">条件表达式</param> /// <returns>指定对象的集合</returns> public virtual IList<T>Find(IMongoQuery query)
{
MongoCollection
<T> collection =GetCollection();returncollection.Find(query).ToList();
}

对于分页,我们是非常需要的,首先在大数据的集合里面,我们不可能一股脑的把所有的数据全部返回,因此根据分页参数返回有限数量的集合处理就是我们应该做的,分页的操作代码和上面很类似,只是利用了Skip和Take的接口,返回我们需要的记录数量就可以了。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> FindWithPager(Expression<Func<T, bool>>match, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;

IQueryable
<T> query =GetQueryable(match);
info.RecordCount
=query.Count();returnquery.Skip(excludedRows).Take(pageSize).ToList();
}

或者是下面的代码

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="query">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T>FindWithPager(IMongoQuery query, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;

IQueryable
<T> queryable =GetQueryable(query);
info.RecordCount
=queryable.Count();returnqueryable.Skip(excludedRows).Take(pageSize).ToList();
}

6)对象的写入操作

对象的写入可以使用save,它是根据_id的来决定插入还是更新的,如下代码所示。

        /// <summary>
        ///保存指定对象到数据库中,根据Id的值,决定是插入还是更新/// </summary>
        /// <param name="t">指定的对象</param>
        /// <returns>执行成功指定对象信息</returns>
        public virtualT Save(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

MongoCollection
<T> collection =GetCollection();var result =collection.Save(t);returnt;
}

插入记录就可以利用insert方法进行处理的,代码如下所示。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual boolInsert(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

MongoCollection
<T> collection =GetCollection();var result =collection.Insert(t);return result != null && result.DocumentsAffected > 0;
}

如果是批量插入,可以利用它的insertBatch的方法进行处理,具体代码如下所示。

        /// <summary>
        ///插入指定对象集合到数据库中/// </summary>
        /// <param name="list">指定的对象集合</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool InsertBatch(IEnumerable<T>list)
{
ArgumentValidation.CheckForNullReference(list,
"传入的对象list为空");

MongoCollection
<T> collection =GetCollection();var result =collection.InsertBatch(list);return result.Any(s => s != null && s.DocumentsAffected > 0); //部分成功也返回true }

7)对象的更新操作

更新操作分为了两个不同的部分,一个是全部的记录更新,也就是整个JSON的替换操作了,一般我们是在原来的基础上进行更新的,如下代码所示。

        /// <summary>
        ///更新对象属性到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        /// <param name="id">主键的值</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Update(T t, stringid)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");bool result = false;
MongoCollection
<T> collection =GetCollection();var existing =FindByID(id);if (existing != null)
{
var resultTmp =collection.Save(t);
result
= resultTmp != null && resultTmp.DocumentsAffected > 0;
}
returnresult;
}

还有一种方式是部分更新,也就是更新里面的指定一个或几个字段,不会影响其他字段,也就不会全部替换掉其他内容的操作了。这里利用了一个UpdateBuilder<T>的对象,用来指定那些字段需要更新,以及这些字段的值内容的,具体的更新代码如下所示。

        /// <summary>
        ///封装处理更新的操作/// </summary>
        /// <param name="id">主键的值</param>
        /// <param name="update">更新对象</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Update(string id, UpdateBuilder<T>update)
{
ArgumentValidation.CheckForNullReference(update,
"传入的对象update为空");
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");var query = Query.EQ("_id", newObjectId(id));
MongoCollection
<T> collection =GetCollection();var result =collection.Update(query, update);return result != null && result.DocumentsAffected > 0;
}

部分更新,可以结合使用Inc和Set方法来进行处理,如下是我在子类里面利用到上面的Update部分更新的API进行处理个别字段的更新操作。

        /// <summary>
        ///为用户增加岁数/// </summary>
        /// <param name="id">记录ID</param>
        /// <param name="addAge">待增加的岁数</param>
        /// <returns></returns>
        public bool IncreaseAge(string id, intaddAge)
{
//增加指定的岁数 var query = Query<UserInfo>.EQ(s =>s.Id, id);var update = Update<UserInfo>.Inc(s =>s.Age, addAge);var collection =GetCollection();var result =collection.Update(query, update);return result != null && result.DocumentsAffected > 0;
}
/// <summary> ///单独修改用户的名称/// </summary> /// <param name="id">记录ID</param> /// <param name="newName">用户新名称</param> /// <returns></returns> public bool UpdateName(string id, stringnewName)
{
//增加指定的岁数 var query = Query<UserInfo>.EQ(s =>s.Id, id);var update = Update<UserInfo>.Set(s =>s.Name, newName);var collection =GetCollection();var result =collection.Update(query, update);return result != null && result.DocumentsAffected > 0;
}

8)对象的删除操作

对象的删除,一般可以利用条件进行删除,如单个删除可以使用_id属性进行处理,也可以利用批量删除的接口进行删除操作,代码如下所示。

        /// <summary>
        ///根据指定对象的ID,从数据库中删除指定对象/// </summary>
        /// <param name="id">对象的ID</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Delete(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

MongoCollection
<T> collection =GetCollection();//var result = collection.Remove(Query<T>.EQ(s => s.Id, id)); var result = collection.Remove(Query.EQ("_id", newObjectId(id)));return result != null && result.DocumentsAffected > 0;
}

其中上面注释的var result = collection.Remove(Query<T>.EQ(s => s.Id, id));代码,就是利用了强类型的对象属性和值进行移除,一样可以的。

对于批量删除,可以利用Query的不同进行处理。

        /// <summary>
        ///根据指定对象的ID,从数据库中删除指定指定的对象/// </summary>
        /// <param name="idList">对象的ID集合</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool DeleteBatch(List<string>idList)
{
ArgumentValidation.CheckForNullReference(idList,
"传入的对象idList为空");

MongoCollection
<T> collection =GetCollection();var query = Query.In("_id", newBsonArray(idList));var result =collection.Remove(query);return result != null && result.DocumentsAffected > 0;
}

或者基于IMongoQuery的条件进行处理。

        /// <summary>
        ///根据指定条件,从数据库中删除指定对象/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual boolDeleteByQuery(IMongoQuery query)
{
MongoCollection
<T> collection =GetCollection();var result =collection.Remove(query);return result != null && result.DocumentsAffected > 0;
}

9)其他相关接口

一般除了上面的接口,还有一些其他的接口,如获取记录的总数、判断条件的记录是否存在等也是很常见的,他们的代码封装如下所示。

        /// <summary>
        ///获取表的所有记录数量/// </summary>
        /// <returns></returns>
        public virtual intGetRecordCount()
{
returnGetQueryable().Count();
}
        /// <summary>
        ///根据查询条件,判断是否存在记录/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        public virtual bool IsExistRecord(Expression<Func<T, bool>>match)
{
return GetQueryable(match).Any();//.Count() > 0 }/// <summary> ///根据查询条件,判断是否存在记录/// </summary> /// <param name="query">条件表达式</param> /// <returns></returns> public virtual boolIsExistRecord(IMongoQuery query)
{
return GetQueryable(query).Any();//.Count() > 0 }

非常感谢您的详细阅读,以上基本上就是我对整个MongoDB数据库的各个接口的基类封装处理了,其中已经覆盖到了基础的增删改查、分页等操作接口,以及一些特殊的条件处理接口的扩展,我们利用这些封装好的基类很好的简化了子类的代码,而且可以更方便的在基类的基础上进行特殊功能的扩展处理。

当然,以上介绍的都不是最新的接口,是2.0(或2.2)版本之前的接口实现,虽然在2.2里面也还可以利用上面的MongoDatabase对象接口,但是IMongoDatabase最新的接口已经全面兼容异步的操作,但也是一个很大的跳跃,基本上引入了不同的接口命名和处理方式,利用异步可以支持更好的处理体验,但是也基本上是需要对所有的接口进行了全部的重写了。

下一篇我会专门介绍一下基于最新的异步接口如何实现这些常规增删改查、分页等的基类实现封装处理。

在前面的系列博客中,我曾经介绍过,MongoDB数据库的C#驱动已经全面支持异步的处理接口,并且接口的定义几乎是重写了。本篇主要介绍MongoDB数据库的C#驱动的最新接口使用,介绍基于新接口如何实现基础的增删改查及分页等处理,以及如何利用异步接口实现基类相关的异步操作。

MongoDB数据库驱动在2.2版本(或者是从2.0开始)好像完全改写了API的接口,因此目前这个版本同时支持两个版本的API处理,一个是基于MongoDatabase的对象接口,一个是IMongoDatabase的对象接口,前者中规中矩,和我们使用Shell里面的命令名称差不多,后者IMongoDatabase的接口是基于异步的,基本上和前者差别很大,而且接口都提供了异步的处理操作。

1、MongoDB数据库C#驱动的新接口

新接口也还是基于数据库,集合,文档这样的处理概念进行封装,只是它们的接口不再一样了,我们还是按照前面的做法,定义一个数据库访问的基类,对MongoDB数据库的相关操作封装在基类里面,方便使用,同时基类利用泛型对象,实现更强类型的约束及支持,如基类BaseDAL的定义如下所示。

    /// <summary>
    ///数据访问层的基类/// </summary>
    public partial class BaseDAL<T> where T : BaseEntity, new()

利用泛型的方式,把数据访问层的接口提出来,并引入了数据访问层的基类进行实现和重用接口,如下所示。

基于新接口,如获取数据库对象的操作,则利用了
IMongoDatabase的接口了,如下所示。

            var client = newMongoClient(connectionString);var database = client.GetDatabase(new MongoUrl(connectionString).DatabaseName);

相对以前的常规接口,MongoClient对象已经没有了
GetServer
的接口了。如果对创建数据库对象的操作做更好的封装,可以利用配置文件进行指定的话,那么方法可以封装如下所示。

        /// <summary>
        ///根据数据库配置信息创建MongoDatabase对象,如果不指定配置信息,则从默认信息创建/// </summary>
        /// <param name="databaseName">数据库名称,默认空为local</param>
        /// <returns></returns>
        protected virtualIMongoDatabase CreateDatabase()
{
string connectionString = null;if (!string.IsNullOrEmpty(dbConfigName))
{
//从配置文件中获取对应的连接信息 connectionString =ConfigurationManager.ConnectionStrings[dbConfigName].ConnectionString;
}
else{
connectionString
=defaultConnectionString;
}
var client = newMongoClient(connectionString);var database = client.GetDatabase(newMongoUrl(connectionString).DatabaseName);returndatabase;
}

根据IMongoDatabase 接口,那么其获取集合对象的操作如下所示,它使用了另外一个定义IMongoCollection了。

        /// <summary>
        ///获取操作对象的IMongoCollection集合,强类型对象集合/// </summary>
        /// <returns></returns>
        public virtual IMongoCollection<T>GetCollection()
{
var database =CreateDatabase();return database.GetCollection<T>(this.entitysName);
}

2、查询单个对象实现封装处理

基于新接口的查询处理,已经没有FindOne的方法定义了,只是使用了Find的方法,而且也没有了Query的对象可以作为条件进行处理,而是采用了新的定义对象FilterDefinition,例如对于根据ID查询单个对象,接口的实现如下所示。

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual T FindByID(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();return collection.Find(s=> s.Id ==id).FirstOrDefault();
}

对于利用FilterDefinition进行查询的操作,如下所示。

        /// <summary>
        ///根据条件查询数据库,如果存在返回第一个对象/// </summary>
        /// <param name="filter">条件表达式</param>
        /// <returns>存在则返回指定的第一个对象,否则返回默认值</returns>
        public virtual T FindSingle(FilterDefinition<T>filter)
{
IMongoCollection
<T> collection =GetCollection();returncollection.Find(filter).FirstOrDefault();
}

我们可以看到,这些都是利用Find方法的不同重载实现不同条件的处理的。

对于这个新接口,异步是一个重要的改变,那么它的异步处理是如何的呢,我们看看上面两个异步的实现操作,具体代码如下所示。

        /// <summary>
        ///查询数据库,检查是否存在指定ID的对象(异步)/// </summary>
        /// <param name="key">对象的ID值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        public virtual async Task<T> FindByIDAsync(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();return await collection.FindAsync(s=>s.Id ==id).Result.FirstOrDefaultAsync();
}
/// <summary> ///根据条件查询数据库,如果存在返回第一个对象(异步)/// </summary> /// <param name="query">条件表达式</param> /// <returns>存在则返回指定的第一个对象,否则返回默认值</returns> public virtual async Task<T> FindSingleAsync(FilterDefinition<T>query)
{
return awaitGetQueryable(query).SingleOrDefaultAsync();
}

我们看到,上面的Collection或者GetQueryable(query)返回的对象,都提供给了以Async结尾的异步方法,因此对异步的封装也是非常方便的,上面的
GetQueryable(query)是另外一个公共的实现方法,具体代码如下所示。

        /// <summary>
        ///返回可查询的记录源/// </summary>
        /// <param name="query">查询条件</param>
        /// <returns></returns>
        public virtual IFindFluent<T, T> GetQueryable(FilterDefinition<T>query)
{
return GetQueryable(query, this.SortPropertyName, this.IsDescending);
}
        /// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IFindFluent<T,T> GetQueryable(FilterDefinition<T> query, string sortPropertyName, bool isDescending = true)
{
IMongoCollection
<T> collection =GetCollection();
IFindFluent
<T, T> queryable =collection.Find(query);var sort = this.IsDescending ? Builders<T>.Sort.Descending(this.SortPropertyName) : Builders<T>.Sort.Ascending(this.SortPropertyName);returnqueryable.Sort(sort);
}

我们可以看到,它返回了
IFindFluent<T, T>的对象,这个和以前返回的IMongoQuery对象又有不同,基本上,使用最新的接口,所有的实现都不太一样,这也是因为MongoDB还在不停变化之中有关。

3、GetQueryable几种方式

为了简化代码,方便使用,我们对获取MongoDB的LINQ方式的处理做了简单的封装,提供了几个GetQueryable的方式,具体代码如下所示。

        /// <summary>
        ///返回可查询的记录源/// </summary>
        /// <returns></returns>
        public virtual IQueryable<T>GetQueryable()
{
IMongoCollection
<T> collection =GetCollection();
IQueryable
<T> query =collection.AsQueryable();return query.OrderBy(this.SortPropertyName, this.IsDescending);
}
        /// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IQueryable<T> GetQueryable<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true)
{
IMongoCollection
<T> collection =GetCollection();
IQueryable
<T> query =collection.AsQueryable();if (match != null)
{
query
=query.Where(match);
}
if (orderByProperty != null)
{
query
= isDescending ?query.OrderByDescending(orderByProperty) : query.OrderBy(orderByProperty);
}
else{
query
= query.OrderBy(this.SortPropertyName, isDescending);
}
returnquery;
}

以及基于FilterDefinition的条件处理,并返回IFindFluent<T,T>接口对象的代码如下所示。

        /// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IFindFluent<T,T> GetQueryable(FilterDefinition<T> query, string sortPropertyName, bool isDescending = true)
{
IMongoCollection
<T> collection =GetCollection();
IFindFluent
<T, T> queryable =collection.Find(query);var sort = this.IsDescending ? Builders<T>.Sort.Descending(this.SortPropertyName) : Builders<T>.Sort.Ascending(this.SortPropertyName);returnqueryable.Sort(sort);
}

4、集合的查询操作封装处理

基于上面的封装,对结合的查询,也是基于不同的条件进行处理,返回对应的列表的处理方式, 最简单的是利用GetQueryable方式进行处理,代码如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> Find(Expression<Func<T, bool>>match)
{
returnGetQueryable(match).ToList();
}

或者如下所示

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> Find(FilterDefinition<T>query)
{
returnGetQueryable(query).ToList();
}

以及对排序字段,以及升降序的处理操作如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        public virtual IList<T> Find<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true)
{
return GetQueryable<TKey>(match, orderByProperty, isDescending).ToList();
}
/// <summary> ///根据条件查询数据库,并返回对象集合/// </summary> /// <param name="query">条件表达式</param> /// <param name="orderByProperty">排序字段</param> /// <param name="isDescending">如果为true则为降序,否则为升序</param> /// <returns></returns> public virtual IList<T> Find<TKey>(FilterDefinition<T> query, string orderByProperty, bool isDescending = true)
{
returnGetQueryable(query, orderByProperty, isDescending).ToList();
}

以及利用这些条件进行分页的处理代码如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> FindWithPager(Expression<Func<T, bool>>match, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;

IQueryable
<T> query =GetQueryable(match);
info.RecordCount
=query.Count();returnquery.Skip(excludedRows).Take(pageSize).ToList();
}
/// <summary> ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary> /// <param name="query">条件表达式</param> /// <param name="info">分页实体</param> /// <returns>指定对象的集合</returns> public virtual IList<T> FindWithPager(FilterDefinition<T>query, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;var find =GetQueryable(query);
info.RecordCount
= (int)find.Count();returnfind.Skip(excludedRows).Limit(pageSize).ToList();
}

对于异步的封装处理,基本上也和上面的操作差不多,例如对于基础的查询,异步操作封装如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>指定对象的集合</returns>
        public virtual async Task<IList<T>> FindAsync(Expression<Func<T, bool>>match)
{
return awaitTask.FromResult(GetQueryable(match).ToList());
}
/// <summary> ///根据条件查询数据库,并返回对象集合/// </summary> /// <param name="query">条件表达式</param> /// <returns>指定对象的集合</returns> public virtual async Task<IList<T>> FindAsync(FilterDefinition<T>query)
{
return awaitGetQueryable(query).ToListAsync();
}

复杂一点的分页处理操作代码封装如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual async Task<IList<T>> FindWithPagerAsync(Expression<Func<T, bool>>match, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;

IQueryable
<T> query =GetQueryable(match);
info.RecordCount
=query.Count();var result =query.Skip(excludedRows).Take(pageSize).ToList();return awaitTask.FromResult(result);
}
/// <summary> ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary> /// <param name="query">条件表达式</param> /// <param name="info">分页实体</param> /// <returns>指定对象的集合</returns> public virtual async Task<IList<T>> FindWithPagerAsync(FilterDefinition<T>query, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < 1) ? 1: info.CurrenetPageIndex;int pageSize = (info.PageSize <= 0) ? 20: info.PageSize;int excludedRows = (pageindex - 1) *pageSize;var queryable =GetQueryable(query);
info.RecordCount
= (int)queryable.Count();return awaitqueryable.Skip(excludedRows).Limit(pageSize).ToListAsync();
}

5、增删改方法封装处理

对于常规的增删改操作,在新的MongoDB数据库驱动里面也修改了名称,使用的时候也需要进行调整处理了。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        public virtual voidInsert(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

IMongoCollection
<T> collection =GetCollection();
collection.InsertOne(t);
}

异步的操作实现如下所示。

        /// <summary>
        ///插入指定对象到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        public virtual asyncTask InsertAsync(T t)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");

IMongoCollection
<T> collection =GetCollection();awaitcollection.InsertOneAsync(t);
}

批量插入记录的操作如下所示。

        /// <summary>
        ///插入指定对象集合到数据库中/// </summary>
        /// <param name="list">指定的对象集合</param>
        public virtual void InsertBatch(IEnumerable<T>list)
{
ArgumentValidation.CheckForNullReference(list,
"传入的对象list为空");

IMongoCollection
<T> collection =GetCollection();
collection.InsertMany(list);
}

对应的异步操作处理如下所示,这些都是利用原生支持的异步处理接口实现的。

        /// <summary>
        ///插入指定对象集合到数据库中/// </summary>
        /// <param name="list">指定的对象集合</param>
        public virtual async Task InsertBatchAsync(IEnumerable<T>list)
{
ArgumentValidation.CheckForNullReference(list,
"传入的对象list为空");

IMongoCollection
<T> collection =GetCollection();awaitcollection.InsertManyAsync(list);
}

更新操作,有一种整个替换更新,还有一个是部分更新,它们两者是有区别的,如对于替换更新的操作,它的接口封装处理如下所示

        /// <summary>
        ///更新对象属性到数据库中/// </summary>
        /// <param name="t">指定的对象</param>
        /// <param name="id">主键的值</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Update(T t, stringid)
{
ArgumentValidation.CheckForNullReference(t,
"传入的对象t为空");
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");bool result = false;
IMongoCollection
<T> collection =GetCollection();//使用 IsUpsert = true ,如果没有记录则写入 var update = collection.ReplaceOne(s => s.Id == id, t, new UpdateOptions() { IsUpsert = true});
result
= update != null && update.ModifiedCount > 0;returnresult;
}

如果对于部分字段的更新,那么操作如下所示 ,主要是利用UpdateDefinition对象来指定需要更新那些字段属性及值等信息。

        /// <summary>
        ///封装处理更新的操作(部分字段更新)/// </summary>
        /// <param name="id">主键的值</param>
        /// <param name="update">更新对象</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Update(string id, UpdateDefinition<T>update)
{
ArgumentValidation.CheckForNullReference(update,
"传入的对象update为空");
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();var result = collection.UpdateOne(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true});return result != null && result.ModifiedCount > 0;
}

上面的异步更新操作如下所示。

        /// <summary>
        ///封装处理更新的操作(部分字段更新)/// </summary>
        /// <param name="id">主键的值</param>
        /// <param name="update">更新对象</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual async Task<bool> UpdateAsync(string id, UpdateDefinition<T>update)
{
ArgumentValidation.CheckForNullReference(update,
"传入的对象update为空");
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();var result = await collection.UpdateOneAsync(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true});var sucess = result != null && result.ModifiedCount > 0;return awaitTask.FromResult(sucess);
}

删除的操作也是类似的了,基本上和上面的处理方式接近,顺便列出来供参考学习。

        /// <summary>
        ///根据指定对象的ID,从数据库中删除指定对象/// </summary>
        /// <param name="id">对象的ID</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool Delete(stringid)
{
ArgumentValidation.CheckForEmptyString(id,
"传入的对象id为空");

IMongoCollection
<T> collection =GetCollection();var result = collection.DeleteOne(s=> s.Id ==id);return result != null && result.DeletedCount > 0;
}
/// <summary> ///根据指定对象的ID,从数据库中删除指定指定的对象/// </summary> /// <param name="idList">对象的ID集合</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> public virtual bool DeleteBatch(List<string>idList)
{
ArgumentValidation.CheckForNullReference(idList,
"传入的对象idList为空");

IMongoCollection
<T> collection =GetCollection();var query = Query.In("_id", newBsonArray(idList));var result = collection.DeleteMany(s =>idList.Contains(s.Id));return result != null && result.DeletedCount > 0;
}

如果根据条件的删除,也可以利用条件定义的两种方式,具体代码如下所示。

        /// <summary>
        ///根据指定条件,从数据库中删除指定对象/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        public virtual bool DeleteByExpression(Expression<Func<T, bool>>match)
{
IMongoCollection
<T> collection =GetCollection();
collection.AsQueryable().Where(match).ToList().ForEach(s
=> collection.DeleteOne(t => t.Id ==s.Id));return true;
}
/// <summary> ///根据指定条件,从数据库中删除指定对象/// </summary> /// <param name="match">条件表达式</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> public virtual bool DeleteByQuery(FilterDefinition<T>query)
{
IMongoCollection
<T> collection =GetCollection();var result =collection.DeleteMany(query);return result != null && result.DeletedCount > 0;
}

6、数据访问子类的封装和方法调用

好了,基本上上面大多数使用的方法都发布出来了,封装的原则就是希望数据访问层子类能够简化代码,减少不必要的复制粘贴,而且必要的时候, 也可以对具体的接口进行重写,实现更强大的处理控制。

例如对于上面的基类,我们在具体的集合对象封装的时候,需要继承于BaseDAL<T>这样的方式,这样可以利用基类丰富的接口,简化子类的代码,如User集合类的代码如下所示。

   /// <summary>
    ///User集合(表)的数据访问类/// </summary>
    public class User : BaseDAL<UserInfo>{/// <summary>
        ///默认构造函数/// </summary>
        publicUser() 
{
this.entitysName = "users";//对象在数据库的集合名称 }/// <summary> ///为用户增加岁数/// </summary> /// <param name="id">记录ID</param> /// <param name="addAge">待增加的岁数</param> /// <returns></returns> public bool IncreaseAge(string id, intaddAge)
{
var collection =GetCollection();var update = Builders<UserInfo>.Update.Inc(s =>s.Age, addAge);var result = collection.UpdateOne(s => s.Id ==id, update);return result != null && result.ModifiedCount > 0;
}
/// <summary> ///单独修改用户的名称/// </summary> /// <param name="id">记录ID</param> /// <param name="newName">用户新名称</param> /// <returns></returns> public bool UpdateName(string id, stringnewName)
{
var collection =GetCollection();var update = Builders<UserInfo>.Update.Set(s =>s.Name, newName);var result = collection.UpdateOne(s => s.Id ==id, update);return result != null && result.ModifiedCount > 0;
}
}

在界面层使用的时候,只需要声明一个对应的User数据访问类dal对象,就可以利用它的相关接口进行对应的数据操作了,如下代码所示。

            IList<UserInfo> members = dal.Find(s => s.Name.StartsWith("Test"));foreach (UserInfo info inmembers)
{
Console.WriteLine(info.Id
+ "," +info.Name);
}
            var user = dal.FindSingle(s => s.Id == "56815e6634ab091e1406ec68");if(user != null)
{
Console.WriteLine(user.Name);
}

对于部分字段的更新处理,在界面上,我们可以利用封装好的接口进行处理,如下所示。

        /// <summary>
        ///测试部分字段修改的处理/// </summary>
        private void btnAddAge_Click(objectsender, EventArgs e)
{
UserInfo info
= dal.GetAll()[0];if(info != null)
{
Console.WriteLine(
"Age before Incr:" +info.Age);int addAge = 10;
dal.IncreaseAge(info.Id, addAge);

info
=dal.FindByID(info.Id);
Console.WriteLine(
"Age after Incr:" +info.Age);


Console.WriteLine(
"Name before modify:" +info.Name);var update = Builders<UserInfo>.Update.Set(s => s.Name, info.Name +DateTime.Now.Second);
dal.Update(info.Id, update);

info
=dal.FindByID(info.Id);
Console.WriteLine(
"Name after modify:" +info.Name);
}
}

对于异步接口的调用代码,如下所示。

        /// <summary>
        ///异步操作的调用/// </summary>
        private async void btnAsync_Click(objectsender, EventArgs e)
{
UserInfo newInfo
= newUserInfo();
newInfo.Name
= "Ping" +DateTime.Now.ToString();
newInfo.Age
=DateTime.Now.Minute;
newInfo.Hobby
= "乒乓球";awaitdal.InsertAsync(newInfo);var list = await dal.FindAsync(s => s.Age < 30);foreach (UserInfo info inlist)
{
Console.WriteLine(info.Id
+ "," +info.Name);
}
Console.WriteLine(newInfo.Id);
}

在前面介绍了三篇关于MongoDB数据库的开发使用文章,严格来讲这个不能归类于MongoDB数据库开发,不过Redis又有着和MongoDB数据库非常密切的关系,它们两者很接近,Redis主要是内存中的NoSQL数据库,用来提高性能的;MongoDB数据库则是文件中的NoSQL数据库,做数据序列号存储使用的,它们两者关系密切又有所区别。本篇主要介绍Redis的安装及使用,为后面Redis和MongoDB数据库的联合使用先铺下基础。

1、Redis基础及安装

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis的代码遵循ANSI-C编写,可以在所有POSIX系统(如Linux,
*
BSD, Mac OS X, Solaris等)上安装运行。而且Redis并不依赖任何非标准库,也没有编译参数必需添加。

1)Redis支持两种持久化方式:

(1):snapshotting(快照)也是默认方式.(把数据做一个备份,将数据存储到文件)

(2)Append-only file(缩写aof)的方式

快照是默认的持久化方式,这种方式是将内存中数据以快照的方式写到二进制文件中,默认的文件名称为dump.rdb.可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key键修改就自动做快照.

aof方式:由于快照方式是在一定间隔时间做一次的,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

2)Redis数据结构

Redis 的作者antirez曾称其为一个数据结构服务器(
data structures server
),这是一个非常准确的表述,Redis的所有功能就是将数据以其固有的几种结构保存,并提供给用户操作这几种结构的接口。我们可以想象我们在各种语言中的那些固有数据类型及其操作。

Redis目前提供四种数据类型:
string
,
list
,
set

zset
(sorted set)和
Hash

  • string
    是最简单的类型,你可以理解成与Memcached一模一个的类型,一个key对应一个value,其上支持的操作与Memcached的操作类似。但它的功能更丰富。
  • list
    是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等。操作中key理解为链表的名字。
  • set
    是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作。操作中key理解为集合的名字。
  • zset
    是set的一个升级版本,他在set的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset会自动重新按新的值调整顺序。可以理解了有两列的mysql表,一列存value,一列存顺序。操作中key理解为zset的名字。
  • Hash
    数据类型允许用户用Redis存储对象类型,Hash数据类型的一个重要优点是,当你存储的数据对象只有很少几个key值时,数据存储的内存消耗会很小.更多关于Hash数据类型的说明请见:
    http://code.google.com/p/redis/wiki/Hashes

3)Redis数据存储

Redis的存储分为内存存储、磁盘存储和log文件三部分,配置文件中有三个参数对其进行配置。

save seconds updates

save
配置,指出在多长时间内,有多少次更新操作,就将数据同步到数据文件。这个可以多个条件配合,比如默认配置文件中的设置,就设置了三个条件。

appendonly yes
/
no

appendonly
配置,指出是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面的save条件来同步的,所以有的数据会在一段时间内只存在于内存中。

appendfsync no
/
always
/
everysec

appendfsync
配置,
no
表示等操作系统进行数据缓存同步到磁盘,
always
表示每次更新操作后手动调用
fsync
()将数据写到磁盘,
everysec
表示每秒同步一次。

4)Redis的安装

Redis可以在不同的平台运行,不过我主要基于Windows进行开发工作,所以下面主要是基于Windows平台进行介绍。

Redis可以安装以DOS窗口启动的,也可以安装为Windows服务的,一般为了方便,我们更愿意把它安装为Windows服务,这样可以比较方便管理。下载地址:
https://github.com/MSOpenTech/redis/releases
下载,安装为Windows服务即可。

当前可以下载到最新的Windows安装版本为3.0,安装后作为Windows服务运行,安装后可以在系统的服务里面看到Redis的服务在运行了,如下图所示。

安装好Redis后,还有一个Redis伴侣Redis Desktop Manager需要安装,这样可以实时查看Redis缓存里面有哪些数据,具体地址如下:
http://redisdesktop.com/download

下载属于自己平台的版本即可

下载安装后,打开运行界面,如果我们往里面添加键值的数据,那么可以看到里面的数据了。

2、Redis的C#使用

Redis目前提供四种数据类型:
string
,
list
,
set

zset
(sorted set)和
Hash
。因此它在C#里面也有对应的封装处理,而且有很多人对他进行了封装,提供了很多的响应开发包,具体可以访问
http://redis.io/clients#c
进行了解。一般建议用ServiceStack.Redis的封装驱动比较好,具体的使用可以参考
https://github.com/ServiceStack/ServiceStack.Redis

我们开发C#代码的时候,可以在NuGet程序包上面进行添加对应的
ServiceStack.Redis引用,如下所示。

在弹出的NuGet程序包里面,输入ServiceStack.Redis进行搜索,并添加下面的驱动引用即可。

这样会在项目引用里面添加了几个对应的程序集,如下所示。

在C#里面使用Redis,首先需要实例化一个Redis的客户端类,如下所示。

        //创建一个Redis的客户端类
        RedisClient client = new RedisClient("127.0.0.1", 6379);

在使用前,我们需要清空所有的键值存储,使用FushAll方法即可,如下所示

            //Redis FlushAll 命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
            client.FlushAll();

根据上面的驱动,可以为不同类型的处理编写一些演示代码,下面代码是摘录网上的案例进行介绍。

            #region string类型的测试代码client.Add<string>("StringValueTime", "带有有效期的字符串", DateTime.Now.AddMilliseconds(10000));while (true)
{
if (client.ContainsKey("StringValueTime"))
{
Console.WriteLine(
"String.键:StringValue, 值:{0} {1}", client.Get<string>("StringValueTime"), DateTime.Now);
Thread.Sleep(
10000);
}
else{
Console.WriteLine(
"键:StringValue, 值:已过期 {0}", DateTime.Now);break;
}
}

client.Add
<string>("StringValue", "String和Memcached操作方法差不多");
Console.WriteLine(
"数据类型为:String.键:StringValue, 值:{0}", client.Get<string>("StringValue"));

Student stud
= new Student() { id = "1001", name = "李四"};
client.Add
<Student>("StringEntity", stud);
Student Get_stud
= client.Get<Student>("StringEntity");
Console.WriteLine(
"数据类型为:String.键:StringEntity, 值:{0} {1}", Get_stud.id, Get_stud.name);#endregion #region Hash类型的测试代码client.SetEntryInHash("HashID", "Name", "张三");
client.SetEntryInHash(
"HashID", "Age", "24");
client.SetEntryInHash(
"HashID", "Sex", "");
client.SetEntryInHash(
"HashID", "Address", "上海市XX号XX室");

List
<string> HaskKey = client.GetHashKeys("HashID");foreach (string key inHaskKey)
{
Console.WriteLine(
"HashID--Key:{0}", key);
}

List
<string> HaskValue = client.GetHashValues("HashID");foreach (string value inHaskValue)
{
Console.WriteLine(
"HashID--Value:{0}", value);
}

List
<string> AllKey = client.GetAllKeys(); //获取所有的key。 foreach (string Key inAllKey)
{
Console.WriteLine(
"AllKey--Key:{0}", Key);
}
#endregion #region List类型的测试代码 /** list是一个链表结构,主要功能是push,pop,获取一个范围的所有的值等,操作中key理解为链表名字。
* Redis的list类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素,
* 这样list既可以作为栈,又可以作为队列。Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,
* Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构
*/client.EnqueueItemOnList("QueueListId", "1.张三"); //入队 client.EnqueueItemOnList("QueueListId", "2.张四");
client.EnqueueItemOnList(
"QueueListId", "3.王五");
client.EnqueueItemOnList(
"QueueListId", "4.王麻子");long q = client.GetListCount("QueueListId");

Console.WriteLine(client.GetItemFromList(
"QueueListId", 0));for (int i = 0; i < q; i++)
{
Console.WriteLine(
"QueueListId出队值:{0}", client.DequeueItemFromList("QueueListId")); //出队(队列先进先出) }

q
= client.GetListCount("QueueListId");
Console.WriteLine(q);

client.PushItemToList(
"StackListId", "1.张三"); //入栈 client.PushItemToList("StackListId", "2.张四");
client.PushItemToList(
"StackListId", "3.王五");
client.PushItemToList(
"StackListId", "4.王麻子");long p = client.GetListCount("StackListId");for (int i = 0; i < p; i++)
{
Console.WriteLine(
"StackListId出栈值:{0}", client.PopItemFromList("StackListId")); //出栈(栈先进后出) }
q
= client.GetListCount("StackListId");
Console.WriteLine(q);
#endregion #region Set无序集合的测试代码 /*它是string类型的无序集合。set是通过hash table实现的,添加,删除和查找,对集合我们可以取并集,交集,差集*/client.AddItemToSet("Set1001", "小A");
client.AddItemToSet(
"Set1001", "小B");
client.AddItemToSet(
"Set1001", "小C");
client.AddItemToSet(
"Set1001", "小D");
HashSet
<string> hastsetA = client.GetAllItemsFromSet("Set1001");foreach (string item inhastsetA)
{
Console.WriteLine(
"Set无序集合ValueA:{0}", item); //出来的结果是无须的 }

client.AddItemToSet(
"Set1002", "小K");
client.AddItemToSet(
"Set1002", "小C");
client.AddItemToSet(
"Set1002", "小A");
client.AddItemToSet(
"Set1002", "小J");
HashSet
<string> hastsetB = client.GetAllItemsFromSet("Set1002");foreach (string item inhastsetB)
{
Console.WriteLine(
"Set无序集合ValueB:{0}", item); //出来的结果是无须的 }

HashSet
<string> hashUnion = client.GetUnionFromSets(new string[] { "Set1001", "Set1002"});foreach (string item inhashUnion)
{
Console.WriteLine(
"求Set1001和Set1002的并集:{0}", item); //并集 }

HashSet
<string> hashG = client.GetIntersectFromSets(new string[] { "Set1001", "Set1002"});foreach (string item inhashG)
{
Console.WriteLine(
"求Set1001和Set1002的交集:{0}", item); //交集 }

HashSet
<string> hashD = client.GetDifferencesFromSet("Set1001", new string[] { "Set1002" }); //[返回存在于第一个集合,但是不存在于其他集合的数据。差集] foreach (string item inhashD)
{
Console.WriteLine(
"求Set1001和Set1002的差集:{0}", item); //差集 }#endregion #region SetSorted 有序集合的测试代码 /*sorted set 是set的一个升级版本,它在set的基础上增加了一个顺序的属性,这一属性在添加修改.元素的时候可以指定,
* 每次指定后,zset(表示有序集合)会自动重新按新的值调整顺序。可以理解为有列的表,一列存 value,一列存顺序。操作中key理解为zset的名字.
*/client.AddItemToSortedSet("SetSorted1001", "1.刘仔");
client.AddItemToSortedSet(
"SetSorted1001", "2.星仔");
client.AddItemToSortedSet(
"SetSorted1001", "3.猪仔");
List
<string> listSetSorted = client.GetAllItemsFromSortedSet("SetSorted1001");foreach (string item inlistSetSorted)
{
Console.WriteLine(
"SetSorted有序集合{0}", item);
}
#endregion

对于具体类型的类对象,也可以使用As方法进行转换为对应的处理对象进行处理,如下所示

IRedisTypedClient<Phone> phones = client.As<Phone>();

具体的测试代码如下所示。

        /// <summary>
        ///Redis对对象类的处理例子/// </summary>
        private void btnTypeValue_Click(objectsender, EventArgs e)
{
IRedisTypedClient
<Phone> phones = client.As<Phone>();
Phone phoneFive
= phones.GetValue("5");if (phoneFive == null)
{
Thread.Sleep(
50);
phoneFive
= newPhone
{
Id
= 5,
Manufacturer
= "Apple",
Model
= "xxxxx",
Owner
= newPerson
{
Id
= 1,
Age
= 100,
Name
= "伍华聪",
Profession
= "计算机",
Surname
= "wuhuacong"}
};

phones.SetEntry(phoneFive.Id.ToString(), phoneFive);
}

client.Store
<Phone>(newPhone
{
Id
= 2,
Manufacturer
= "LG",
Model
= "test-xxx",
Owner
= newPerson
{
Id
= 2,
Age
= 40,
Name
= "test",
Profession
= "teacher",
Surname
= "wuhuacong"}
});
var message = "Phone model is" + phoneFive.Manufacturer + ",";
message
+= "Phone Owner Name is:" +phoneFive.Owner.Name;
Console.WriteLine(message);
}

以上就是关于Redis的安装以及简单的例子使用说明,在具体中,我们可以利用Redis的高性能特性,来构建我们的缓存数据,并且可以利用Redis和MongoDB数据库的完美衔接,可以整合一起做的更好,为相关的后台提供更高效的数据处理操作,毕竟在互联网的大环境下,性能是非常重要的。