2023年2月

在Winform开发框架的应用中,大多数的时候,我们都是采用固定的表名作为操作数据表的对象,但有时候,有些特殊的需要,需要动态指定表的名称,如表名称可能和年份有关系或者和登录用户的身份有关系等等。本文介绍基于我的Winform开发框架,介绍如何动态构造表名,实现向不同表获取数据的操作实现。

例如:数据库中有一些客户信息的表(Customer),按照年份存储,都是Customer2012这样格式的表,每次调用的时候需要告诉Customer业务对象实际上去访问哪个Customer的表,比如Customer2012或者Customer2013,那么我们应该如何操作才能达到这种效果呢?

首先我们来了解下整个
Winform开发框架
的分层布局如下所示。其中界面层UI直接访问BLL层,不需要通过网络(WCF开发框架通过网络访问数据),其中公用辅助类库Common层、实体类层可以在各个层中访问,并把常用的权限管理、字典管理封装为组件模块,直接调用,底层则使用工厂方式,来支持各种不同的数据库,其中UI层、BLL层、DAL层、实体层均使用继承类方式实现最良好的封装、最优的代码设计。

业务访问层的设计图如下所示。

数据访问层的设计如下所示。

由于基类的封装良好性,以及利用泛型获得强类型的操作体验,减少出错,更加方便易用,在扩展上也更加高效快捷。

综上所述,为了实现动态表名的操作,我们只需要扩展三个类即可实现,第一个是BaseBLL业务层基类,提供给接口给界面进行调用;第二个是IBaseDAL,提供数据访问层接口进行调用,第三个是在AbstractBaseDAL基类中实现修改数据表的接口即可。下面我们来看具体的代码分析。

1)在BaseBLL增加InitTableName函数的实现。

    public class BaseBLL<T> where T : BaseEntity, new()
{
#region 构造函数 private string dalName = "";protected IBaseDAL<T> baseDal = null;public BaseBLL() : this("")
{
}
public BaseBLL(stringdalName)
{
.......................

baseDal
= Reflect<IBaseDAL<T>>.Create(this.dalName, Assembly.GetExecutingAssembly().GetName().Name);
}
#endregion /// <summary> ///初始化数据库表名/// </summary> /// <param name="tableName">数据库表名</param> public void InitTableName(stringtableName)
{
baseDal.InitTableName(tableName);
}
}

2)在IBaseDAL中增加接口定义

    /// <summary>
    ///数据访问层接口/// </summary>
    public interface IBaseDAL<T> whereT : BaseEntity
{
//其他接口定义 ............................./// <summary> ///初始化数据库表名/// </summary> /// <param name="tableName">数据库表名</param> void InitTableName(stringtableName);
}

3)在AbstractBaseDAL数据访问基类中实现InitTableName接口。

    /// <summary>
    ///数据访问层的基类/// </summary>
    public abstract class AbstractBaseDAL<T> where T : BaseEntity, new()
{
protected string tableName;//需要初始化的对象表名 protected string primaryKey;//数据库的主键字段名 protected string sortField;//排序字段 publicAbstractBaseDAL()
{}
/// <summary> ///指定表名以及主键,对基类进构造/// </summary> /// <param name="tableName">表名</param> /// <param name="primaryKey">表主键</param> public AbstractBaseDAL(string tableName, stringprimaryKey)
{
this.tableName =tableName;this.primaryKey =primaryKey;this.sortField = primaryKey;//默认为主键排序 }//省略其他代码 ............................../// <summary> ///初始化数据库表名/// </summary> /// <param name="tableName">数据库表名</param> public void InitTableName(stringtableName)
{
this.tableName =tableName;
}
}

4)界面层调用代码

以数据字典操作为例,我们动态指定表名可以调用下面代码,如下所示。

        private void btnTest_Click(objectsender, EventArgs e)
{
BLLFactory
<DictType>.Instance.InitTableName("tb_DictType");
List
<DictTypeInfo> list = BLLFactory<DictType>.Instance.GetAll();
}

以上就是动态表名在我的WInform开发框架中的实现扩展,虽然这个实现不算复杂,但是可以从中了解
Winform开发框架
的业务对象的调用过程,以及看到基类对象扩展的方便性和高效性。

由于底层的对象良好封装,在我们实际开发过程中,大多数情况下,不用编写额外的数据访问代码,对于界面及整体框架的代码,也有代码生成工具
Database2Sharp
能够实现快速的框架代码以及界面代码(包括Winform、Web界面代码)的生成,以此从根本上提高开发的效率,减少出错的机会,统一整体框架的代码风格,实现更加健壮的开发应用。

在上面一篇随笔《
代码生成工具之界面快速生成
》介绍了代码生成工具
Database2Sharp
的界面生成操作,其中介绍了Web界面(包括列表界面、内容显示、内容编辑界面的生成,另外还介绍了Winform界面的内容编辑界面的生成,本篇主要继续介绍Winform界面生成中的查询列表界面的操作展示等信息。

基于Winform的界面生成,配合我的Winform开发框架,提供了三种不同的界面生成,包括传统界面样式、DotNetBar界面样式和DevExpress界面样式的代码生成,这几种界面是目前Winform开发中非常常见的界面样式。

1、EnterpriseLibray架构代码及Web界面生成

基于EnterpriseLibray架构的代码生成,除了可以生成传统的业务层、数据访问层、数据访问接口层、实体层外,还同时生成了Web界面工程,Web界面工程已经添加了相关的引用程序集及必须的控件,生成后即可编译运行,一些表字段设计合理的话,甚至不用修改一行代码就能直接使用,如下所示。

生成的Web界面截图如下所示,包含列表查询界面、内容显示界面、内容编辑界面等。

内容编辑界面(新增和编辑)如下所示。

2、Winform界面生成

1)Winform数据编辑界面生成

在上面一篇随笔《
代码生成工具之界面快速生成
》同时也介绍了Winform界面的生成,上一篇主要介绍了Winform界面中的数据编辑界面。

Winform界面生成界面提供了很多参数进行控制,以期生成精细化的界面内容。

DevExpress界面样式的生成。

传统样式的界面生成。

2)Winform查询列表界面生成

很多情况下,查询列表界面很常见,如果能快速生成标准的界面,除了可以节省时间,提高开发效率外,也给我们统一界面风格及代码风格等方面,提供更好的支持。

复杂累赘的界面能够自动生成,绝对是开发过程的一大提升,让我们更加享受开发的乐趣。

1)设置好相关的界面参数,如指定列表的查询字段、列表显示字段,选择界面样式,以及设定代码的主命名空间等参数。

2)生成界面代码到文件后,把文件直接复制到项目中,不用修改直接就可以看到列表界面效果,Yeah,正是我们需要的样式。

当然,上面的查询条件可以一行,也可以两行或者多行,界面生成的时候,会自动合理计算好布局,保证完美展现我们想要的列表界面效果。以上界面几乎不用任何修改就直接可以编译运行,里面的后台代码也同时生成了。

后台代码里面生成包括,分页控件展示及列表查询显示、Excel数据导入、数据导出等功能的后台界面代码。以下就是完整生成的界面后台代码,没有编辑过的列表界面后台代码如下所示。

    public partial classFrmItemDetail : BaseDock
{
publicFrmItemDetail2()
{
InitializeComponent();

InitDictItem();
this.winGridViewPager1.OnPageChanged += newEventHandler(winGridViewPager1_OnPageChanged);this.winGridViewPager1.OnStartExport += newEventHandler(winGridViewPager1_OnStartExport);this.winGridViewPager1.OnEditSelected += newEventHandler(winGridViewPager1_OnEditSelected);this.winGridViewPager1.OnAddNew += newEventHandler(winGridViewPager1_OnAddNew);this.winGridViewPager1.OnDeleteSelected += newEventHandler(winGridViewPager1_OnDeleteSelected);this.winGridViewPager1.OnRefresh += newEventHandler(winGridViewPager1_OnRefresh);this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;this.winGridViewPager1.ShowLineNumber = true;this.txtItemNo.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtItemName.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtManufacture.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtMapNo.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtSpecification.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtMaterial.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtItemBigType.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtItemType.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtUnit.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);this.txtSource.KeyUp += new System.Windows.Forms.KeyEventHandler(this.SearchControl_KeyUp);

}
/// <summary> ///编写初始化窗体的实现,可以用于刷新/// </summary> public override voidFormOnLoad()
{
BindData();
}
/// <summary> ///初始化字典列表内容/// </summary> private voidInitDictItem()
{
//初始化代码 }/// <summary> ///分页控件刷新操作/// </summary> private void winGridViewPager1_OnRefresh(objectsender, EventArgs e)
{
BindData();
}
/// <summary> ///分页控件删除操作/// </summary> private void winGridViewPager1_OnDeleteSelected(objectsender, EventArgs e)
{
if (MessageDxUtil.ShowYesNoAndTips("您确定删除选定的记录么?") ==DialogResult.No)
{
return;
}
int[] rowSelected = this.winGridViewPager1.GridView1.GetSelectedRows();foreach (int iRow inrowSelected)
{
string ID = this.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow, "ID");
BLLFactory
<ItemDetail>.Instance.Delete(ID);
}

BindData();
}
/// <summary> ///分页控件编辑项操作/// </summary> private void winGridViewPager1_OnEditSelected(objectsender, EventArgs e)
{
string ID = this.winGridViewPager1.gridView1.GetFocusedRowCellDisplayText("ID");
List
<string> IDList = new List<string>();for (int i = 0; i < this.winGridViewPager1.gridView1.RowCount; i++)
{
string strTemp = this.winGridViewPager1.GridView1.GetRowCellDisplayText(i, "ID");
IDList.Add(strTemp);
}
if (!string.IsNullOrEmpty(ID))
{
FrmEditItemDetail dlg
= newFrmEditItemDetail();
dlg.ID
=ID;
dlg.IDList
=IDList;if (DialogResult.OK ==dlg.ShowDialog())
{
BindData();
}
}
}
/// <summary> ///分页控件新增操作/// </summary> private void winGridViewPager1_OnAddNew(objectsender, EventArgs e)
{
btnAddNew_Click(
null, null);
}
/// <summary> ///分页控件全部导出操作前的操作/// </summary> private void winGridViewPager1_OnStartExport(objectsender, EventArgs e)
{
string where =GetConditionSql();this.winGridViewPager1.AllToExport = BLLFactory<ItemDetail>.Instance.FindToDataTable(where);
}
/// <summary> ///分页控件翻页的操作/// </summary> private void winGridViewPager1_OnPageChanged(objectsender, EventArgs e)
{
BindData();
}
/// <summary> ///根据查询条件构造查询语句/// </summary> private stringGetConditionSql()
{
SearchCondition condition
= newSearchCondition();
condition.AddCondition(
"ItemNo", this.txtItemNo.Text, SqlOperator.Like);
condition.AddCondition(
"ItemName", this.txtItemName.Text, SqlOperator.Like);
condition.AddCondition(
"Manufacture", this.txtManufacture.Text, SqlOperator.Like);
condition.AddCondition(
"MapNo", this.txtMapNo.Text, SqlOperator.Like);
condition.AddCondition(
"Specification", this.txtSpecification.Text, SqlOperator.Like);
condition.AddCondition(
"Material", this.txtMaterial.Text, SqlOperator.Like);
condition.AddCondition(
"ItemBigType", this.txtItemBigType.Text, SqlOperator.Like);
condition.AddCondition(
"ItemType", this.txtItemType.Text, SqlOperator.Like);
condition.AddCondition(
"Unit", this.txtUnit.Text, SqlOperator.Like);
condition.AddCondition(
"Source", this.txtSource.Text, SqlOperator.Like);string where = condition.BuildConditionSql(DatabaseType.SqlServer).Replace("Where", "");return where;
}
/// <summary> ///绑定列表数据/// </summary> private voidBindData()
{
this.winGridViewPager1.DisplayColumns = "ItemNo,ItemName,Manufacture,MapNo,Specification,Material,ItemBigType,ItemType,Unit,Price,Source,StoragePos,UsagePos,Note,WareHouse,Dept";#region 添加别名解析 this.winGridViewPager1.AddColumnAlias("ItemNo", "备件编号");this.winGridViewPager1.AddColumnAlias("ItemName", "备件名称");this.winGridViewPager1.AddColumnAlias("Manufacture", "供货商");this.winGridViewPager1.AddColumnAlias("MapNo", "图号");this.winGridViewPager1.AddColumnAlias("Specification", "规格型号");this.winGridViewPager1.AddColumnAlias("Material", "材质");this.winGridViewPager1.AddColumnAlias("ItemBigType", "备件属类");this.winGridViewPager1.AddColumnAlias("ItemType", "备件类别");this.winGridViewPager1.AddColumnAlias("Unit", "单位");this.winGridViewPager1.AddColumnAlias("Price", "单价");this.winGridViewPager1.AddColumnAlias("Source", "来源");this.winGridViewPager1.AddColumnAlias("StoragePos", "库位");this.winGridViewPager1.AddColumnAlias("UsagePos", "使用位置");this.winGridViewPager1.AddColumnAlias("Note", "备注");this.winGridViewPager1.AddColumnAlias("WareHouse", "所属库房");this.winGridViewPager1.AddColumnAlias("Dept", "所属部门");#endregion string where =GetConditionSql();
List
<ItemDetailInfo> list = BLLFactory<ItemDetail>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo);this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<ItemDetailInfo>(list);this.winGridViewPager1.PrintTitle = Portal.gc.gAppUnit + "--" + "信息报表";
}
/// <summary> ///查询数据操作/// </summary> private void btnSearch_Click(objectsender, EventArgs e)
{
BindData();
}
/// <summary> ///新增数据操作/// </summary> private void btnAddNew_Click(objectsender, EventArgs e)
{
FrmEditItemDetail dlg
= newFrmEditItemDetail();if (DialogResult.OK ==dlg.ShowDialog())
{
BindData();
}
}
/// <summary> ///提供给控件回车执行查询的操作/// </summary> private void SearchControl_KeyUp(objectsender, KeyEventArgs e)
{
if (e.KeyCode ==Keys.Enter)
{
btnSearch_Click(
null, null);
}
}
private string moduleName = "备件信息";/// <summary> ///导入Excel的操作/// </summary> private void btnImport_Click(objectsender, EventArgs e)
{
string templateFile = string.Format("{0}-模板.xls", moduleName);
FrmImportExcelData dlg
= newFrmImportExcelData();
dlg.SetTemplate(templateFile, System.IO.Path.Combine(Application.StartupPath, templateFile));
dlg.OnDataSave
+= newFrmImportExcelData.SaveDataHandler(ExcelData_OnDataSave);
dlg.OnRefreshData
+= newEventHandler(ExcelData_OnRefreshData);
dlg.ShowDialog();
}
void ExcelData_OnRefreshData(objectsender, EventArgs e)
{
BindData();
}
boolExcelData_OnDataSave(DataRow dr)
{
bool success = false;
ItemDetailInfo info
= newItemDetailInfo();
info.ItemNo
= dr["备件编号"].ToString();
info.ItemName
= dr["备件名称"].ToString();
info.Manufacture
= dr["供货商"].ToString();
info.MapNo
= dr["图号"].ToString();
info.Specification
= dr["规格型号"].ToString();
info.Material
= dr["材质"].ToString();
info.ItemBigType
= dr["备件属类"].ToString();
info.ItemType
= dr["备件类别"].ToString();
info.Unit
= dr["单位"].ToString();
info.Price
= Convert.ToDecimal(dr["单价"].ToString());
info.Source
= dr["来源"].ToString();
info.StoragePos
= dr["库位"].ToString();
info.UsagePos
= dr["使用位置"].ToString();
info.Note
= dr["备注"].ToString();
info.WareHouse
= dr["所属库房"].ToString();
info.Dept
= dr["所属部门"].ToString();

success
= BLLFactory<ItemDetail>.Instance.Insert(info);returnsuccess;
}
/// <summary> ///导出Excel的操作/// </summary> private void btnExport_Click(objectsender, EventArgs e)
{
string file = FileDialogHelper.SaveExcel(string.Format("{0}.xls", moduleName));if (!string.IsNullOrEmpty(file))
{
List
<ItemDetailInfo> list = BLLFactory<ItemDetail>.Instance.GetAll();
DataTable dtNew
= DataTableHelper.CreateTable("序号|int");
DataRow dr;
int j = 1;for (int i = 0; i < list.Count; i++)
{
dr
=dtNew.NewRow();
dr[
"序号"] = j++;
dr[
"备件编号"] =list[i].ItemNo;
dr[
"备件名称"] =list[i].ItemName;
dr[
"供货商"] =list[i].Manufacture;
dr[
"图号"] =list[i].MapNo;
dr[
"规格型号"] =list[i].Specification;
dr[
"材质"] =list[i].Material;
dr[
"备件属类"] =list[i].ItemBigType;
dr[
"备件类别"] =list[i].ItemType;
dr[
"单位"] =list[i].Unit;
dr[
"单价"] =list[i].Price;
dr[
"来源"] =list[i].Source;
dr[
"库位"] =list[i].StoragePos;
dr[
"使用位置"] =list[i].UsagePos;
dr[
"备注"] =list[i].Note;
dr[
"所属库房"] =list[i].WareHouse;
dr[
"所属部门"] =list[i].Dept;
dtNew.Rows.Add(dr);
}
try{string error = "";
AsposeExcelTools.DataTableToExcel2(dtNew, file,
outerror);if (!string.IsNullOrEmpty(error))
{
MessageDxUtil.ShowError(
string.Format("导出Excel出现错误:{0}", error));
}
else{if (MessageDxUtil.ShowYesNoAndTips("导出成功,是否打开文件?") ==System.Windows.Forms.DialogResult.Yes)
{
System.Diagnostics.Process.Start(file);
}
}
}
catch(Exception ex)
{
LogHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
}
}
}

传统界面效果生成操作,只需要选择传统界面样式,生成即可,得到的初始化界面如下所示。

如果是只是指定了几个查询条件(一行的情况),那么工具会自动计算好布局位置,得到界面效果如下所示。

代码生成工具Database2Sharp还提供了生成基于DotNetbar的样式的查询列表界面代码,和上图类似,在此不再赘述。

通过代码生成工具,不仅可以生成整体性的EnterpriseLibary框架结构代码,连我们繁琐的Web界面、Winform界面都能快速、高标准生成,开发界面其实是一件很轻松快速的事情,不要整天从数据库字段和界面属性设置中来回切换了。有了Database2Sharp代码生成工具,一切变得宁静很多,但更加快速。

Database2Sharp是一款主要用于C#代码以及数据库文档生成的工具,软件支持Oracle、SqlServer、MySql、Access、Sqlite等数据库的代码生成,可以生成各种架构代码、生成Winform界面代码、Web界面代码、导出数据库文档、浏览数据库架构、查询数据、生成Sql脚本等,还整合自定义模板和数据库信息的引擎,方便编写自定义模板调试和开发。

1、EnterpriseLibrary架构代码生成

1.1 代码生成总体概述

EnterpriseLibrary代码生成时一个整体性项目代码的生成操作,他能根据设计好数据库信息以及模板文件,生成一个完整性非常高的项目。一般结合我的Winform开发框架进行增量式的项目开发,效率更高,而且更多组件模块的集成,完美的整合,以及模块化的封装,能带给你无穷的开发乐趣同时,使得项目无论从代码风格、用户界面、设计理念,都能保持很好的统一,快速优雅的完成碰到的项目。

使用Database2Sharp来生成框架代码,虽然直接生成的代码,就是一个整体方案的代码,基本上可以直接运行。而Winform开发框架和代码生成工具生成的项目组织上有所不同。下面提供几个注意的地方。

1)代码生成工具生成的代码是基于Project的,而Winform开发框架为了项目数量,方便管理,是把业务层、数据访问层、数据接口层、实体层放到一个工程项目中了(
WCF
项目会把实体层独立作为一个项目处理
),因此生成的代码我们复制到对应的目录位置就可以了,默认命名空间不需要改动。

2)为了代码生成方便,代码生成工具需要把数据库字段的中文说明作为代码注释或者说明的一部分,因此,设计数据库(SqlServer、Oracle等)的时候,我们强烈要求把注释添加到字段说明里面去。

3)数据库表一般需要提供一个主键关键字(建议取名为ID),主键字段可以为自增长的整形类型,也可以是任意字符型。建议SqlServer一般采用自增长整形、Oracle采用Number类型,并为每个表指定一个部分同名的序列名称,如Seq_ABC,其中ABC代表对应的表名。

1.2 数据库表设计

由于我们一般的开发过程是,先设计好数据库,然后生成项目代码框架,再进行调整完善。因此在开发前,我们非常关键的第一步就是要设计好数据库。

数据库设计可以采用多种方式进行,但为了提高设计效率以及方便修改等操作,一般我们最好基于数据库建模软件进行数据库的设计过程,如PowerDeigner就是一个很好的数据库设计平台。如下图所示:

当然数据库设计的时候,也可以指定它们之间的逻辑引用关系,这样对于数据的完整性校验比较有保证,如下图所示。

无论是上面那种设计关系,我们都需要在设计过程中,注意到表字段备注信息,由于在代码生成工具生成代码的时候,很多时候需要使用中文的字段名称来描述,如实体类字段的备注信息、界面的查询字段说明、列表的表头提示等等,这些都是从你设计的数据库表字段备注里面来,因此要特别注意。在PowerDesigner设计软件里面,只需要指定Comment,然后生成SQL的时候,就会有相关的备注信息了。

1.3 代码生成参数配置

基于前面设计好了数据库,生成相关的SQL,然后在数据库管理系统(Oracle/SqlServer/MySql等)上执行脚本,创建数据库成功后。就可以利用代码生成工具进行代码生成了,但生成代码前,需要配置几项参数,以求能够更完美生成项目工程,实现快速使用。
(一)基础参数

在上图我们可以看到,代码生成工具只需要很少的几个属性,就能较好的生成所需的代码。
主命名空间,就是我们的项目代码的命名空间的前面基本不变的部分。如实体类的命名空间一般为WHC.TestProject.Entity,那么主命名一般为WHC.TestProject即可,相应界面层生成后的命名空间为WHC.TestProject.UI,业务逻辑层的命名空间为WHC.TestProject.BLL,数据访问层根据不同的数据库生成不同的命名空间,如Oracle数据访问层命名空间为WHC.TestProject.DALOracle,SqlServer数据访问层命名空间为WHC.TestProject.DALSQL, 而Access数据访问层命名空间为WHC.TestProject.DALAccess等如此类推。
输出目录,顾名思义就是我们代码最终的生成目录了。
过滤表名前缀,是减少代码出现多余的表名前缀。一般在大一些项目上,或者仅仅保持良好的命名习惯上,我们都应该给不同应用范畴的表,通过前缀来进行区分,如我对于基础类可能用“TB_”前缀来区分,对于权限范畴的可能用“T_ACL_”来区分,其他的可能用“T_”来区分等等。这样我们在生成代码的时候,就应该去掉这些多余的前缀,使得我们的业务类更加易读。如字典大类表名称为“TB_DictType”,那么对应的业务类生成应该就是“DictType”了。
(二)别名修改
除了以上的几项设置外,有时候,为了较好调整表名或者字段属性的名称,也增加了一项修改别名的功能,如“ABC”的表名我们不知道是什么意思,如果把它的别名修改为“Company”这样的单词,一般人可能就知道这个类库代表的意义了,别名修改就是为了这样需求而出现的。
别名修改,有表名的别名,和表字段的别名修改两种,如下所示

1.4 代码生成

1.4.1 框架介绍

无论是在项目开始阶段的全新代码生成或者在框架搭建起来后(如基于Winform开发框架、WCF开发框架),虽然起点有所不同,但是都是一个框架模式搭建完善相关的业务模块的。
无论是基于Winform、Webform的开发应用,甚至是基于分布式程序开发的WCF开发模式,利用Database2Sharp都能使你感到事半功倍的成就感,由于它们三者都是在基于EnterpriseLibrary的框架代码生成基础上完成的,这个框架模式是通用于上述几种不同的应用开发框架,它们大致的框架布局如下所示。


Winform开发框架以及Web开发框架,它们就是在BLL业务逻辑层之上搭建一层界面展示层而已,而WCF开发框架则还需要在BLL业务逻辑层之上搭建一层WCF服务层,然后在界面层和WCF服务层之间,通过服务应用的方式,增加一层WCF服务层的代理层,如下所示。


Winform开发框架适用于开发用户体验好、功能强大的业务管理系统,可以基于单机版数据库(如Access、Sqlite等数据库)或者基于局域网的数据库,如Oracle、SqlServer、MySql、Mongodb、DB2等网络型的数据库应用,也就是我们传统所说的C/S架构模式。

Web开发,一般也可以开发功能强大的业务管理系统,不过较Winform来讲,它的用户体验会差一些,而且需要部署在IIS上,部署会比较麻烦一些,但特点是能够部署分布式的应用,而且不用安装软件,直接在IE、Chrome浏览器上打开URL连接即可使用,是传统所说的B/S应用模式。

而WCF开发框架,是利用了C/S应用模式里面的程序良好的界面体验性、响应快速性等特点,也充分利用了B/S应用模式里面的分布式架构特点,是一种融合前两种框架特点应用模式,对于要求分布式,有要较好的用户体验性,这种框架是很好的选择。

1.4.2 数据库连接配置

了解了不同的框架用途,我们就可以利用代码生成工具来搭建我们所需的业务管理系统了。
第一步,配置对应数据库连接信息。数据库SqlServer的配置信息如下所示。

Oracle数据库配置信息如下所示:

Oracle数据库除了配置以上信息外,还要注意的是,需要利用NetManager来配置好对应Oracle数据库名称的侦听配置,如下所示。也就是Oracle需要安装好对应版本的Oracle客户端,配置好数据库侦听信息才能使用代码生成工具Database2Sharp访问Oracle数据库表信息,这样是一般常规Oracle工具需要做的操作,包括PLSQL Developer。

MySql数据库配置信息如下所示,注意的是,MySql数据库一般有一些编码的设置,为了有效获取对应数据库表、字段的备注等中文信息,需要设置正确的字符编码才能获取到:

Access数据库配置信息如下所示,一般情况下,只需要指定Access数据库路径即可,如果有密码,填上数据库密码信息。

Sqlite数据库配置信息如下所示,一般情况下,只需要指定Sqlite数据库路径即可。

1.4.3 代码生成

代码生成工具,是结合数据库信息进行代码生成的过程,因此需要先获取对应数据库信息。
第一步,在Database2Sharp左边的树形数据库列表中,单击展开对应数据库的详细表,由于Database2Sharp是一次性加载方式,加载数据库表的信息同时,也会加载相关的视图、存储过程(Oracle、SqlServer)、以及表字段和表之间的关系等等信息。
单击其中一个表,我们可以看到对应该表的字段信息,以及创建表的SQL语句,也就是我们常说的DDL脚本。

第二步,在数据库节点上,单击鼠标右键,选择【代码生成】- 【EnterpriseLibrary代码生成】菜单,开始生成代码如下所示。

第三步,选择相应的数据库以及数据库表,用于代码的生成。

第四步,设置相关的代码生成配置信息,包括项目主命名空间,输出目录,以及过滤表名前缀等。

第五步,确认代码生成操作,一般确认,就会对选定的表进行项目代码的生成操作,生成一体化的整体性解决方案。

如果项目是要生成基于WCF开发框架的,那么勾选“同时生成WCF服务项目”选项即可(
工具注册用户可用
)。

勾选后,可以修改WCF服务的项目名称,方便创建不同的WCF业务项目。WCF项目是基于VS2010开发环境、.NET4.0框架的项目,这是目前最优的配置信息了。

如果勾选生成后打开解决方案,那么代码生成完成后,就会自动打开项目工程,你可以尝试编译,一般情况下,可以完全编译通过的。

以上就是我对于代码生成工具Database2Sharp的一些总结,希望对大家使用Database2Sharp快速开发项目有所帮助。

国庆去了一趟北京,回来一直忙着各种各样的事情,有公司的,有个人的,就没停过来,所以很久没有写博客了,写博客要有一个好的心情及一个好的思路,否则宁愿不写,这是我的准则。虽然这段时间没有写博客分享各种经验,不过一直还在做一些框架相关的事情,很多东西沉淀下来,慢慢有空就继续分享,与大家做一些探讨研究了。

Winform开发框架方面的文章我介绍很多了,有宏观介绍,也有部分技术细节的交流,每次我希望能从不同角度,不同方面来介绍我的WInform开发框架,这些其实都是来源于客户的需求,真实的项目场景。本文主要介绍Winform开发框架的各种演化,适合在各种系统应用场景下的使用过程。Winform开发框架,从技术方向上讲,可以分为传统的Winform开发框架,WCF开发框架。对于这两种分类,根据数据存放的位置不同,也可以演化成多种应用模式。下面分别对这些演化的Winform框架进行介绍。

1、传统Winform开发框架

我这里指的传统Winform开发框架,就是利用数据库中间件,直接访问数据库的一种应用框架,根据数据库管理系统部署的位置的不同,可能分为单机版(如Access数据库、Sqlite数据库等),局域网网络版(如SqlServer、Oracle等数据库),局域网网络版,一般需要的是把数据库部署在局域网另外一个电脑上,这样应用和数据库分开,也有利于性能的提高和数据的分享。

这种开发模式,在dotnet里面,就是利用基于ado.net的操作,实现数据的直接访问,是一种比较常规的开发模式,不过问题,就是,不能通过互联网进行数据的访问,只能在单机或者局域网的环境下进行业务管理系统的部署和使用,相对目前很多分布式的应用来讲,有一定的局限性。

不过这种方式也是很常见的模式,常用在一些内部业务管理系统或者一些工作流系统的维护上,由于Winform的体验性比较好,数据也能有效管理控制,开发部署成本也相对较低,系统开发效率以及应用性能也比较高,因此是一种比较常见的框架表现模式。

如下面我的Winform开发框架界面,就是一个比较标准,传统式的界面,这种界面一般大家都比较常见。

这种Winform开发框架 是通过数据访问层访问各种指定的数据库,如SqlServer或者Oracle等,一般底层封装好一点的框架,基本上都会支持多种数据库,方便在不同的业务中使用。我的传统Winform开发框架的架构设计图,如下所示。


2、传统WCF开发框架

除了以上的传统的WInform开发框架,基于WInform的技术和WCF的分布式技术,形成了另外一种Winform开发框架,即为WCF开发框架,我这个WCF开发框架的介绍也比较多,绝大多数都是来源于真实的项目应用。其实WCF技术,即可用于Winform上,也可以用于Web上,WCF技术可以用在很多领域,如Web开发、类似Socket通讯的即时通讯应用等,这里介绍的WCF开发框架,是基于Winform的基础上使用的WCF开发框架。

这里定义的传统WCF开发框架,是指利用WInform框架的模式,来承载B/S的方式获取数据进行展示,本地不存储数据。就是直接获取数据并在列表控件或者其他基础控件上显示数据。

由于WCF框架应用了很多新的技术,以及是基于分布式网络环境的应用,因此,需要考虑服务的部署,数据访问的安全性(用户名密码访问、X509证书加密、其他授权访问),数据响应以及错误处理等方方面面,如果是第一次接触,没有很好的指导及案例学习,可能消化相关的概念并理解,就需要一个不短的过程。

传统的WCF开发框架,由于是本地不缓存数据,实时通过WCF服务代理,向WCF服务请求数据,获取数据后进行显示的过程,开发思路相对比较简单,因此这种方式在很多WCF应用上,是比较常见的一种。

传统的基于Winform的WCF开发框架,界面可以和上面的Winform开发框架界面一样,不过它的获取数据的机制不同,它不是通过数据访问类访问本地的数据库,而是通过服务器公布WCF服务来获取不同数据对象,然后再Winform中进行展现,如下面是它的一个访问机制的示意图。

对于这个传统的WCF开发框架的架构,它其实是通过Winform的客户端WCF代理类,实现和远端服务器的WCF服务进行通讯,一般是基于Soap协议的XML格式,当然可以很好的配置加密机制,如X509证书加密,这样传输数据就比较安全,同时对于WCF服务,还可以采用基于自定义的用户名、密码的验证方式来有效提高WCF服务接口的安全性。以下是WCF开发框架的架构设计图,我们从中可以看出,它的应用程序的界面表现层是和WCF服务客户端代理层有关联,而代理层和WCF服务层有关联,这种隔绝了应用程序直接访问数据库的弊端,提高数据安全性,同时也使得应用程序实现了分布式的开发应用。


3、离线式WCF开发框架

相对于上面介绍的传统式WCF开发框架,这种离线式的WCF开发框架 ,就是结合了传统Winform开发框架的数据访问方式,又利用了WCF分布式数据获取的特点,使得数据可以离线使用,在一种业务要求集中化,又要求不影响正常业务操作的应用系统场景下比较适合,如我的送水管理软件网络版,就是利用这种模式,分店可以独立维护数据,没有网络也一样可以正常进行业务操作,但是在网络畅通的时候,可以向总店发送分店的数据,或者获取总店的公共数据。

这种离线模式的WCF开发框架,是一种对网络环境要求不高,既可以独立使用,又可以和总部发生业务数据往来的一种模式。这种场景下,如果不考虑数据同步及上传的情况下,他就是一个传统的Winform开发框架模式。

考虑数据同步,就会使得整个离线式的系统可能更加复杂,要求可能需要自身记录可能的数据变更(添加、修改、删除),还需要经常性的同步系统公用数据,如果要求同步的间隔比较短,那么我们就要多考虑一些因素。

离线式的WCF开发框架,数据存储我们一般选择单机版、方便部署复制的Sqlite数据库,或者Access数据库,在一些特别的情况下,才会选择比较大的SQLServer数据库或者Mysql数据库,由于WCF开发框架的应用客户端和服务器版本的数据库不同,如果没有比较弹性化、支持多数据库的WCF开发框架,这种开发模式,就会比较吃力繁琐。

离线式的WCF开发框架,一个特点就是基本上显示,以及保存等操作数据库的数据,都是本地的数据库,不是远端的服务器数据库,这样,就需要记录所有发生变更的数据库操作,包括写入,删除、修改等,以便在网络畅通的情况下,可以上传数据到服务器上面。

和上面第二点介绍的传统式WCF开发框架,这个就是多了一个本地数据库,这样可以提高数据的网络依赖性,其他日常操作的响应,在一些网络环境不经常连接的地方,可以很好的发挥作用,而且,通过实现把需要更新的数据导出到文件中,还可以实现数据的离线式同步,基本上可以完全独立于网络了。

这个模式的WCF框架架构图,是综合了Winform开发框架架构设计图和WCF框架架构设计图的特点,既有访问本地数据库的模块,也有访问操作WCF服务的模块,在不同的地方发挥不同的作用。

4、混合式WCF开发框架

除了以上几种,还有一种是可以在传统winform开发框架和WCF开发框架之间自由切换,这种其实用传统式WCF开发框架或者离线式WCF开发框架就可以解决,不过有些人觉得在局域网里面访问WCF服务,有点小题大做,而且性能也有一定的损失,那么就可以在界面调用具体操作数据类的时候,封装多一层,通过配置来指向WCF代理类还是Winform框架中的业务访问类,这种确实也可以实现的。

还有一种情况,就是把系统划分为很多万能模块(既适应WInform集成,也适应WCF集成),在不同的场合进行不同的开光切换,而且只是一个条件语句的跳转,就可以实现具体的切换,这样非常有利于模块的集成封装。

综上所述,我自己在各种项目的框架应用选择上,都是以最适合客户场景来进行选型,如一般的小型的业务管理系统,用Winform开发框架能快速给客户做好系统开发;而对于要求分布式的业务管理系统,工作的时候网络不成问题的这种,我一般就选择传统的WCF开发框架;而对于复杂的,网络不能保证的,要求分布式应用,数据集中管理的,一般采用离线式的WCF开发框架。如果为了更好开发集成模块,适应各种场景的集成,那么就可能要求模块具有混合式WCF开发框架的特点。

由于项目的需要,需要在项目的Winform系统的一个模块中集成手写签名的功能,一开始对这块不是很了解,只是了解他能够替代鼠标进行签名。既然是签名,一般就是需要记录手稿图片,作为一个记录核实的凭证,因为有效的签名是很难模拟的。市场上也存在很多类型的电子签名笔,一时间还真不知道那种适合。本文主要从签名笔的选型以及功能实现等方面来对实现这个需求进行分析介绍,希望能够给有同样际遇的朋友一个参考。

1、电子手写签名介绍

电子手写签名,其实就是模拟真实的笔进行签名的过程,我这里主要是介绍使用外部设备来记录手稿图片的需求,这样其实就是类似于把我们真实在纸张上的签名内容,放到了电脑记录面板上进行操作了,而这个操作模拟,其实就是利用了类似鼠标功能的接触笔来实现的。

有些笔是在纸上或者电脑屏幕上进行模拟签名,通过一个接收器方式接受笔的接触信号,一般要先设定纸张或者屏幕的范围,然后进行签名书写。如下所示就是这种。

由于签名笔淘宝上也有很多,开始淘到的就是类似这种,不过效果不理想,好像总是定位不准,而且和鼠标发生严重冲突,基本上操作不了,商家客服说很少有这种现象发生,但是却发生在我身上,于是只有退货。然后淘到的是一款汉王手写板笔,开始用的还可以,其实就是代替了鼠标进行操作,试过可以后,就搁置起来。手写板的大概样式类似下图所示。

一直用鼠标模拟签名进行开发,写该篇随笔的时候,本来想用来展示下效果,可惜又用不了,不知道什么原因。

做签名功能开发的时候,其实我是不关注手写笔功能的,因为我想其实如果鼠标能操作就可以,手写笔其实也就应该可以操作。因此只需要在输入的地方记录鼠标操作的痕迹,类似手写签名的效果即可,大概如下所示。

2、手写签名的实现

从上图可以看到,只需要提供一个类似绘图的面板即可记录鼠标的轨迹,也就是功能有点类似Windows自带的白板(或者绘图板)软件即可。

其实要模拟鼠标签名的效果,只需要利用功能强大的GraphicsPath对象就差不多了,剩下的就是记录点和绘制点,设置绘图笔的宽度和颜色等方面。下面我们看看具体的实现代码吧。

首先要申明几个必要的对象,来承载相关的信息。

        //记录直线或者曲线的对象
        private System.Drawing.Drawing2D.GraphicsPath mousePath = newSystem.Drawing.Drawing2D.GraphicsPath();//画笔透明度
        private int myAlpha = 100;//画笔颜色对象
        private Color myUserColor = newColor();//画笔宽度
        private int myPenWidth = 3;//签名的图片对象
        public Bitmap SavedBitmap;

从上图效果图上,我们看到,我们在其中放置了一个绘图面板,其实就是一个PictureBox对象而已,我们只需要在PictureBox对象,记录鼠标的移动、鼠标按下,以及对象刷新操作事件即可实现模拟签名的效果了,如下代码所示。

        private void pictureBox1_MouseMove(objectsender, MouseEventArgs e)
{
if (e.Button ==System.Windows.Forms.MouseButtons.Left)
{
try{
mousePath.AddLine(e.X, e.Y, e.X, e.Y);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
pictureBox1.Invalidate();
}
private void pictureBox1_MouseDown(objectsender, MouseEventArgs e)
{
if (e.Button ==System.Windows.Forms.MouseButtons.Left)
{
mousePath.StartFigure();
}
}
private void pictureBox1_Paint(objectsender, PaintEventArgs e)
{
try{
myUserColor
=System.Drawing.Color.Blue;
myAlpha
= 255;
Pen CurrentPen
= newPen(Color.FromArgb(myAlpha, myUserColor), myPenWidth);
e.Graphics.DrawPath(CurrentPen, mousePath);
}
catch{ }
}

保存及清空操作,其实很简单,清空的时候,记得把绘图面板清空,并重置路径对象即可。保存也是记录PictureBox对象的大小宽度,把图片存储到图片对象里面,供控件使用。

具体实现如下所示。

        private void btnClear_Click(objectsender, EventArgs e)
{
pictureBox1.CreateGraphics().Clear(Color.White);
mousePath.Reset();
}
private void btnSave_Click(objectsender, EventArgs e)
{
SavedBitmap
= newBitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(SavedBitmap,
new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height));this.DialogResult =System.Windows.Forms.DialogResult.OK;
}

上面实现还不能完成一个最终的签名效果,有时候,我们需要把这些图片放到数据库里面,如果是把上面的操作的图片记录下来,发现很大,一般我们签名的效果显示,不会很大,一方面只需要辨认其笔迹即可,另外一方面也不会过于增大数据库的存储空间。那么我们就需要对图片大小进行一定的处理了。其实可能在详细信息里面,我们就这样记录显示即可。那么就一定要裁剪图片的大小。

以上的医生签名,我们触发的操作就是弹出一个签名窗体,在其中绘制签名,确认后返回,并把图片进行显示在详细信息窗体里面。

        private void btnDoctorSign_Click(objectsender, EventArgs e)
{
FrmSignicture dlg
= newFrmSignicture();if (dlg.ShowDialog() ==System.Windows.Forms.DialogResult.OK)
{
this.picDoctor.Image =dlg.SavedBitmap;
}
}

为了安装最终显示的PictureBox的大小保存图片,我们需要裁减,裁减就是重新根据图片大小进行保存Bitmap对象,这种可以从全屏最初的几M缩小到十几K,方便存储。

至于图片对象存储到数据库,这个由于利用了我Winform开发框架里面的数据库基础对象,基本上不需要特别对待及处理,只需要把图片字段的Byte数组获得即可了。这里就不在赘述Winform开发框架的功能及相关的基类处理了。

        publicBitmap SaveImage(PictureBox pictureBox1)
{
Bitmap SavedBitmap
= newBitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(SavedBitmap,
new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Height));returnSavedBitmap;
}

最后,有时候,签名还需要在列表里面显示,这样方便对一些关键信息进行查看核对。如下图所示

那么对于列表中显示图片,我们在DevExpress界面的分页控件中应该如何处理呢,这估计也是DevExpress开发中很多常见问题之一?

其实也很简单,就是在DataSourceChanged 变化的事件中改变单元格的对象属性即可。

                this.winGridViewPager1.OnRefresh += newEventHandler(winGridViewPager1_OnRefresh);this.winGridViewPager1.ShowLineNumber = true;this.winGridViewPager1.gridView1.Appearance.Row.TextOptions.HAlignment =DevExpress.Utils.HorzAlignment.Center;this.winGridViewPager1.gridView1.Appearance.HeaderPanel.TextOptions.HAlignment =DevExpress.Utils.HorzAlignment.Center;this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;this.winGridViewPager1.gridView1.DataSourceChanged += newEventHandler(gridView1_DataSourceChanged);this.winGridViewPager1.gridView1.RowHeight = 44;
        void gridView1_DataSourceChanged(objectsender, EventArgs e)
{
RepositoryItemPictureEdit pic1
= newRepositoryItemPictureEdit();
pic1.SizeMode
=DevExpress.XtraEditors.Controls.PictureSizeMode.Zoom;
pic1.NullText
= " ";
pic1.CustomHeight
= 44;
pic1.BestFitWidth
= 100;this.winGridViewPager1.gridView1.Columns["Signature"].ColumnEdit =picFlight;this.winGridViewPager1.gridView1.Columns["Signature"].MaxWidth = 100;this.winGridViewPager1.gridView1.Columns["Signature"].MinWidth = 100;

RepositoryItemPictureEdit picDoctor
= newRepositoryItemPictureEdit();
picDoctor.SizeMode
=DevExpress.XtraEditors.Controls.PictureSizeMode.Zoom;
picDoctor.NullText
= " ";
picDoctor.CustomHeight
= 44;
picDoctor.BestFitWidth
= 100;this.winGridViewPager1.gridView1.Columns["DoctorSignature"].ColumnEdit =picDoctor;this.winGridViewPager1.gridView1.Columns["DoctorSignature"].MaxWidth = 100;this.winGridViewPager1.gridView1.Columns["DoctorSignature"].MinWidth = 100;
}

以上就基本上解决了,签名,以及图片保存,以及列表显示图片效果的问题了。