2023年2月

在很多应用系统里面,虽然一般采用一种数据库运行,但是由于各种情况的需要,可能业务系统会部署在不同类型的数据库上,如果开发的系统能够很方便支持多种数据库的切换,那可以为我们减少很多烦恼,同时提高系统的适应性和强壮型。还有一种情况,由于业务数据库的不断膨胀或者方便数据库的切割隔离,有时候也会把不同的业务数据库进行分拆,如权限提供数据库,客户关系管理数据库,工作流程数据库,企业营运数据库等等,因此在一个系统里面,同时使用2个或者以上的数据库的情况也是有的。

在我较早期的一篇随笔《
Winform开发框架中实现多种数据库类型切换以及分拆数据库的支持
》里面,介绍了框架如何在一个项目里支持多个数据库的拆分处理。一般情况下,我们都是在一个数据库类型的情况下,分拆多个数据库,但是也有变态的需求例外,如我们可能把一些常规的数据库存储在本地SQLite数据库里面,其他的一些数据放在局域网其他类型数据库(如SQLServer)里面;那么这种同一时刻支持多种数据库类型,并且给用户创建BLL层类的时候,动态指定数据库是否可以实现的呢?

当然可以,我们在上面的基础上稍微调整一下创建方法即可。

1、同时支持多种类型数据库处理并存

之前介绍了,需要分拆数据库的话,我们需要在数据访问基类
AbstractBaseDAL
里面,添加一个SetConfigName的方法,用来指定具体的数据库配置项目,如下所示。

    /// <summary>
    ///数据访问层的超级基类,所有数据库的数据访问基类都继承自这个超级基类,包括Oracle、SqlServer、Sqlite、MySql、Access等/// </summary>
    public abstract class AbstractBaseDAL<T> where T : BaseEntity, new()
{
/// <summary> ///设置数据库配置项名称/// </summary> /// <param name="dbConfigName">数据库配置项名称</param> public virtual void SetDbConfigName(stringdbConfigName)
{
this.dbConfigName =dbConfigName;
}

....................
}

那么我们这次只需要在这个基础上调整一下就可以实现同一时刻变换不同数据库支持的了,由于在框架里面,我们一般已经实现了多种数据库访问的逻辑(如下所示),因此切换起来访问肯定也是没问题的(保证有数据库即可)。

方法就是在BLL层里面添加一个公开方法,可以设置配置项和数据库类型的函数,如下所示。

        /// <summary>
        ///根据参数信息,重新初始化数据访问层(例:可以指定不同的数据访问层)/// </summary>
        /// <param name="dbConfigName">数据库配置项名称</param>
        /// <param name="componentDbType">数据库类型,默认从ComponentDbType中读取,如果dbConfigName指定不同类型的数据库连接,需要指定componentDbType。</param>
        public void SetConfigName(string dbConfigName, string componentDbType = null)
{
//componentDbType = null时,从配置项取ComponentDbType的值 string dbType =componentDbType;if (string.IsNullOrEmpty(componentDbType))
{
AppConfig config
= newAppConfig();
dbType
= config.AppConfigGet("ComponentDbType");
}
string DALPrefix =GetDALPrefix(dbType);this.dalName = bllFullName.Replace(bllPrefix, DALPrefix);//替换中级的BLL.为DAL.,就是DAL类的全名 baseDal = Reflect<IBaseDAL<T>>.Create(this.dalName, dalAssemblyName);//构造对应的DAL数据访问层的对象类 if (!string.IsNullOrEmpty(dbConfigName))
{
baseDal.SetDbConfigName(dbConfigName);
//设置数据库配置项名称 }
}

这样我们除了可以设置EnterpriseLibrary的配置项外,还可以指定这个数据库的类型,不需要全部使用统一的ComponentDbType的值。

如下代码处理,我们就可以在访问其他数据库的时候,切换这个BLL层的对象为其他类型的数据库(SQLite),这样不管其他类如何变化,这个Province的数据访问的是SQLite数据库里面的数据。

BLLFactory<Province>.Instance.SetConfig("sqlite", "sqlite");

2、支持多数据库操作的基类CommonDAL,实现不同数据库访问

有时候,我们在一个比较小的应用程序里面,想灵活对数据库表进行一些简单的处理操作,不想使用代码生成工具生成整个架构的代码,那么这个时候,这个CommonDAL就派上用场了,这个可以快速访问数据库的表,它的定义如下所示。

这个类的几个构造函数如下所示,参数分别为表名,主键字段,数据库类型。

        /// <summary>
        ///默认构造函数/// </summary>
        publicCommonDAL()
{
}
/// <summary> ///指定表名以及主键,对基类进构造/// </summary> /// <param name="tableName">表名</param> /// <param name="primaryKey">表主键</param> /// <param name="dbType">数据库类型,如果为空从配置文件里面获取ComponentDbType的键值</param> public CommonDAL(string tableName) : this(tableName, null, null)
{

}
/// <summary> ///指定表名以及主键,对基类进构造/// </summary> /// <param name="tableName">表名</param> /// <param name="primaryKey">表主键</param> /// <param name="dbType">数据库类型,如果为空从配置文件里面获取ComponentDbType的键值</param> public CommonDAL(string tableName, string primaryKey, string dbType = null)
:
this()
{
}

这个就是一个精简版本的AbstractBaseDAL基类,提供了我们能够使用的多数数据库操作方法。

例如我在一个验证视图及其格式的例子程序里面,就使用了这个类来实现快速的数据库操作处理。

    /// <summary>
    ///判断视图名称是否存在的任务/// </summary>
    public classViewNameExistJob : IExecuteJob
{
public boolExecute()
{
List
<string> list =DataHelper.GetViewList();bool allSuccess = true;foreach (string view inlist)
{
CommonDb dal
= new CommonDb(view, "F_Guid");try{
DataTable dt
=dal.GetReaderSchema(view);
}
catch(Exception ex)
{
allSuccess
= false;
LogTextHelper.Error(
string.Format("视图:{0}不存在。{1}", view, ex.Message));
}
}
if(allSuccess)
{
LogTextHelper.Info(
"视图全部存在。");
}
returnallSuccess;
}
}

或者其他数据库访问处理。

                CommonDb dal = new CommonDb(view, "F_Guid");try{int count =dal.GetRecordCount();if (count == 0)
{
LogTextHelper.Info(
string.Format("视图【{0}】数据为空,请检查!", view));
allSuccess
= false;
}
}
catch(Exception ex)
{
allSuccess
= false;
LogTextHelper.Error(
string.Format("视图:【{0}】不存在。{1}", view, ex.Message));
}

这样也可以实现多数据库的随便切换,不过这个是用于简易的数据库访问,对于需要多种业务封装的处理类,我们还是使用常规的框架分层模式来实现数据的处理操作。

”摇一摇周边“是微信提供的一种新的基于位置的连接方式。用户通过“摇一摇”的“周边”页卡,可以与线下商户进行互动,获得商户提供的个性化的服务。微信4月份有一个赠送摇一摇设备的活动,我们有幸获得赠送资格,取得一个摇一摇的设备用来测试这个新增的、很有潜力的功能。”摇一摇周边“是微信基于低功耗蓝牙技术的 O2O 入口级应用,与微信的其他线下连接能力一道,加速促成了微信 O2O 闭环的实现。本文主要介绍一摇设备的配置使用,以及如何在开发层面上,定义及实现微信摇一摇的功能接口。

1、IBeacon基础知识介绍

摇一摇周边是基于IBeacon来实现的。IBeacon是苹果公司开发的一种通过低功耗蓝牙技术进行一个十分精确的微定位技术。IBeacon设备通过蓝牙信号广播设备id, 手机等终端进入IBeacon设备的信号范围,可以收到该设备的id.

实现分如下四个步骤:

第一步. 服务提供者向微信后台申请服务,微信后台生成一个IBeaconId,并将其映射到服务提供者提供的服务,再将IBeaconId告诉服务提供者;

第二步. 服务提供者把第一步拿到的IBeaconId设置到IBeacon设备上,让IBeacon设备广播该IBeaconId;

第三步. 用户在该IBeacon设备的信号范围内打开微信摇一摇周边,微信App拿到该IBeaconId;

第四步. 微信通过第三步拿到的IBeaconId,向微信后台拉取相应的服务,展示在摇出来的结果上。

第五步. 用户点击摇出来结果,在微信内嵌的浏览器上,会带上用户信息跳转到服务提供者在第一步申请服务时填的url,进入应用页面

应用场景:

2、摇一摇设备的使用

摇一摇的beacon设备很小,底座可以用赠送的双面胶粘贴在墙面上,底面还可以贴一个微信的提示标签,挺有意思的效果。

同时可以下载这个beacon设备的App软件RealKit进行设置设备,设备界面管理界面如下所示。

根据官方摇一摇(
https://zb.weixin.qq.com/
)的配置说明,我们拥有设备后,需要配置设备响应的页面,流程如下所示。

由于是微信赠送的设备,默认情况下,微信后台已经给我们添加赠送的摇一摇设备记录了;

如果是自己购买这种摇一摇的设备,那么需要自己手工添加设备记录,并输入相关的参数即可。

添加设备后,我们需要配置摇一摇的页面,页面就是在摇动微信的时候,显示给微信用户的一个界面,页面管理界面如下所示。

最终配置完成后,我们就来试一下这个神秘的设备了,看看效果如何。这个设备的信号穿透力还是很不错,隔了10米,两堵墙摇一摇还是很快出来界面的。

这个摇一摇周边的功能,我在IPhone4S里面始终无法出现周边这个模块,在5、6plus里面倒是很正常的出现并响应处理,而IPAD则是在摇一摇后出现页面的,但是页面无法响应。

3、基于C#的微信摇一摇接口的实现

摇一摇和功能界面相呼应,提供了设备管理、页面管理、素材管理、关联关系绑定、设备及用户信息、数据统计等功能接口,如下所示。

1)设备管理

而其中设备管理部分,又分为了好几个API的处理。

1 申请设备ID
2 编辑设备信息
3 配置设备与门店的关联关系
4 查询设备列表

申请设备ID

接口说明
申请配置设备所需的UUID、Major、Minor。申请成功后返回批次ID,可用返回的批次ID用“查询设备列表”接口拉取本次申请的设备ID。单次新增设备超过500个,需走人工审核流程,大概需要三个工作日;单次新增设备不超过500个的,当日可返回申请的设备ID。一个公众账号最多可申请99999个设备ID,如需申请的设备ID数超过最大限额,请邮件至zhoubian@tencent.com,邮件格式如下: 标题:申请提升设备ID额度 内容:1、公众账号名称及appid(wx开头的字符串,在mp平台可查看) 2、用途 3、预估需要多少设备ID。

接口调用说明

http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/shakearound/device/applyid?access_token=ACCESS_TOKEN
POST数据格式:json
POST数据例子:
{
   "quantity":3,	
   "apply_reason":"测试",	
   "comment":"测试专用",
   "poi_id":1234	
}

参数说明

参数 是否必须 说明
access_token 调用接口凭证
quantity 申请的设备ID的数量,单次新增设备超过500个,需走人工审核流程
apply_reason 申请理由,不超过100个字
comment 备注,不超过15个汉字或30个英文字母
poi_id 设备关联的门店ID,关联门店后,在门店1KM的范围内有优先摇出信息的机会。门店相关信息具体可
查看门店相关的接口文档

返回说明
正常时的返回JSON数据包示例:

当申请个数小于等于500时,
{
"data": {
       "apply_id": 123,
       "device_identifiers":[
			{
				"device_id":10100,	
				"uuid":"FDA50693-A4E2-4FB1-AFCF-C6EB07647825",		
				"major":10001,
				"minor":10002
			}
		]
    },
    "errcode": 0,
    "errmsg": "success."
}
当申请个数大于500时,
{
"data": {
               "apply_id": 123,
		"audit_status": 0,	
		"audit_comment": "审核未通过"	
   },
   "errcode": 0,
   "errmsg": "success."
}


参数说明

参数 说明
device_identifiers 指定的设备ID列表
device_id 设备编号
UUID、major、minor UUID、major、minor
audit_status 审核状态。0:审核未通过、1:审核中、2:审核已通过;审核会在三个工作日内完成
audit_comment 审核备注,包括审核不通过的原因
apply_id 申请的批次ID,可用在“查询设备列表”接口按批次查询本次申请成功的设备ID。

根据这些接口定义,我们可以创建一个摇一摇专用的接口类IShakeAround。

    /// <summary>
    ///摇一摇周边的接口定义/// </summary>
    public interfaceIShakeAround
{
#region 设备管理 /// <summary> ///申请设备ID。///接口说明 申请配置设备所需的UUID、Major、Minor。///若激活率小于50%,不能新增设备。单次新增设备超过500个,需走人工审核流程。审核通过后,可用返回的批次ID用“查询设备列表”接口拉取本次申请的设备ID。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="applyInfo">设备申请信息</param> ShakeDeviceApplyJson ApplyDevice(stringaccessToken, ShakeDeviceAddJson applyInfo);/// <summary> ///编辑设备信息。///接口说明 编辑设备的备注信息。可用设备ID或完整的UUID、Major、Minor指定设备,二者选其一。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="info">设备信息</param> /// <param name="comment">设备的备注信息,不超过15个汉字或30个英文字母。</param> /// <returns></returns> CommonResult UpdateDevice(string accessToken, ShakeDeviceIdentifier info, stringcomment);/// <summary> ///配置设备与门店的关联关系。接口说明 修改设备关联的门店ID、设备的备注信息。可用设备ID或完整的UUID、Major、Minor指定设备,二者选其一。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="info">设备信息</param> /// <param name="poi_id">待关联的门店ID</param> /// <returns></returns> CommonResult BindDevice(string accessToken, ShakeDeviceIdentifier info, intpoi_id);/// <summary> ///查询设备列表.///接口说明 查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。可指定设备ID或完整的UUID、Major、Minor查询,也可批量拉取设备信息列表。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="device_identifiers">设备列表信息</param> /// <returns></returns> ShakeDeviceSearchList SearchDevice(string accessToken, List<ShakeDeviceIdentifier>device_identifiers);/// <summary> ///查询设备列表.///接口说明 查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。可指定设备ID或完整的UUID、Major、Minor查询,也可批量拉取设备信息列表。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="data">分页查询条件。apply_id为批次ID,如果指定则以批次进行查询,否则以指定范围查询。</param> /// <returns></returns> ShakeDeviceSearchList SearchDevice(stringaccessToken, ShakeDeviceSearchPaging data);#endregion

接口定义好,我们增加对应的类实现即可,如下是申请设备的接口具体实现函数,其他遵循
同样的规则
就不再赘述。

        /// <summary>
        ///申请设备ID。///接口说明 申请配置设备所需的UUID、Major、Minor。///若激活率小于50%,不能新增设备。单次新增设备超过500个,需走人工审核流程。审核通过后,可用返回的批次ID用“查询设备列表”接口拉取本次申请的设备ID。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="applyInfo">设备申请信息</param>
        public ShakeDeviceApplyJson ApplyDevice(stringaccessToken, ShakeDeviceAddJson applyInfo)
{
var url = string.Format("https://api.weixin.qq.com/shakearound/device/applyid?access_token={0}", accessToken);string postData =applyInfo.ToJson();

ShakeDeviceApplyJson data
= null;
ShakeDeviceApplyResult result
= JsonHelper<ShakeDeviceApplyResult>.ConvertJson(url, postData);if (result != null)
{
data
=result.data;
}
returndata;
}

2)页面管理

同样页面管理也包含了几个不同的方法,用来创建、编辑、删除页面等处理操作

1 新增页面
2 编辑页面信息
3 查询页面列表
4 删除页面

新增页面

新增摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。其中,图片必须为用素材管理接口上传至微信侧服务器后返回的链接。

接口调用说明

http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/shakearound/page/add?access_token=ACCESS_TOKEN
POST数据格式:json
POST数据例子:
{
   "title":"主标题",   
   "description":"副标题",	
   "page_url":" https://zb.weixin.qq.com ",	
   "comment":"数据示例",
   "icon_url":"http://3gimg.qq.com/shake_nearby/dy/icon "
}

根据这些接口定义,同样我们可以为IShakeAround接口类增加对应的接口定义了。

        #region 页面管理

        /// <summary>
        ///新增页面。///新增摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。///其中,图片必须为用素材管理接口上传至微信侧服务器后返回的链接。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="info">新增页面POST数据对象</param>
        /// <returns></returns>
        ShakePageResult AddPage(stringaccessToken, ShakePageJson info);/// <summary>
        ///编辑页面信息。///编辑摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="info">编辑页面POST数据对象</param>
        /// <returns></returns>
        ShakePageResult UpdatePage(stringaccessToken, ShakePageJson info);/// <summary>
        ///查询页面列表。///查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。///提供询方式:指定页面ID查询。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="begin">页面列表的起始索引值</param>
        /// <param name="count">待查询的页面个数</param>
        /// <returns></returns>
        ShakePageSearchJson SearchPage(string accessToken, int begin, intcount);/// <summary>
        ///查询页面列表。///查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。///提供询方式:批量拉取页面列表/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="page_ids">指定页面的id列表</param>
        /// <returns></returns>
        ShakePageSearchJson SearchPage(string accessToken, List<int>page_ids);/// <summary>
        ///查询页面列表。///查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。///提供两种查询方式,可指定页面ID查询,也可批量拉取页面列表。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="info"></param>
        /// <returns></returns>
        ShakePageSearchJson SearchPage(stringaccessToken, ShakePageSearchPaging info);/// <summary>
        ///删除页面。///删除已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。///只有页面与设备没有关联关系时,才可被删除。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="page_ids">指定页面的id列表</param>
        /// <returns></returns>
        CommonResult DeletePage(string accessToken, List<int>page_ids);#endregion 页面管理

3)其他管理

当然除了上面设备、页面的管理,还有一些如

上传图片素材、配置设备与页面的关联关系、以设备为维度的数据统计接口、以页面为维度的数据统计接口等方法接口用来实现,根据上面的处理方式定义即可。

4)测试代码

增加相关的接口定义,以及完成对应的接口实现,我们就需要编写一些测试类来对我们的接口进行测试的,这些对设备、页面的测试代码如下所示。

       /// <summary>
        ///摇一摇设备的申请、修改、绑定处理操作测试/// </summary>
        private void btnDevice_Click(objectsender, EventArgs e)
{
try{int poi_id = 275961135;
ShakeDeviceApplyJson result
= api.ApplyDevice(this.token, new ShakeDeviceAddJson() { quantity = 1, apply_reason = "测试", comment = "测试备注", poi_id =poi_id });if (result != null)
{
Console.WriteLine(result.ToJson());
if (result.device_identifiers != null)
{
ShakeDeviceIdentifier device
= result.device_identifiers[0];if (device != null)
{
int device_id = device.device_id; //465123; Console.WriteLine(device_id);string comment = "修改的备注";
ShakeDeviceIdentifier info
= newShakeDeviceIdentifier(device.device_id, device.uuid, device.major, device.minor);
CommonResult comResult
= api.UpdateDevice(this.token, info, comment);
MessageUtil.ShowTips(comResult.Success
? "操作成功" : "修改设备失败:" +comResult.ErrorMessage);

comResult
= api.BindDevice(this.token, info, poi_id);
MessageUtil.ShowTips(comResult.Success
? "操作成功" : "修改设备失败:" +comResult.ErrorMessage);
}
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}

}

        /// <summary>
        ///增加摇一摇素材、增加页面、修改页面、删除页面的操作示例代码/// </summary>
        private void btnPage_Click(objectsender, EventArgs e)
{
string file = FileDialogHelper.OpenImage(false);if(string.IsNullOrEmpty(file))
{
return;
}

ShakeMaterialJson mediaJson
= api.AddMaterail(this.token, file);if (mediaJson != null)
{
ShakePageJson json
= newShakePageJson()
{
title
= "主标题",
description
= "副标题",
comment
= "备注说明",
page_url
= "https://www.iqidi.com",
icon_url
=mediaJson.pic_url
};
ShakePageResult result
= api.AddPage(this.token, json);if (result != null)
{
Console.WriteLine(result.ToJson());
if (result.data != null)
{
json.page_id
=result.data.page_id;
json.comment
= "修改备注信息";

result
= api.UpdatePage(this.token, json);if (result != null)
{
Console.WriteLine(
"修改页面");
Console.WriteLine(result.ToJson());
}

CommonResult comResult
= api.DeletePage(this.token, new List<int>() { json.page_id.Value });
MessageUtil.ShowTips(comResult.Success
? "删除页面操作成功" : "删除页面失败:" +comResult.ErrorMessage);
}
}
}
}

个人认为,摇一摇设备,开启了一个更广阔的应用空间,随着越来越多设备商的支持,微信接口的完善和增加,可以迸发出更多有意思、实用的应用场景。

在上篇随笔《
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的操作说明。