wenmo8 发布的文章

在上篇博客《
基于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数据库的完美衔接,可以整合一起做的更好,为相关的后台提供更高效的数据处理操作,毕竟在互联网的大环境下,性能是非常重要的。

在前面介绍了很多篇相关的《
Bootstrap开发框架
》的系列文章,这些内容基本上覆盖到了我这个Bootstrap框架的各个主要方面的内容,总体来说基本达到了一个稳定的状态,随着时间的推移可以会引入一些更好更新的内容进行完善,本篇继续这个系列,主要介绍如何实现Web页面内容的打印预览和保存操作。

1、Web页面打印的问题

在此之前,我一般使用比较好用的LODOP来执行打印的操作,这个在我之前有很多文章都有涉及,这个控件是一个ActiveX的控件,需要下载安装后就可以在页面是进行打印的排版设计,预览,打印等操作,还是很方便的一个控件,因此都很适合普通内容的打印,证件的套打等操作。

不过随着浏览器技术的更新,这个插件在Chrome或者FireFox上好像不受支持了,基本上摒弃了这种插件的处理方式了。例如如果我在页面上需要打印对话框里面的内容,如下所示。

如果按正常使用LODOP的方式来进行处理的话,那么会得到Chrome浏览器的提示,并且这个不管你重新下载安装、更新LODOP控件,都会继续这个错误提示的。

这个在一篇《
Lodop页面总提示"未安装"或"请升级"的可能原因
》,以及《
Lodop用户应对谷歌浏览器停用Plugin技术的处理办法
》都有说明,因此我们需要另辟蹊径来处理这个页面打印等的处理了。

对于替代方式,这里就是本篇内容介绍的主题了,我一直喜欢寻找一些比较好的方式的方式来实现自己需要的功能,于是找到了PrintThis的这个插件(
https://github.com/jasonday/printThis
)以及jquery-print-preview-plugin(
https://github.com/etimbo/jquery-print-preview-plugin
),对比两者我比较喜欢第一个的简洁方便的使用。

2、PrintThis打印插件的使用

有了上面的问题,我们引入一个新的打印方式,也就是JQuery插件来实现我们所需要页面内容的打印操作。

这个插件的使用非常简洁方便,首先需要在页面里面引入对应的JS文件,如下所示。

<scriptsrc="~/Content/JQueryTools/printThis/printThis.js"></script>

我们再在页面顶部增加两个按钮,如打印和导出操作,代码如下所示

<divclass="toolbar">
 <ahref="#"onclick="javascript:Preview();"><imgalt="打印预览"src="~/Content/images/print.gif" /><br/>打印预览</a>
 <ahref="#"onclick="javascript:SaveAs();"><imgalt="另存为"src="~/Content/images/saveas.gif" /><br/>另存为</a>         
</div>

然后我们还需要声明一个DIV用来放置显示的Web页面内容,这样也方便对它调用进行打印操作。

我们打印的处理代码也很简单,就是直接对层进行打印处理就可以了,可以看到下面的使用代码非常简单。

        //打印预览
        functionPreview() {
$(
"#printContent").printThis({
debug:
false,
importCSS:
true,
importStyle:
true,
printContainer:
true,
loadCSS:
"/Content/Themes/Default/style.css",
pageTitle:
"通知公告",
removeInline:
false,
printDelay:
333,
header:
null,
formValues:
true});
};

打印执行后,IE和Chrome都会弹出一个打印预览对话框,确认是否进行打印的操作。

3、页面内容的保存操作

有时候,为了方便业务处理,我们一般也可以提供给用户一个导出打印内容的操作,如下所示代码就是把打印的内容导出到Word里面给用户加工等用途。

        functionSaveAs() {var id = $('#ID2').val();
window.open(
'/Information/ExportWordById?id=' +id );
}

上面的操作,主要就是调用了MVC的控制器方法进行处理,传入一个id就可以把内容提取出来,然后把它生成所需的Word内容即可。

这里后台我们主要利用Apose.Word控件来进行模板化的文档生成,具体可以参考一下我前面介绍过的使用方法文章《
利用Aspose.Word控件实现Word文档的操作
》、《
利用Aspose.Word控件和Aspose.Cell控件,实现Word文档和Excel文档的模板化导出
》。

其中我们可以在书签里面定义或者查看一些书签的信息,如下图所示。

这样我们在代码里面,就可以获取信息并指定这个Word模板了。

            InformationInfo info = BLLFactory<Information>.Instance.FindByID(id);if (info != null)
{
string template = "~/Content/Template/政策法规模板.doc";string templateFile =Server.MapPath(template);
Aspose.Words.Document doc
= new Aspose.Words.Document(templateFile);

WORD模板的内容,可以使用文本替换方式,如下所示。

SetBookmark(ref doc, "Content", info.Content);

也可以使用书签BookMark方式查询替换,如下代码所示。

            Aspose.Words.Bookmark bookmark =doc.Range.Bookmarks[title];if (bookmark != null)
{
bookmark.Text
=value;
}

对于主体的HTML内容,这需要特殊对待,一般需要使用插入HTML的专用方式进行写入内容,否则就显示HTML代码了,使用专用HTML方法写入的内容,和我们在网页上看到的基本没有什么差异了。如下代码所示。

                DocumentBuilder builder = newDocumentBuilder(doc);
Aspose.Words.Bookmark bookmark
= doc.Range.Bookmarks["Content"];if (bookmark != null)
{
builder.MoveToBookmark(bookmark.Name);
builder.InsertHtml(info.Content);
}

整个导入WORD文档的方法就是利用这些内容的整合,实现一个标准文档的生成,这种业务文档是固定模板的,因此很适合在实际业务中使用,比起使用其他方式自动生成的HTML文件或者文档,有更好的可塑性和美观性。

整个代码如下所示。

        public FileStreamResult ExportWordById(stringid)
{
if (string.IsNullOrEmpty(id)) return null;

InformationInfo info
= BLLFactory<Information>.Instance.FindByID(id);if (info != null)
{
string template = "~/Content/Template/政策法规模板.doc";string templateFile =Server.MapPath(template);
Aspose.Words.Document doc
= newAspose.Words.Document(templateFile);#region 使用文本方式替换 //Dictionary<string, string> dictSource = new Dictionary<string, string>();//dictSource.Add("Title", info.Title);//dictSource.Add("Content", info.Content);//dictSource.Add("Editor", info.Editor);//dictSource.Add("EditTime", info.EditTime.ToString());//dictSource.Add("SubType", info.SubType);//foreach (string name in dictSource.Keys)//{//doc.Range.Replace(name, dictSource[name], true, true);//} #endregion //使用书签方式替换 SetBookmark(ref doc, "Title", info.Title);
SetBookmark(
ref doc, "Editor", info.Editor);
SetBookmark(
ref doc, "EditTime", info.EditTime.ToString());
SetBookmark(
ref doc, "SubType", info.SubType);//SetBookmark(ref doc, "Content", info.Content);//对于HTML内容,需要通过InsertHtml方式进行写入 DocumentBuilder builder = newDocumentBuilder(doc);
Aspose.Words.Bookmark bookmark
= doc.Range.Bookmarks["Content"];if (bookmark != null)
{
builder.MoveToBookmark(bookmark.Name);
builder.InsertHtml(info.Content);
}

doc.Save(System.Web.HttpContext.Current.Response, info.Title, Aspose.Words.ContentDisposition.Attachment,
Aspose.Words.Saving.SaveOptions.CreateSaveOptions(Aspose.Words.SaveFormat.Doc));

HttpResponseBase response
=ControllerContext.HttpContext.Response;
response.Flush();
response.End();
return new FileStreamResult(Response.OutputStream, "application/ms-word");
}
return null;
}
private void SetBookmark(ref Aspose.Words.Document doc, string title, stringvalue)
{
Aspose.Words.Bookmark bookmark
=doc.Range.Bookmarks[title];if (bookmark != null)
{
bookmark.Text
=value;
}
}

最后导出的WORD文档就是模板化的具体文档内容了,WORD预览界面如下所示。

如果有兴趣,可以继续参考系列文章:

基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理

基于Metronic的Bootstrap开发框架经验总结(2)--列表分页处理和插件JSTree的使用


基于Metronic的Bootstrap开发框架经验总结(3)--下拉列表Select2插件的使用

基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用

基于Metronic的Bootstrap开发框架经验总结(5)--Bootstrap文件上传插件File Input的使用


基于Metronic的Bootstrap开发框架经验总结(6)--对话框及提示框的处理和优化

基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理


基于Metronic的Bootstrap开发框架经验总结(8)--框架功能总体界面介绍


基于Metronic的Bootstrap开发框架经验总结(9)--实现Web页面内容的打印预览和保存操作

在代码生成工具的各种功能规划中,我们一向以客户的需求作为驱动,因此也会根据需要增加一些特殊的功能或者处理。在实际的开发中,虽然我们一般以具体的表进行具体业务开发,但是有些客户提出有时候视图开发也是很常见的,为了提高代码生成和界面生成的效率,基于视图开发的过程也应该支持。还有主从表的界面生成操作,在很多实际的业务领域也是很常见的。基于上面的需求,本次代码生成工具Database2Sharp中增加视图的代码生成以及主从表界面生成功能,为客户的高效率开发快马加鞭、保驾护航。

1、基于视图的代码生成和界面生成操作

为了支持视图的相关代码生成,我们把代码生成工具底层的元数据进行了优化整合,是指在代码生成方面,具体的表和视图不再有具体的差异,基本上都是可以统一对待,实现快速的框架代码生成、Winform界面生成、Web界面生成操作的,所有的表的相关属性,视图也具有,因此在代码模板方便,不需要进行调整,兼容了代码模板的属性处理,提高了已有代码模板的安全性。

如上所示,为了区分表和视图的位置,我们把视图统一放在了表的后面,方便选择处理,在选择的时候,表和视图都是一视同仁,所以代码生成的处理适用于表的,也是适用于视图的,可以统一一并生成,极大的提高了代码生成的效率。

当然,视图的增删改操作,和表毕竟不一样,一般视图只是为了查询数据进行展示的,所以在实际开发的时候,可以适当屏蔽视图的增删改操作,或者自定义相关的接口进行处理。

在Winform界面的生成的时候,我们也一样整合了视图的列表,可以基于视图进行界面代码的生成。

不过我们注意到,一般表我们使用备注信息作为Winform界面的字段说明信息的,如果是视图,那么是无法获取到它的视图字段备注信息的,因为视图的字段备注是不存在的,为了实现和表一样具有中文备注的界面,我们增加了一个对视图字段进行备注信息维护的界面,有着字段的备注,我们生成Winform界面的时候,对应字段的标签就有中文信息了。

通过Enterprise Library架构生成的相关代码,也同时具有相关的备注信息,如下代码所示。

通过代码生成工具里面的Winform界面代码生成,当然也会具有相关的备注信息,可以在界面上显示对应的中文标签信息了。

视图的Winform界面代码生成和普通的表生成的Winform界面操作过程一样,具体界面操作如下所示。

这样生成的Winform界面操作和普通表的处理方式一致,而且对应的视图字段也有了备注信息,因此在界面上的标签说明也就和表一样,可以显示备注信息了。

2、主从表的界面生成操作

在有些情况下,有些业务表是具有主从关系的,如一个是汇总信息,一个是明细信息,如仓库的入库、出库操作,会员的消费操作,都是典型的主从表应用场景,可以把它们作为一个界面生成的案例进行处理。

标准的主从表界面如下界面所示。

或者会员消费信息的横向界面展示如下所示。

在我之前的代码生成界面里面,主要是生成标准的分页列表展示,以及编辑明细两个标准界面,为了更加丰富开发工具的界面生成,根据上面两种界面的综合情况,增加一个主从表的界面生成操作,这样可以更加适应实际的业务开发工作,高效进行界面的快速生成。

如在代码生成工具里面,设置主从表的界面生成如下所示。

生成Winform界面代码后,在VS编辑器里面,可以看到如下所示界面。

最后在实际程序上运行生成的界面,就可以看到预览的界面效果了,界面效果如下所示。

为了方便,上面界面增加了一个复选框,用来切换横向或者纵向展示明细列表的,勾选后进行横向显示,如下所示。

上面主从表的展示,还包括了明细表信息的汇总功能,这样基本上满足了主从表的信息展示了,如果需要,还可以在这基础上进行更方便的改进了。