热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

不使用反射,“一行代码”实现Web、WinForm窗体表单数据的填充、收集、清除,和到数据库的CRUD

问题篇:昨天在CSDN看到这样一个帖子:“苦逼的三层代码”:采用传统的三层架构写代码,每个数据表都要定义一个实体对象,编写后台的时候,Web层需要针对页面的用户输入逐个手动编写赋值到实
问题篇:

    昨天在CSDN看到这样一个帖子:“苦逼的三层代码”:

采用传统的三层架构写代码,每个数据表都要定义一个实体对象,编写后台的时候,
Web层需要针对页面的用户输入逐个手动编写赋值到实体对象的各个属性,然后DAL层还要用SqlHelper 进行各个存储过程对应参数的实体赋值,
我的天呀,写几个表还好,多个表呢,
写的后台都没力气,
典型的苦逼代码工没营养,各位有啥好的处理方法或开发方式。。

 

    看到跟帖,大部分都说使用ORM解决这个问题,但我觉得ORM还是没有解决贴主的几个问题:

  1. 每个数据表都要定义一个实体对象
  2. 页面的用户输入逐个手动编写赋值到实体对象的各个属性
  3. 表很多,代码重复量大,典型的苦逼代码工

    另外跟帖中也有不少上用动软的三层代码生成器,这个方法看似能够解决一部分问题,但必须使用代码生成器规定的那种三层结构,不利于灵活扩展,而且遇到业务稍复杂的情况,也不是代码生成器能够解决的问题。

    实际上,对于问题1,问题2,我们按照一定规则,使用反射是可以解决对象属性手工逐个赋值、取值的过程的,需要我们自己好好制定这个规则。这里我采用另外一种方案,不使用反射,“一行代码”实现Web、WinForm窗体表单数据的填充、收集、清除,和到数据库的CRUD,而秘诀就是对表单控件进行扩展。

原理篇:


    我们常用的表单控件主要有以下几个:

  • CheckBox、
  • DropDownList、
  • Label、
  • ListBox、
  • RadioButton、
  • TextBox

    我们对这些控件进行扩展,让它统一继承一个数据接口 IDataControl:

IDataControl接口
        /// 
/// 数据映射控件接口
///

public interface IDataControl
{

///
/// 与数据库数据项相关联的数据
///

string LinkProperty
{
get;
set;
}

///
/// 与数据关联的表名
///

string LinkObject
{
get;
set;
}

///
/// 是否通过服务器验证默认为true
///

bool IsValid
{
get;
}



///
/// 数据类型
///

TypeCode SysTypeCode
{
get;
set;
}

///
/// 只读标记
///

bool ReadOnly
{
get;
set;
}


///
/// 是否允许空值
///

bool isNull
{
get;
}

///
/// 是否是主键
///

bool PrimaryKey
{
get;
set;
}



///
/// 设置值
///

///
void SetValue(object value);

///
/// 获取值
///

///
object GetValue();

///
/// 服务端验证
///

///
bool Validate();

}

    稍后我们来讲这个接口的具体应用,下面,我们定义几个新的数据控件,来继承这个接口:
    注:下面以WinForm控件为例子,WebForm与之类似。

public partial class DataCalendar : DateTimePicker, IDataControl
{
//数据日历控件
}

public partial class DataCheckBox : CheckBox, IDataControl
{
//数据复选框控件
}

public partial class DataDropDownList : ComboBox, IDataControl
{
//数据下拉选择框控件
}

public class DataLabel : Label, IDataControl
{
//数据标签控件
}

public partial class DataListBox : ListBox, IDataControl
{
//数据列表框控件
}

public partial class DataRadioButton : RadioButton, IDataControl
{
//数据选项按钮控件
}

public class DataTextBox : TextBox, IDataTextBox
{
//数据文本框控件
}

    有了这些扩展的表单控件,我们只需要调用它的接口方法,进行赋值和取值:

DataTextBox dtb=new DataTextBox();
dtb.SetValue(
"text1");
string value=dtb.GetValue().ToString();//text1

    而在DataTextBox的这两个接口方法实现中,是不需要使用反射的:

       public void SetValue(object obj)
{
if (obj == null || obj.ToString() == "")
{
this.Text = "";
return;
}
//其它检测和格式控制代码略
this.Text = obj.ToString().Trim();
}

public object GetValue()
{
//其它检测和格式控制代码略
return this.Text.Trim();
}

    有了数据控件的这2个接口方法,我们对各种数据控件进行统一的数据收集、填充就很容易了,无非就是遍历一下窗体上面的数据控件,找到它们然后一个个处理即可,具体代码后面的实例会说到。


    既然说到表单数据的填充,将查询出来的数据集中哪个表的某个字段和哪个控件对应呢?
    这就用到了IDataControl接口的下面2个属性了:

string LinkProperty{get;set;}//对应字段名或者实体类的属性名
string LinkObject{get;set;}//对应表名或者实体类的类名称

    OK,有了IDataControl接口的这几个接口方法和属性,不使用反射,封装一下,“一行代码”实现Web、WinForm窗体表单数据的填充、收集、清除,和到数据库的CRUD,也就不是难事了。

实战篇:


    按照这个方法,我在PDF.NET开发框架中实现了本文标题说的功能,最近还做了一个简单的例子,大家可以去开源项目网站下载:
    项目网址: http://pwmis.codeplex.com 到下载页,选择“ PDF.Net_V4.6 WinForm 数据表单实例 ”这个下载链接即可。

    下面说说这个小程序的搭建过程,

1,新建项目

    首先创建一个WinForm程序项目,引入下面几个DLL类库:

 

2,添加数据控件到工具箱

    因为是WinForm项目,所以我们引用了PWMIS.Windows.dll, 它包含了我们需要的数据控件。
    找到该文件,将它拖入我们的工具箱:

    添加前,在工具箱中增加一个项:PDF.NET DataForm,然后在资源管理器中选择Windows数据控件组件的文件,将它“拖放”到刚才建立的 PDF.NET DataForm下面

 

    这是拖放后,添加PDF.NET Windows 数据控件成功后的工具箱样子。

 

3,添加数据窗体

    我们在主窗体上放置几个按钮和一个网格控件,以便增、删、改、查询数据:

    然后我们再新建立一个窗体 Form2 ,在上面放置几个我们需要的表单控件并设置好我们需要保存的表名称和对应的字段名称:

 

4,编写代码

    4.1,基础CRUD代码

    窗体建立好了,现在开始写代码,刚开始还没有数据库呢,这里我们是有Access数据库文件,方便我们测试,在“创建数据库”按钮事件里面写如下代码:

private void btnCreateDB_Click(object sender, EventArgs e)
{
string dbpath = Application.StartupPath + "\\TEST.mdb";
if (!File.Exists(dbpath))
{
//创建数据库文件
PWMIS.AccessExtensions.AccessUility.CreateDataBase(dbpath);
//创建表
Access access = new Access();
access.ConnectionString
= "Provider=Microsoft.Jet.Oledb.4.0;Data Source=" + dbpath;

PWMIS.AccessExtensions.AccessUility.CreateTable(access,
new User());
//配置连接
PWMIS.AccessExtensions.AccessUility.ConfigConnectionSettings("AccessConn", dbpath);

MessageBox.Show(
"创建数据成功!");
this.btnInsert.Enabled = true;
this.btnUpdate.Enabled = true;
this.btnDelete.Enabled = true;
}
else
{
MessageBox.Show(
"数据库已经创建过了,如需重新创建,请先删除数据库文件。");
}

}

    注意,我们并没有手工去创建数据表,而是利用事先定义好的PDF.NET实体类 User,在Access数据库中自动创建了一个数据表的:

 PWMIS.AccessExtensions.AccessUility.CreateTable(access, new User());

    User实体类的定义很简单,它内部指明了实体类将要映射到的表名和实体类属性映射的字段名:

User实体类定义
public class User:EntityBase
{
public User()
{
this.TableName = "会员用户表";
this.IdentityName = "标识";
this.PrimaryKeys.Add("标识");
}

protected override void SetFieldNames()
{
PropertyNames
= new string[] {"标识","用户名","用户类型","注册时间","消费金额" };
}

public int UserID
{
get { return getProperty<int>("标识"); }
set { setProperty("标识", value); }
}

public string UserName
{
get { return getProperty<string>("用户名"); }
set { setProperty("用户名", value, 50); }
}

public int UserType
{
get { return getProperty<int>("用户类型"); }
set { setProperty("用户类型", value); }
}


public DateTime RegisterDate
{
get { return getProperty("注册时间"); }
set { setProperty("注册时间", value); }
}

public Single Expenditure
{
get { return getProperty("消费金额"); }
set { setProperty("消费金额", value); }
}
}

   实体类是事先手写好的,表结构是后来程序运行时创建的,这也算是PDF.NET的CodeFirst 功能吧!

    下面,写主窗体的数据加载代码:

 List list = OQL.From().Select().END.ToList();
this.dataGridView1.DataSource =list;

    这里用上了PDF.NET框架的OQL扩展,一行代码查询数据,需要项目引用PWMIS.Core.Extensions.dll 以及
    using PWMIS.Core.Extensions;

 

    修改数据也是一行代码:

 User user = this.dataGridView1.CurrentRow.DataBoundItem as User;
EntityQuery
.Instance.Update(user);

 

    重头戏在我们的Form2.cs 中,我们看看提交按钮里面,是怎么收集、更新表单数据的:

 private void btnSubmit_Click(object sender, EventArgs e)
{
//前面检查数据的代码略
var ibCommandList = MyWinForm.Instance.AutoUpdateIBFormData(this.Controls);
}

    就这一行代码就足够了,不需要使用任何实体类之类的,直接保存(Insert、Update)数据到数据库,框架会自动判断当前是新增还是修改,而根据就是看“主键数据控件”是否有值。


    如果要清除表单数据,重新录入数据也很简单:

  private void btnClear_Click(object sender, EventArgs e)
{
WinFormControlDataMap.ClearData(
this.Controls);
}

 

    4.2,多窗体之间的数据同步   

    在我们这个小例子中,表单窗体(Form2)的数据变化后(新增、修改),可以立即反应到主窗体(Form1)上,而不用主窗体去重新加载数据,这里就必须用到数据绑定集合:

 private BindingList UserBindingList = new BindingList();
//填充集合的代码,就是将数据从数据库查询出来,然后放到该集合中,代码略
this.dataGridView1.DataSource = UserBindingList;

    光有BindingList 集合还不够,它的成员对象还必须实现“属性更改通知”接口INotifyPropertyChanged,而PDF.NET的实体类正好实现了该接口:

public abstract class EntityBase : INotifyPropertyChanged, IEntity, ICloneable
{
//... 略
}

    因此用PDF.NET的实体类来做WinForm、WPF、SL等窗体的数据Model是很合适的,适合在MVVM,MVP模式的项目中使用。

   

    下面,使用框架提供的表单数据收集功能,就很容易的将数据收集到实体类,然后同步更新主窗体的列表数据了,也是一行代码:

 Form1 form1 = this.Owner as Form1;
User user
= form1.GetUserByID(int.Parse(dlbUID.Text));
//收集数据到实体类中
WinFormControlDataMap.CollectDataToEntityClass(user, this.Controls);

 

5,实例效果

最后,我们来看看这个功能的运行效果图:

增加数据,在新窗体中录入数据

 

单击按钮保存数据,主窗体列表中自动增加一行数据

 

新窗口先不关闭,修改下消费金额,确定,发现主窗口列表的数据被同步修改了。

整个过程没有从数据库去重新刷新数据到主窗口网格控件的,实现了多个窗体之见的数据同步。

 

 

 -----------分界线------------------------

 欢迎加入PDF.NET开发框架 开源技术团队

PDF.NET Ver4.6 开源稳定版发布

 


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
author-avatar
朱志铭贤雯
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有