wenmo8 发布的文章

在上篇随笔《
Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
》里面,介绍了在Entity Framework 实体框架里面引入了DTO的对象,通过数据传输模型DTO和实体模型Entity的分离与联合,很好的隔离了它们的关系,使得即使是复杂的实体模型Entity,也不会影响WCF接口数据的传输和处理。本文主要介绍在基于这个分离模型的基础上,如何在界面实现多种常规的处理操作。

1、常规业务的增加、更新操作

对于业务对象的增加,由于我们引入了DTO对象,因此在界面的处理端,肯定也是利用了DTO对象进行的,如下代码是增加、修改的处理操作处理。

       public boolSaveAddNew()
{
DictDataInfo info
= newDictDataInfo();
SetInfo(info);
try{bool succeed = CallerFactory<IDictDataService>.Instance.Insert(info);if(succeed)
{
int intSeq = 0;string seqValue = this.txtSeq.Text;if (int.TryParse(seqValue, outintSeq))
{
this.txtSeq.Text = (intSeq + 1).ToString().PadLeft(seqValue.Trim().Length, '0');
}
this.txtName.Focus();this.txtName.SelectAll();
}
returnsucceed;
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
return false;
}
public override boolSaveUpdated()
{
DictDataInfo info
= CallerFactory<IDictDataService>.Instance.FindByID(ID);if (info != null)
{
SetInfo(info);
try{bool succeed = CallerFactory<IDictDataService>.Instance.Update(info, info.ID.ToString());returnsucceed;
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
}
return false;
}

上面的操作,和我之前的混合框架的使用代码是差不多的,原来的基于EnterpriseLibrary架构的框架,实体类采用的就是 "表名+Info" 的方式,虽然这里的**Info代表DTO对象,是实体框架的Entity对象的映射类,不过总体业务上的处理代码是差不多的了,这也是我希望看到比较平滑过渡和容易理解的改变之一。

2、基于DTO表达式的查询处理

如果对于查询,我们知道,如果使用字符串的条件表达式,一般也是可以实现处理操作的,不过就是需要硬编码SQL语句,对于一些安全性高一点的处理,可能不太好,由于实体框架可以采用Lamda表达式来进行查询,那么我们是否也可以在界面采用Lamda表达式来替代条件的SQL语句呢?

我们知道,上篇随笔已经介绍了引入DTO对象,用来解耦实体框架的对象模型,如下所示的模块分层场景。

这样我们在设计BLL业务逻辑层的时候,肯定还是可以使用实体框架的Expression<Func<T, bool>>表达式的,如IBLL层的接口定义对于Expression表达式的使用接口如下所示。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        IList<T> Find(Expression<Func<T, bool>>match);/// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="sortPropertyName">排序属性名称</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true);

不过在门面层Facade层就不能继续使用了这种Expression<Func<T, bool>>表达式的了,同时也不能在Facade层使用
IQueryable<T>接口,因为WCF服务无法序列化这个接口的。

那基于这个原因,我们应该如何传递Expression<Func<T, bool>> match这个条件参数的表达式呢,答案是引入Serialize.Linq组件,使用ExpressionNode对象进行承载,最后再把它解析为Expression<Func<T, bool>> match进行处理就可以了。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        [OperationContract(Name = "Find")]
IList
<DTO>Find(ExpressionNode match);/// <summary> ///根据条件查询数据库,并返回对象集合(异步)/// </summary> /// <param name="match">条件表达式</param> /// <returns></returns> [OperationContract(Name = "FindAsync")]
Task
<IList<DTO>> FindAsync(ExpressionNode match);

我们在客户端界面里面处理的话,就需要构建一个ExpressionNode对象,查询处理代码如下所示。

这里主要需要先从Expression<Function<T,boo>>到ExpressionNode,通过调用expression.ToExpressionNode();进行处理得到,如下代码所示。

        privateExpressionNode GetCondtionSql()
{
Expression
<Func<DictDataInfo, bool>> expression = p => p.DictType_ID == this.lblDictType.Tag.ToString();
var queryNode = expression.ToExpressionNode();
return
queryNode;
}
private voidBindData()
{
#region 添加别名解析 this.winGridViewPager1.DisplayColumns = "Name,Value,Seq,Remark,EditTime";this.winGridViewPager1.AddColumnAlias("ID", "编号");this.winGridViewPager1.AddColumnAlias("DictType_ID", "字典大类");this.winGridViewPager1.AddColumnAlias("Name", "项目名称");this.winGridViewPager1.AddColumnAlias("Value", "项目值");this.winGridViewPager1.AddColumnAlias("Seq", "字典排序");this.winGridViewPager1.AddColumnAlias("Remark", "备注");this.winGridViewPager1.AddColumnAlias("Editor", "修改用户");this.winGridViewPager1.AddColumnAlias("EditTime", "更新日期");#endregion if (this.lblDictType.Tag != null)
{
ExpressionNode condition
=GetCondtionSql();
WHC.Pager.Entity.PagerInfo pagerInfo
= this.winGridViewPager1.PagerInfo;
IList
<DictDataInfo> list = CallerFactory<IDictDataService>.Instance.FindWithPager(condition, refpagerInfo);//this.winGridViewPager1.PagerInfo.RecordCount = pagerInfo.RecordCount; this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<DictDataInfo>(list);
}
}

我们在Facade接口实现端,就需要把ExpressionNode反过来变成Expression<Function<T,boo>>对象。

        /// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        public virtual IList<DTO>Find(ExpressionNode match)
{
Expression
<Func<Entity, bool>> mappedSelector =ConvertExpression(match);

IList
<Entity> tList =baseBLL.Find(mappedSelector);return tList.MapToList<Entity, DTO>();
}
/// <summary> ///根据条件查询数据库,并返回对象集合(异步)/// </summary> /// <param name="match">条件表达式</param> /// <returns></returns> public virtual async Task<IList<DTO>>FindAsync(ExpressionNode match)
{
Expression
<Func<Entity, bool>> mappedSelector =ConvertExpression(match);

IList
<Entity> tList = awaitbaseBLL.FindAsync(mappedSelector);

IList
<DTO> collection = tList.MapToList<Entity, DTO>();return await Task<IList<DTO>>.FromResult(collection);
}

这样我们就可以很好利用Entity Framework 实体框架的LINQ表达式进行查询了。

3、多条件的处理方式

上面的查询代码里面,我们注意到了,条件里面只有一个条件,如下代码。

        privateExpressionNode GetCondtionSql()
{
Expression
<Func<DictDataInfo, bool>> expression = p => p.DictType_ID == this.lblDictType.Tag.ToString();var queryNode =expression.ToExpressionNode();returnqueryNode;
}

那么对于有多个条件的话,处理就需要特殊处理了,否则就没法组合多个条件进行查询了,多个条件的处理是如何的呢?

如对于日志查询界面来说,如果是采用条件语句的方式,需要使用下面的代码组装语句,然后通过接口方法进行获取数据。

        /// <summary>
        ///根据查询条件构造查询语句/// </summary> 
        private stringGetConditionSql()
{
SearchCondition condition
= newSearchCondition();
condition.AddCondition(
"LoginName", this.txtLoginName.Text, SqlOperator.Like);
condition.AddCondition(
"FullName", this.txtRealName.Text, SqlOperator.Like);
condition.AddCondition(
"Note", this.txtNote.Text, SqlOperator.Like);
condition.AddCondition(
"IPAddress", this.txtIPAddress.Text, SqlOperator.Like);
condition.AddCondition(
"MacAddress", this.txtMacAddress.Text, SqlOperator.Like);if (dateTimePicker1.Text.Length > 0)
{
condition.AddCondition(
"LastUpdated", Convert.ToDateTime(dateTimePicker1.DateTime.ToString("yyyy-MM-dd")), SqlOperator.MoreThanOrEqual);
}
if (dateTimePicker2.Text.Length > 0)
{
condition.AddCondition(
"LastUpdated", Convert.ToDateTime(dateTimePicker2.DateTime.AddDays(1).ToString("yyyy-MM-dd")), SqlOperator.LessThanOrEqual);
}
string systemType = this.txtSystemType.GetComboBoxValue();if (!string.IsNullOrEmpty(systemType))
{
condition.AddCondition(
"SystemType_ID", systemType, SqlOperator.Equal);
}
//如果是公司管理员,增加公司标识 if(Portal.gc.UserInRole(RoleInfo.CompanyAdminName))
{
condition.AddCondition(
"Company_ID", Portal.gc.UserInfo.Company_ID, SqlOperator.Equal);
}
string where = condition.BuildConditionSql().Replace("Where", "");//如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的 if (!string.IsNullOrEmpty(treeConditionSql))
{
where =treeConditionSql;
}
return where;
}

这里有很多条件,通过 SearchCondition 对象,我们能够很方便组合多个条件的查询,然后生成所需的条件语句就可以了,那么对于实体框架里面,我们需要采用Lamda表达式的话,应该如何构建对象并传入给接口方法呢,代码如下所示。

        /// <summary>
        ///根据查询条件构造查询语句/// </summary> 
        privateExpressionNode GetConditionSql()
{
Expression
<Func<LoginLogInfo, bool>> expression = p => true;if (!string.IsNullOrEmpty(this.txtLoginName.Text))
{
expression
= expression.And(x => x.LoginName.Contains(this.txtLoginName.Text));
}
if (!string.IsNullOrEmpty(this.txtRealName.Text))
{
expression
= expression.And(x => x.FullName.Contains(this.txtRealName.Text));
}
if (!string.IsNullOrEmpty(this.txtNote.Text))
{
expression
= expression.And(x => x.Note.Contains(this.txtNote.Text));
}
if (!string.IsNullOrEmpty(this.txtIPAddress.Text))
{
expression
= expression.And(x => x.IPAddress.Contains(this.txtIPAddress.Text));
}
if (!string.IsNullOrEmpty(this.txtMacAddress.Text))
{
expression
= expression.And(x => x.MacAddress.Contains(this.txtMacAddress.Text));
}
if (dateTimePicker1.Text.Length > 0)
{
expression
= expression.And(x => x.LastUpdated >= Convert.ToDateTime(dateTimePicker1.DateTime.ToString("yyyy-MM-dd")));
}
if (dateTimePicker2.Text.Length > 0)
{
expression
= expression.And(x => x.LastUpdated <= Convert.ToDateTime(dateTimePicker2.DateTime.AddDays(1).ToString("yyyy-MM-dd")));
}
string systemType = this.txtSystemType.GetComboBoxValue();if (!string.IsNullOrEmpty(systemType))
{
expression
= expression.And(x => x.SystemType_ID ==systemType);
}
//如果是公司管理员,增加公司标识 if(Portal.gc.UserInRole(RoleInfo.CompanyAdminName))
{
expression
= expression.And(x => x.Company_ID ==Portal.gc.UserInfo.Company_ID);
}
//如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的 if (treeCondition != null)
{
expression
=treeCondition;
}
returnexpression.ToExpressionNode();
}

这里我们注意到expression.And或者expression.Or函数,它不是这个expression对象的方法的,是我们针对这个做的一个扩展类函数,它专门处理 Lamda-Expression表达式的扩展,方便组合多个条件,
如两个表达式条件可以组合为AND或者OR条件方式。

这样我们在界面处理的时候,绑定数据的处理方法就可以如下所示了。

        public voidBindData()
{
#region 添加别名解析 this.winGridViewPager1.DisplayColumns = "ID,User_ID,LoginName,FullName,Company_ID,CompanyName,Note,IPAddress,MacAddress,SystemType_ID,LastUpdated";this.winGridViewPager1.ColumnNameAlias = CallerFactory<ILoginLogService>.Instance.GetColumnNameAlias();//字段列显示名称转义 #endregionExpressionNode where =GetConditionSql();
PagerInfo PagerInfo
= this.winGridViewPager1.PagerInfo;
IList
<LoginLogInfo> list = CallerFactory<ILoginLogService>.Instance.FindWithPager(where, ref PagerInfo);this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<LoginLogInfo>(list);
}

以上就是我对于混合型的Entity Framework 实体框架的界面操作,总结的几种分析场景,希望对大家理解在WCF模式里面,使用实体框架的方法有所帮助。

这个系列文章如下所示:

Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)

Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)

Entity Framework 实体框架的形成之旅--Code First的框架设计(5)

Entity Framework 实体框架的形成之旅--Code First模式中使用 Fluent API 配置(6)

Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)

由于一个客户朋友的需求,需要我的Winform开发框架支持国产达梦数据库的操作,这个数据库很早就听过,但是真正一般项目用的很少,一般在一些特殊的项目可能需要用到。由于我的Winform开发框架,是基于Enterprise Library的数据访问层的实现,因此增加一个数据库的支持很容易,本文介绍如何在框架层面上支持这种神秘的国产数据库-达梦数据库。

1、达梦数据库的简单介绍

达梦数据库管理系统是达梦公司推出的具有完全自主知识产权的高性能数据库管理系统,简称DM。达梦数据库管理系统的最新版本是7.0版本,简称DM7。DM7提供对
SQL92
的特性支持以及SQL99的核心级别支持;支持多种数据库开发接口,包括OLE DB、ADO、ODBC、OCI、JDBC、Hibernate、PHP、PDO、DB Express以及.Net DataProvider等。

达梦数据库可以在(http://www.dameng.com/)上下载试用,安装好后提供很多管理工具,安装后可以让它创建一些实例数据库,以方便我们了解数据库的基本操作,本人对这款数据库了解不多,不过它号称支持SQL-92标准的,那么使用起来就不会有什么问题的,只是一些个性化的语法需要查询即可。

达梦数据库的分析器,可以在上面执行自己的一些语句,注意它的数据库表必须带上一个模式前缀,类似SqlServer的dbo,不过这个是必须的。

2、基于Enterprise Library的Winform开发框架的架构支持处理

我的这款基于Enterprise Library的开发框架,底层使用了微软的数据访问组件Enterprise Library,以适应多种数据库类型的操作,它的分层如下所示,每个DAL层(如DALSQL、DALOracle等)都提供了对应数据库的实现,界面层一下的分层图如下所示。

如果增加多一种数据库,那么我们增加多一个Enterprise Library的组件扩展类,并在业务数据模块里面增加对应的DAL层即可。

对于具体的DALSQL这样的数据实现层,我们不需要把数据访问的处理操作在全部的类里面实现一遍,我们可以通过抽象类的方式,把常规的数据库处理操作抽象到基类上面,如下所示。

这样在BaseDALSQL层(SqlServer数据库的个性化实现部分),只需要实现部分功能即可,我们把大多数的数据库操作,放到最顶级的数据库访问基类AbstractBaseDAL类上,从而是我们能够尽可能减少增加不同数据库类型,需要改写代码的数量。

最终我们增量增加一个数据访问层就可以实现了另外一种数据库(达梦数据库)的实现了,具体的架构设计图如下所示。

3、具体接入测试案例代码

上面小节,我们论证了框架的可扩展性,并且理论上已经具备支持达梦数据库的扩张了,本小节介绍如何具体实现达梦数据库的底层接入操作,并编写一个简单的测试例子进行测试,印证我们的实现思路。

我们知道,Enterprise Library实现其他数据库的支持,需要增加一个组件扩展类,如EntLibContrib.Data.SQLite是用来支持SQLite数据库的,EntLibContrib.Data.MySql是用来支持Mysql的,这个扩展类的内容也不多,主要是用来解析如下的配置文件的.

以便能够和Enterprise Library的对象进行无缝的整合,那么我们可以参考MySql数据库的扩展类EntLibContrib.Data.MySql的做法,来创建一个基于国产达梦数据库的Enterprise Library扩展类,大概项目代码如下所示。

这样我们就可以增加配置文件如下所示,它就能正常的解析并处理了。

下面我们来编写测试的代码来印证我们的扩展类,实现Winform开发框架支持国产达梦数据库的扩展操作。

我们创建一个数据库通用操作的辅助类来进行讲解,代码如下所示。

   /// <summary>
    ///基于Enterprise Library类库的数据访问测试/// </summary>
    public classEntLibDmHelper
{
publicEntLibDmHelper()
{
}
/// <summary> ///执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。/// </summary> /// <param name="sql">SQL语句</param> /// <returns> ///返回查询结果的所有记录的第一个字段,用逗号分隔。/// </returns> public string SqlValueList(stringsql)
{
Database db
=DatabaseFactory.CreateDatabase();
DbCommand command
=db.GetSqlStringCommand(sql);

StringBuilder result
= newStringBuilder();using (IDataReader dr =db.ExecuteReader(command))
{
while(dr.Read())
{
result.AppendFormat(
"{0},", dr[0].ToString());
}
}
string strResult = result.ToString().Trim(',');returnstrResult;
}
/// <summary> ///执行SQL查询语句,返回所有记录的DataTable集合。/// </summary> /// <param name="sql">SQL查询语句</param> /// <returns></returns> public DataTable SqlTable(stringsql)
{
DataSet ds
= newDataSet();

Database db
=DatabaseFactory.CreateDatabase();
DbCommand command
=db.GetSqlStringCommand(sql);return db.ExecuteDataSet(command).Tables[0];
}
}

注意,上面的代码没有用到达梦的具体对象,而是使用了Enterprise Library的Database等对象来操作,这样也就是非常方便我们进行接口的抽象处理,可以把更多的功能放到数据库访问抽象类里面了。

如果是利用达梦的.NET Provider的对象处理数据库,那么具体的代码应该是这样的。

        /// <summary>    
        ///执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。/// </summary>    
        /// <param name="sql">SQL语句</param>    
        /// <returns>    
        ///返回查询结果的所有记录的第一个字段,用逗号分隔。/// </returns>    
        public string SqlValueList(stringsql)
{
DmConnection connection
= newDmConnection(ConnectionString);
DmCommand cmd
= newDmCommand(sql, connection);

connection.Open();
StringBuilder result
= newStringBuilder();using (DmDataReader dr =cmd.ExecuteReader())
{
while(dr.Read())
{
result.AppendFormat(
"{0},", dr[0].ToString());
}
}
string strResult = result.ToString().Trim(',');returnstrResult;
}
/// <summary> ///执行SQL查询语句,返回所有记录的DataTable集合。/// </summary> /// <param name="sql">SQL查询语句</param> /// <returns></returns> public DataTable SqlTable(stringsql)
{
DataSet ds
= newDataSet();
DmDataAdapter adpater
= newDmDataAdapter(sql, ConnectionString);
adpater.Fill(ds);
return ds.Tables[0];
}

为了方便测试,我编写一个简单的查询例子来进行介绍,如下代码所示,这里我们主要利用了EntLibDmHelper这个辅助类对象,也就是基于Enterprise Library的扩展的处理操作。

        private void btnSearch_Click(objectsender, EventArgs e)
{
BindData();
}
private voidBindData()
{
string condition = "1=1";if (this.txtAuthor.Text.Length > 0)
{
condition
+= string.Format("AND Author like '%{0}%'", this.txtAuthor.Text);
}
if (this.txtName.Text.Length > 0)
{
condition
+= string.Format("AND Name like '%{0}%'", this.txtName.Text);
}
if (this.txtPublisher.Text.Length > 0)
{
condition
+= string.Format("AND Publisher like '%{0}%'", this.txtPublisher.Text);
}
string sql = "Select * from PRODUCTION.Product Where" +condition;

EntLibDmHelper helper
= newEntLibDmHelper();//DMHelper helper = new DMHelper(); DataTable dt =helper.SqlTable(sql);this.dataGridView1.DataSource =dt;

sql
= "Select count(*) from PRODUCTION.Product Where" +condition;string totalCount =helper.SqlValueList(sql);this.lblCount.Text = string.Format("共有数据:{0}条", totalCount);
}

最后例子运行的界面效果如下所示。

基本上印证了我们对框架的整合,实现了支持国产达梦数据库的扩展操作。剩下的就是我们模仿着把BaseDALSQL这样的基类,为达梦数据库增加一个个性化的数据库处理接口,就可以实现整体性框架的支持了。对于各个模块 的数据访问,我们需要增加一个DALDM这样的实现层,基类指向BaseDALDM这样就可以了。

在上篇《
基于Enterprise Library的Winform开发框架实现支持国产达梦数据库的扩展操作
》介绍了如何在Enterprise Library的数据访问层上支持这种神秘的国产数据库-达梦数据库。本文继续这一主题,介绍产达梦数据库的结合Enterprise Library的应用开发。

1、达梦数据库还原处理

达梦数据库管理系统是达梦公司推出的具有完全自主知识产权的高性能数据库管理系统,简称DM。达梦数据库管理系统的最新版本是7.0版本,简称DM7。DM7提供对
SQL92
的特性支持以及SQL99的核心级别支持;支持多种数据库开发接口,包括OLE DB、ADO、ODBC、OCI、JDBC、Hibernate、PHP、PDO、DB Express以及.Net DataProvider等。

达梦数据库可以在(http://www.dameng.com/)上下载试用,安装好后提供很多管理工具,安装后可以让它创建一些实例数据库,以方便我们了解数据库的基本操作。

达梦数据库安装后,在系统开始菜单里面有一些管理工具和PDF文件链接,如下所示。有时间建议阅读下用户手册里面的几个文件,里面的文档写的还是很详细,覆盖数据库管理操作、数据库编程开发、数据库语法几部分的内容。

在Winform开发框架中,在框架源码目录DatabaseDx里面,提供了一个达梦数据库的备份文件(Winframwork.dmp),里面包含了框架所需的表和数据,使用框架前还原(导入)数据库即可。操作方法是在“DM管理工具”界面里面,左边的模式树上面右键出现【导入】功能,然后选择导入数据库操作,界面如下所示。

单击【导入】功能后,在弹出的界面上,选择对应的目录和数据库备份文件,并输入需要的模式名称即可,界面操作如下所示。

还原(导入)达梦数据库后,刷新模式树列表,则会在左边的模式看到新导入的数据库相关信息。

2、开发应用的扩展整合

基于Enterprise Library的开发框架,底层使用了微软的数据访问组件Enterprise Library,以适应多种数据库类型的操作,它的分层如下所示,每个DAL层(如DALSQL、DALOracle等)都提供了对应数据库的实现,界面层一下的分层图如下所示。

如果增加多一种数据库,那么我们增加多一个Enterprise Library的组件扩展类,并在业务数据模块里面增加对应的DAL层即可。

Enterprise Library实现其他数据库的支持,需要增加一个组件扩展类,如EntLibContrib.Data.SQLite是用来支持SQLite数据库的,EntLibContrib.Data.MySql是用来支持Mysql的,这个扩展类的内容也不多,主要是用来解析Enterprise Library扩展的配置内容的。

3、扩展支持的配置参数

在开发框架里面的启动程序或者WCF服务里面的Web.Config配置文件,我们看到如下的配置信息。

在配置文件里面,我添加了1、2、3个步骤,第
1
步是指达梦数据库的连接字符串,这个是基于Enterprise Library的连接参数内容,由于Enterprise Library默认并不支持达梦数据库的,是通过其扩展模块EntLibContrib.Data.Dm进行支持的,因此需要在第
2
步里面指定具体的扩展对象和程序集信息;最后为了在框架里面知道具体的数据库类型,在第
3
步增加了一个
ComponentDbType
的配置信息,用来确定是哪种数据库类型(因为框架支持多种数据库,需要明确类型,方便构建不同的查询语句语法)。

最后提及的是第3步里面的
DM_Schema
是达梦数据库的配置参数,因为达梦数据库需要指定具体的表的模式前缀。指定了这个参数,我们在数据库模块里面的表名,就可以统一使用这个配置的【模式】名称了,如我们可以在DALDm数据访问层里面,使用这个
this.Schema
的属性,这个属性的值就是通过上面的
DM_Schema
配置获取的。

当然,我们已经在数据访问层里面,为tableName增加了数据库模式前缀(相当于Schema.TableName);如果在DALDm层里面,使用了this.tableName这个参数,那么它已经内置包含了数据库模式的前缀,所以不需要在使用tableName的时候,还显示的增加模式的值,具体正确使用如下所示。

也就是说,如果使用this.tableName这个参数,那么当前表就不用增加模式前缀,否则为表名增加this.Schema这个属性前缀。

4、如何开发支持达梦数据库的应用

由于本文主要介绍如何整合达梦数据库,开发基于达梦数据库的业务应用,因此将重点介绍如何基于这个数据库进行的框架代码生成。

由于整个Enterprise Library的开发框架,在业务层及UI层,是不涉及具体的数据库处理的,因此这些特性让它能够很好提供多数据库的支持,我们基于这个原则,一般也不要打破这个约定,不要在UI层或者BLL层使用特定数据库的SQL。

为了快速开发框架代码,提供两种方式供快速生成数据访问层的代码,一种是基于其他数据库访问层的复制并修改处理,另一种是全新的数据访问层生成。前者是在其他数据访问层已经完成的情况下,可以快速复制并修改即可。后者这是开始增量开发业务,在代码生成工具Database2Sharp支持的基础上进行代码生成。

1)基于其他数据库访问层的复制处理

由于不同的数据库支持,是创建不同的数据访问层,如达梦数据库的数据访问层,一般为DALDm,如下图所示就是字典模块中的数据访问层代码。

上面我们看到,达梦数据库的数据访问层它的命名空间为WHC.Dictionary.DALDm这样的格式,前面部分为主命名空间,后面部分基本为约定的名称DALDm;而这个DictType数据访问类的基类这是继承了达梦数据库的数据访问基类BaseDALDm,这个做法和其他数据库类似。

我们看到,这个内容和SQLServer的数据访问层基本相同的,SQLServer的数据访问层代码如下所示。

对比我们可以看到,他们之间基本上一样的,如果我们在已经开发好了一种数据类型的实现基础上,在进行另外一种数据访问层的代码开发,我们就可以这样快速操作实现代码的复制、修改。

(1)  在VS里面按住Ctrl按键,并用鼠标进行拖动,复制一份副本,修改目录为DALDm。

(2)  在VS里面,关闭全部代码窗口,把复制生成的文件全部打开。

(3)  替换DALSQL为DALDm,这样每个类里面会替换两次内容,实现命名空间和基类名称的统一替换。

(4)  调整其他层对DALDm的引用关系。

重新编译代码即可。

2)全新的数据访问层生成

这种方式的代码生成,需要在代码生成工具里面,选择Enterprise Library架构生成,生成的代码把它们逐一复制到对应的目录,并调整相关的引用关系即可。

具体可以参考代码生成工具Database2Sharp的操作说明。

本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作、联合中间表获取对象集合、递归操作、设置单一字段的修改等几种方式。

1、写入中间表操作

一般情况下,我们可以通过执行数据库脚本方式写入。

        /// <summary>
        ///增加用户IP信息/// </summary>
        /// <param name="userID"></param>
        /// <param name="blackID"></param>
        public void AddUser(int userID, stringblackID)
{
string commandText = string.Format("INSERT INTO T_ACL_BLACKIP_USER(User_ID, BLACKIP_ID) VALUES({0}, '{1}')", userID, blackID);base.SqlExecute(commandText);
}
/// <summary> ///移除用户IP 信息/// </summary> /// <param name="userID"></param> /// <param name="blackID"></param> public void RemoveUser(int userID, stringblackID)
{
string commandText = string.Format("DELETE FROM T_ACL_BLACKIP_USER WHERE User_ID={0} AND BLACKIP_ID='{1}'", userID, blackID);base.SqlExecute(commandText);
}
        /// <summary>
        ///执行一些特殊的语句/// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="parameters">要应用于命令字符串的参数</param>
        public virtual int SqlExecute(string sql, params object[] parameters)
{
returnbaseContext.Database.ExecuteSqlCommand(sql, parameters);
}

上面间接用来执行脚本方式写入中间表数据;但是,如果我们采用Entity Framework的方式来处理,也是可以的,例如我们为角色添加和移除用户的代码如下所示。

        public void AddUser(int userID, introleID)
{
var role = new Role() { ID = roleID, Functions = new List<Function>() };
context.Roles.Attach(role);

User user
= new User() { ID =userID };
context.Users.Attach(user);
int count = this.GetQueryable().Where(s => s.ID == roleID).Count(s => s.Users.Any(p => p.ID ==userID));if (count == 0)
{
role.Users.Add(user);
}

context.SaveChanges();
}
public void RemoveUser(int userID, introleID)
{
var role = this.GetQueryable().FirstOrDefault(s => s.ID ==roleID);var deleted = role.Users.FirstOrDefault(s => s.ID ==userID);if (deleted != null)
{
role.Users.Remove(deleted);
}

context.SaveChanges();
}

如果我们需要重新写入中间列表的数据,那么处理方式如下所示。

        public bool EditOuUsers(int ouID, List<int>newUserList)
{
//先清空 Ou ou = this.GetQueryable().FirstOrDefault(s => s.ID ==ouID);
ou.Users.Clear();
context.SaveChanges();
//设置新的列表 var users = context.Users.Where(s =>newUserList.Contains(s.ID)).ToList();
ou.Users
=users;return context.SaveChanges() > 0;
}

2、联合中间表获取对象集合

一般情况下,我们在数据访问层需要结合中间表进行查询的时候,常用的处理方式是在SQL语句里面联合两个表进行处理。如联合机构角色中间表、机构用户中间表来获取对应机构的列表操作如下所示。

        public List<OUInfo> GetOUsByRole(introleID)
{
string sql = "SELECT * FROM T_ACL_OU INNER JOIN T_ACL_OU_Role On [T_ACL_OU].ID=T_ACL_OU_Role.OU_ID WHERE Role_ID =" +roleID;return this.GetList(sql, null);
}
public List<OUInfo> GetOUsByUser(intuserID)
{
string sql = "SELECT * FROM T_ACL_OU INNER JOIN T_ACL_OU_User On [T_ACL_OU].ID=T_ACL_OU_User.OU_ID WHERE User_ID =" +userID;return this.GetList(sql, null);
}

如果是在实体框架里面,我们不能那么直接操作SQL语句,尽可能用内置的LINQ查询操作,使用的时候,你也发现很简单的。

        public IList<Ou> GetOUsByRole(introleID)
{
return this.GetQueryable().Where(s => s.Roles.Any(c => c.ID ==roleID)).ToList();
}
public IList<Ou> GetOUsByUser(intuserID)
{
return this.GetQueryable().Where(s => s.Users.Any(c => c.ID ==userID)).ToList();
}

3、递归操作

很多时候,我们表的数据是有层次结构的,有时候,需要根据父ID获取下面的子集列表的时候,我们就需要用到递归。常规的SQL处理方式,可以如下实现。

       /// <summary>
        ///根据指定机构节点ID,获取其下面所有机构列表/// </summary>
        /// <param name="parentId">指定机构节点ID</param>
        /// <returns></returns>
        public List<OUInfo> GetAllOUsByParent(intparentId)
{
List
<OUInfo> list = new List<OUInfo>();string sql = string.Format("Select * From {0} Where Deleted <> 1 Order By PID, Name", tableName);

DataTable dt
=SqlTable(sql);string sort = string.Format("{0} {1}", GetSafeFileName(sortField), isDescending ? "DESC" : "ASC");
DataRow[] dataRows
= dt.Select(string.Format("PID = {0}", parentId), sort);for (int i = 0; i < dataRows.Length; i++)
{
string id = dataRows[i]["ID"].ToString();
list.AddRange(GetOU(id, dt));
}
returnlist;
}
private List<OUInfo> GetOU(stringid, DataTable dt)
{
List
<OUInfo> list = new List<OUInfo>();

OUInfo ouInfo
= this.FindByID(id);
list.Add(ouInfo);
string sort = string.Format("{0} {1}", GetSafeFileName(sortField), isDescending ? "DESC" : "ASC");
DataRow[] dChildRows
= dt.Select(string.Format("PID={0}", id), sort);for (int i = 0; i < dChildRows.Length; i++)
{
string childId = dChildRows[i]["ID"].ToString();
List
<OUInfo> childList =GetOU(childId, dt);
list.AddRange(childList);
}
returnlist;
}

如果这样的实现,在实体框架实现,那么应该如何处理呢?

        public IList<Ou> GetAllOUsByParent(intparentId)
{
List
<Ou> list = this.GetQueryable().Where(s => s.PID == parentId).Where(s => !s.IsDeleted).OrderBy(s => s.PID).OrderBy(s =>s.Name).ToList();foreach(Ou info inlist)
{
list.AddRange(GetAllOUsByParent(info.ID));
}
returnlist;
}

是不是觉得简单很多了呢?

4、设置单一字段的修改

有时候,我们可能需要更新列表里面的某些字段,那么如果采用SQL操作处理,那么代码如下所示。

        /// <summary>
        ///设置删除标志/// </summary>
        /// <param name="id">记录ID</param>
        /// <param name="deleted">是否删除</param>
        /// <param name="trans">事务对象</param>
        /// <returns></returns>
        public bool SetDeletedFlag(object id, bool deleted = true, DbTransaction trans = null)
{
int intDeleted = deleted ? 1 : 0;string sql = string.Format("Update {0} Set Deleted={1} Where ID = {2}", tableName, intDeleted, id);return SqlExecute(sql, trans) > 0;
}

如果是采用实体框架,那么处理代码如下所示。

        public bool SetDeletedFlag(object id, bool deleted = true)
{
var ou = new Ou() { ID = id.ToString().ToInt32(), IsDeleted =deleted };
context.Ous.Attach(ou);

context.Entry(ou).Property(x
=> x.Deleted).IsModified = true;return context.SaveChanges() > 0;
}

如果更新多个字段,那么常规的SQL方式代码如下所示。

        /// <summary>
        ///更新用户登录的时间和IP地址/// </summary>
        /// <param name="id">用户ID</param>
        /// <param name="ip">IP地址</param>
        /// <param name="macAddr">MAC地址</param>
        /// <returns></returns>
        public bool UpdateUserLoginData(int id, string ip, stringmacAddr)
{
//先复制最后的登录时间和IP地址 string sql = string.Format("Update {0} set LastLoginIP=CurrentLoginIP,LastLoginTime=CurrentLoginTime,LastMacAddress=CurrentMacAddress Where ID={1}", tableName, id);
Database db
=CreateDatabase();
DbCommand dbCommand
=db.GetSqlStringCommand(sql);
db.ExecuteNonQuery(dbCommand);

sql
= string.Format("Update {0} Set CurrentLoginIP='{1}',CurrentMacAddress='{2}', CurrentLoginTime=@CurrentLoginTime Where ID = {3}", tableName, ip, macAddr, id);
dbCommand
=db.GetSqlStringCommand(sql);
db.AddInParameter(dbCommand,
"CurrentLoginTime", DbType.DateTime, DateTime.Now);return db.ExecuteNonQuery(dbCommand) > 0;
}

多个字段的更新,使用实体框架则是如下所示。

        public bool UpdateUserLoginData(int id, string ip, stringmacAddr)
{
//先复制最后的登录时间和IP地址 string tableName = context.GetTableName<User>();string sql = string.Format("Update {0} set LastLoginIP=CurrentLoginIP,LastLoginTime=CurrentLoginTime,LastMacAddress=CurrentMacAddress Where ID={1}", tableName, id);
SqlExecute(sql);
var obj = new User() { ID = id.ToString().ToInt32(), CurrentLoginIP = ip, CurrentMacAddress = macAddr, CurrentLoginTime =DateTime.Now };
context.Users.Attach(obj);
context.Entry(obj).Property(x
=> x.CurrentLoginIP).IsModified = true;
context.Entry(obj).Property(x => x.CurrentMacAddress).IsModified = true;
context.Entry(obj).Property(x => x.CurrentLoginTime).IsModified = true;
return context.SaveChanges() > 0;
}
 

这个系列文章如下所示:

Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)

Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)

Entity Framework 实体框架的形成之旅--Code First的框架设计(5)

Entity Framework 实体框架的形成之旅--Code First模式中使用 Fluent API 配置(6)

Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)

Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)

本篇介绍Entity Framework 实体框架的文章已经到了第十篇了,对实体框架的各个分层以及基类的封装管理,已经臻于完善,为了方便对基类接口的正确性校验,以及方便对以后完善或扩展接口进行回归测试,那么建立单元测试就有很大的必要,本篇主要介绍如何利用VS创建内置的单元测试项目进行实体框架的基类接口测试。

在采用单元测试这个事情上,很多人可能想到了NUnit单元测试工具和NMock工具进行处理,其实微软VS里面也已经为我们提供了类似的单元测试工具了,可以不需要使用这个第三方的单元测试工具,经试用VS的单元测试工具还是整合性很好,使用非常方便的。

1、实体框架架构及基础类库接口

在上次的随笔《
Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
》里面,我根据实体框架中混合模式的框架结构,所涉及的架构图形如下所示。

我们从上图可以看到,整个框架从下往上分为了几个明显的层次,一个数据访问层DAL层,一个是业务逻辑层BLL层,一个是Facade门面层,各个层的功能不同,这几个层中以DAL层最为复杂一些,涉及到底层多种数据库的抽象实现,由于Entity Framework 实体框架本身就是对多种数据库的实现抽象,因此本文重点针对这个
DAL
层进行单元测试。

其中的实体框架的公用类库(WHC.Framework.EF),里面涉及到的IBaseDAL就是数据访问层的基类接口,具体数据访问的抽象实现就在BaseDAL的基类上。

在IBaseDAL接口里面,定义了很多我们数据访问类需要使用的增删改查、分页、统计、辅助方法等接口,以及各个方法的异步方法接口,如下所示。

namespaceWHC.Framework.EF
{
/// <summary> ///数据访问层基类接口/// </summary> /// <typeparam name="T">实体对象类型</typeparam> public interface IBaseDAL<T> where T : class{#region 对象添加、修改、删除 /// <summary> ///插入指定对象到数据库中/// </summary> /// <param name="t">指定的对象</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> boolInsert(T t);/// <summary> ///插入指定对象到数据库中(异步)/// </summary> /// <param name="t">指定的对象</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool>InsertAsync(T t);/// <summary> ///插入指定对象集合到数据库中/// </summary> /// <param name="list">指定的对象集合</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> bool InsertRange(IEnumerable<T>list);/// <summary> ///插入指定对象集合到数据库中(异步)/// </summary> /// <param name="list">指定的对象集合</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool> InsertRangeAsync(IEnumerable<T>list);/// <summary> ///更新对象属性到数据库中/// </summary> /// <param name="t">指定的对象</param> /// <param name="key">主键的值</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> bool Update(T t, objectkey);/// <summary> ///更新对象属性到数据库中(异步)/// </summary> /// <param name="t">指定的对象</param> /// <param name="key">主键的值</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool> UpdateAsync(T t, objectkey);/// <summary> ///根据指定对象的ID,从数据库中删除指定对象/// </summary> /// <param name="id">对象的ID</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> bool Delete(objectid);/// <summary> ///根据指定对象的ID,从数据库中删除指定对象(异步)/// </summary> /// <param name="id">对象的ID</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool> DeleteAsync(objectid);/// <summary> ///从数据库中删除指定对象/// </summary> /// <param name="t">指定对象</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> boolDelete(T t);/// <summary> ///从数据库中删除指定对象(异步)/// </summary> /// <param name="t">指定对象</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool>DeleteAsync(T t);/// <summary> ///根据指定条件,从数据库中删除指定对象/// </summary> /// <param name="match">条件表达式</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> bool DeleteByExpression(Expression<Func<T, bool>>match);/// <summary> ///根据指定条件,从数据库中删除指定对象(异步)/// </summary> /// <param name="match">条件表达式</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool> DeleteByExpressionAsync(Expression<Func<T, bool>>match);/// <summary> ///根据指定条件,从数据库中删除指定对象/// </summary> /// <param name="condition">删除记录的条件语句</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> bool DeleteByCondition(stringcondition);/// <summary> ///根据指定条件,从数据库中删除指定对象(异步)/// </summary> /// <param name="condition">删除记录的条件语句</param> /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns> Task<bool> DeleteByConditionAsync(stringcondition);#endregion

或者一些其他的分页等复杂的实现接口。

        #region 返回集合的接口

        /// <summary>
        ///返回可查询的记录源/// </summary>
        /// <returns></returns>
        IQueryable<T>GetQueryable();/// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true);/// <summary>
        ///根据条件表达式返回可查询的记录源/// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IQueryable<T> GetQueryable<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);/// <summary>
        ///返回数据库所有的对象集合/// </summary>
        /// <returns></returns>
        IList<T>GetAll();/// <summary>
        ///返回数据库所有的对象集合(异步)/// </summary>
        /// <returns></returns>
        Task<IList<T>>GetAllAsync();/// <summary>
        ///返回数据库所有的对象集合/// </summary>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IList<T> GetAll<TKey>(Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);/// <summary>
        ///返回数据库所有的对象集合(异步)/// </summary>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        Task<IList<T>> GetAllAsync<TKey>(Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);/// <summary>
        ///返回数据库所有的对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        IList<T>GetAllWithPager(PagerInfo info);/// <summary>
        ///返回数据库所有的对象集合(用于分页数据显示,异步)/// </summary>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        Task<IList<T>>GetAllWithPagerAsync(PagerInfo info);/// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        IList<T> Find(Expression<Func<T, bool>>match);/// <summary>
        ///根据条件查询数据库,并返回对象集合(异步)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        Task<IList<T>> FindAsync(Expression<Func<T, bool>>match);/// <summary>
        ///根据条件查询数据库,并返回对象集合/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IList<T> Find<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);/// <summary>
        ///根据条件查询数据库,并返回对象集合(异步)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        Task<IList<T>> FindAsync<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);/// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        IList<T> FindWithPager(Expression<Func<T, bool>>match, PagerInfo info);/// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示,异步)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        Task<IList<T>> FindWithPagerAsync(Expression<Func<T, bool>>match, PagerInfo info);/// <summary>
        ///根据条件查询数据库,并返回对象集合(用于分页数据显示)/// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns>指定对象的集合</returns>
        IList<T> FindWithPager<TKey>(Expression<Func<T, bool>> match, PagerInfo info, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);#endregion

以及更多的方法接口,我们为了校验没有接口都能够正常工作,就需要对它们进行单元测试。

2、在VS里面创建单元测试项目及编写单元测试代码

在VS里面创建内置的单元测试项目如下所示,在添加新项目里面选择测试->单元测试项目即可,如下图所示。

为了方便对基类测试,我们还是需要创建一个简单的代表性数据库用来检查基础的接口操作。

由于前面的系列,已经介绍过了,我们在构建数据访问层的时候,使用的是基于IOC的方式构建一个对象的接口对象,如
这样
代码
IFactory.Instance<IUserDAL>()
所示。

而单元测试,基本原理就是我们调用接口,并获取对应的输出结果,和我们预期的值进行对比,如果吻合就是正常通过的测试用例。

为了进行基础类库的单元测试,我们需要根据实体框架的结构搭建一个具体表的对象项目工程,这个采用代码生成工具Database2Sharp进行生成就可以了,生成的处理操作如下所示。

这样根据表快速生成的整个实体框架,就是我们所需要的实体框架项目,具体效果如下所示。

例如我们创建一个查找记录的单元测试方法代码如下所示。

namespaceTestFrameworkEF
{
[TestClass]
public classTestBaseDAL
{
private string userId =Guid.NewGuid().ToString();

[TestInitialize]
public voidInit()
{
User user
= new User() { ID = userId, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);
}

[TestCleanup]
public voidCleanup()
{
bool result = IFactory.Instance<IUserDAL>().Delete(userId);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidFindByID()
{
User user
= IFactory.Instance<IUserDAL>().FindByID(userId);

Assert.IsNotNull(user);
Assert.AreEqual(user.ID, userId);
}

其中上面红色代码部分就是单元测试的各种标识,包括单元测试类标识,以及初始化、退出清除、测试用例的标识。

上面案例,我们是在单元测试前,在数据库写入一条记录,然后在进行各种单元测试用例的运行及校验,最后退出的时候,清除我们写入的记录。

而记录的更新和删除接口,我们具体的单元测试代码如下所示。

[TestMethod]public voidUpdate()
{
string newAccount = "Test";
User user
= IFactory.Instance<IUserDAL>().FindByID(userId);
user.Account
=newAccount;bool result = IFactory.Instance<IUserDAL>().Update(user, user.ID);
Assert.AreEqual(result,
true);

user
= IFactory.Instance<IUserDAL>().FindByID(userId);
Assert.IsNotNull(user);
Assert.AreEqual(user.Account, newAccount);
}

[TestMethod]
public voidDelete()
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);

result
= IFactory.Instance<IUserDAL>().Delete(id);
Assert.AreEqual(result,
true);
}

最后我们整个单元测试的测试代码如下所示。

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingWHC.Framework.EF;usingEFCore.IDAL;usingEFCore.Entity;usingMicrosoft.VisualStudio.TestTools.UnitTesting;usingSystem.Linq.Expressions;usingSystem.Linq;usingWHC.Pager.Entity;namespaceTestFrameworkEF
{
[TestClass]
public classTestBaseDAL
{
private string userId =Guid.NewGuid().ToString();

[TestInitialize]
public voidInit()
{
User user
= new User() { ID = userId, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);
}

[TestCleanup]
public voidCleanup()
{
bool result = IFactory.Instance<IUserDAL>().Delete(userId);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidFindByID()
{
User user
= IFactory.Instance<IUserDAL>().FindByID(userId);

Assert.IsNotNull(user);
Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidInsert()
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id , Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);

user
= IFactory.Instance<IUserDAL>().FindByID(id);
Assert.IsNotNull(user);
Assert.AreEqual(user.ID, id);

result
= IFactory.Instance<IUserDAL>().Delete(id);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidInsertRang()
{
List
<User> list = new List<User>();for(int i = 0; i<3; i++)
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id, Account = "Nunit" + i.ToString(), Password = "Nunit"};
list.Add(user);
}
bool result = IFactory.Instance<IUserDAL>().InsertRange(list);
Assert.AreEqual(result,
true);foreach(User user inlist)
{
result
= IFactory.Instance<IUserDAL>().Delete(user.ID);
Assert.AreEqual(result,
true);
}
}

[TestMethod]
public voidUpdate()
{
string newAccount = "Test";
User user
= IFactory.Instance<IUserDAL>().FindByID(userId);
user.Account
=newAccount;bool result = IFactory.Instance<IUserDAL>().Update(user, user.ID);
Assert.AreEqual(result,
true);

user
= IFactory.Instance<IUserDAL>().FindByID(userId);
Assert.IsNotNull(user);
Assert.AreEqual(user.Account, newAccount);
}

[TestMethod]
public voidDelete()
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);

result
= IFactory.Instance<IUserDAL>().Delete(id);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidDeleteByExpression()
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);

Expression
<Func<User, bool>> expression = p => p.ID == user.ID && p.Account ==user.Account;
result
= IFactory.Instance<IUserDAL>().DeleteByExpression(expression);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidDeleteByCondition()
{
var id =Guid.NewGuid().ToString();
User user
= new User() { ID = id, Account = "Nunit", Password = "Nunit"};bool result = IFactory.Instance<IUserDAL>().Insert(user);
Assert.AreEqual(result,
true);string condition = string.Format("ID ='{0}'", id);
result
= IFactory.Instance<IUserDAL>().DeleteByCondition(condition);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidFindSingle()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;
User dbUser
= IFactory.Instance<IUserDAL>().FindSingle(expression);
Assert.IsNotNull(dbUser);
Assert.AreEqual(dbUser.ID, userId);
}

[TestMethod]
public voidGetQueryable()
{
User user
= IFactory.Instance<IUserDAL>().GetQueryable().Take(1).ToList()[0];
Assert.IsNotNull(user);
//Assert.AreEqual(user.ID, userId); }

[TestMethod]
public voidGetQueryableExpression()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;
User user
= IFactory.Instance<IUserDAL>().GetQueryable(expression, "Account").Take(1).ToList()[0];
Assert.IsNotNull(user);
Assert.AreEqual(user.ID, userId);
}


[TestMethod]
public voidGetQueryableExpression2()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;
User user
= IFactory.Instance<IUserDAL>().GetQueryable(expression, s=>s.Account).Take(1).ToList()[0];
Assert.IsNotNull(user);
Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidGetAll()
{
User user
= IFactory.Instance<IUserDAL>().GetAll().Take(1).ToList()[0];
Assert.IsNotNull(user);
}

[TestMethod]
public voidGetAllOrderBy()
{
User user
= IFactory.Instance<IUserDAL>().GetAll(s=>s.Account).Take(1).ToList()[0];
Assert.IsNotNull(user);
}

[TestMethod]
public voidGetAllWithPager()
{
PagerInfo pagerInfo
= newPagerInfo();
pagerInfo.PageSize
= 30;

User user
= IFactory.Instance<IUserDAL>().GetAllWithPager(pagerInfo).Take(1).ToList()[0];
Assert.IsNotNull(user);
}

[TestMethod]
public voidFind()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;
User user
= IFactory.Instance<IUserDAL>().Find(expression).Take(1).ToList()[0];
Assert.IsNotNull(user);

Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidFind2()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;
User user
= IFactory.Instance<IUserDAL>().Find(expression, s=>s.Account).Take(1).ToList()[0];
Assert.IsNotNull(user);

Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidFindWithPager()
{
PagerInfo pagerInfo
= newPagerInfo();
pagerInfo.PageSize
= 30;

Expression
<Func<User, bool>> expression = p => p.ID ==userId;

User user
= IFactory.Instance<IUserDAL>().FindWithPager(expression, pagerInfo).Take(1).ToList()[0];
Assert.IsNotNull(user);

Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidFindWithPager2()
{
PagerInfo pagerInfo
= newPagerInfo();
pagerInfo.PageSize
= 30;

Expression
<Func<User, bool>> expression = p => p.ID ==userId;

User user
= IFactory.Instance<IUserDAL>().FindWithPager(expression, pagerInfo, s=>s.Account).Take(1).ToList()[0];
Assert.IsNotNull(user);

Assert.AreEqual(user.ID, userId);
}

[TestMethod]
public voidGetRecordCount()
{
int count = IFactory.Instance<IUserDAL>().GetRecordCount();
Assert.AreNotEqual(count,
0);
}

[TestMethod]
public voidGetRecordCount2()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;int count = IFactory.Instance<IUserDAL>().GetRecordCount(expression);
Assert.AreNotEqual(count,
0);
}

[TestMethod]
public voidIsExistRecord()
{
bool result = IFactory.Instance<IUserDAL>().IsExistRecord(userId);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidIsExistRecord2()
{
Expression
<Func<User, bool>> expression = p => p.ID ==userId;bool result = IFactory.Instance<IUserDAL>().IsExistRecord(expression);
Assert.AreEqual(result,
true);
}

[TestMethod]
public voidSqlExecute()
{
string newAccount = "newAccount";string sql = string.Format("update [User] set Account='{0}' Where ID='{1}'", newAccount, userId);int count = IFactory.Instance<IUserDAL>().SqlExecute(sql);
Assert.AreEqual(count,
1);
}

[TestMethod]
public voidSqlValueList()
{
string sql = string.Format("Select ID From [User]");
ICollection
<string> valueString = IFactory.Instance<IUserDAL>().SqlValueList(sql);
Assert.IsNotNull(valueString);
Assert.IsTrue(valueString.Count
> 0);
}
}
}

3、运行单元测试

代码编译没有问题后,我们需要检验我们的单元测试代码的正确性,那么只需要在VS的测试菜单里面,执行下面的操作即可。

最后得到的运行结果如下所示,验证了我们基类代码的正确性。