Rafy 领域实体框架示例(1) - 转换传统三层应用程序
Rafy 领域实体框架发布后,虽然有帮助文档,许多朋友还是反映学习起来比较复杂,希望能开发一个示例程序,展示如何使用 Rafy 领域实体框架所以,本文通过使用 Rafy 领域实体框架来改造一个传统的三层架构应用程序——“服装进销存”系统,来讲解如何使用 Rafy 领域实体框架进行数据库应用程序的快速开发,以及替换为使用 Rafy 框架后带来的一些新功能。
完整示例包下载地址:http://pan.baidu.com/s/1AB9TL,其中包含本次改造前、改造后的源代码,以及转换说明文档。(下载该示例代码后,只需要修改 app.config 文件中的连接字符串中的用户名和密码后,就可以直接运行示例,程序即会自动创建数据库并成功运行!还没有下载 Rafy 框架的同学,可以在《Rafy 框架发布》文中下载完整安装包。)
接下来,将说明如何进行代码转换,使用 Rafy 来开发一个典型的数据库应用程序。(以下内容拷贝自示例包中的 PDF 文档。)
原程序说明
考虑到要更好地演示如何使用 Rafy 框架来开发一个传统的管理系统,决定挑选一个开源系统进行改造,而这个系统应该是简单、常见的三层架构,这种系统大家都比较熟悉,这样就可以更加快速的理解框架的使用了。
在开源网站上挑选了很久,免费的三层架构系统挺多,但是许多系统并不规范。一些系统虽然写着使用三层架构,但是金玉其外,败絮其中,看上去非常正式的系统,一打开源码,界面层代码中就可以看到直接编写的 SQL 语句。最终,我选用了《知名度服装进销存管理系统》,源代码下载地址:http://www.51aspx.com/Code/ZhiMingDuClothesSys。该系统三层间的调用比较严格,业务也非常简单。
系统功能描述:
- 人员:操作员管理,供应商管理,顾客管理
- 库存:库存管理,库存盘点
- 销售:服装销售,服装退货
- 服装:服装类别,服装登记
- 销售:销售统计,利润统计
技术特点:使用了三层架构设计程序,更换底层数据库类型方便。系统使用了 SqlLite 作为数据库,下载后可以直接运行。
界面截图 :
程序转换
转换方案
原系统是简单的三层架构:
而我们会使用 Rafy 推荐的架构,来改造整个系统:
对于一个依赖关系较为严格的三层系统来说,要使用 Rafy 框架来替换其中的数据访问层、业务逻辑层以及界面查询的功能,是比较简单的。本次转换,我按照以下步骤进行:
1. 理解系统需求,使用 UML 画出领域实体间的关系。
2. 添加 Rafy 领域实体项目。
3. 根据实体的关系图,在实体程序集中添加对应的实体及实体间的关系;同时也可以把旧表中的属性添加到实体中。
4. 把所有跨多表的业务逻辑转换为领域服务。
5. 依次把历史的实体删除,转而使用新的 Rafy 实体,以及其对应的实体查询、领域服务。
接下来,就正式对代码进行转换:
1. 使用 UML 进行领域建模
经分析,原系统拥有以下领域模型:
User:用户;
Company:供应商;
Customer:顾客;
GoodCategory:商品类别;
Good:商品(服装);
Stock:入库信息;
Regood:返库信息;
Bill 及 Sell:销售单据及销售明细。
它们的关系如下:
(虽然原系统中一些实体的名称取得并不合理,但是为了简化系统的转换工作,新系统中的类命名还是保持和原系统一致。)
关于哪些关系应该使用组合关系来进行设计,大家可以查看 Rafy 用户向导文档中的“领域实体框架/领域实体/实体关系”章节。
2. 升级 .NET 版本
在开始转换代码前,由于原程序使用的是 .NET 2.0 的运行时,而 Rafy 要求必须使用 .NET 4.0。所以我们需要把解决方案中的每个项目都转换为 .NET 4.0 版本。
需要注意的是,由于原程序使用的 SqlLite 只支持 2.0 版本。同时,需要把 SqlLite 替换为 .NET 4.0 的版本。
3. 添加 Rafy 领域实体项目
在解决方案中添加一个 Rafy 领域实体项目,命名为 CS(为原系统名 ClothesSys 的缩写)。
点击确定后生成的项目如下:
接下来,我们将会在这项目中添加领域实体与领域服务,来替换原程序中除界面项目以外的其它几个项目:
4. 实体转换
接下来,依次把历史的实体删除,转而使用新的 Rafy 实体。这一步,需要按照依赖关系,尽量先转换不依赖其他实体的实体,即按照以下顺序进行转换:User、Company、Customer、GoodCategory、Good、Stock、Regood、Bill 和 Sell。由于 Bill 和 Sell 有强聚合关系,所以放到最后一起转换。
(在变更每一个实体时,原代码中所有的 BLL 查询,都需要在实体仓库中编写相关的代码支持;业务逻辑则需要编写领域服务)
实体的转换分为以下几类:
- 无关系实体的转换
- 有关系实体的转换
- 组合实体的转换
5. 简单实体的转换
简单实体没有复杂的关系,只是映射一个简单的表。在转换为 Rafy 实体时,只需要把表中的所有属性都添加到实体中就可以了。在编写时,需要注意的是:
标识
转换为 Rafy 实体后,所有的实体都统一继承自 Entity 类型。Entity 类声明了 int 类型的 Id 属性作为所有实体的标识属性,这个属性会在数据库中生成一个自增长的主键列。
旧实体类上的所有主键列、唯一列,在新实体中都变成了普通列。实体属性的唯一性验证,需要放到实体之上的业务逻辑层中来完成。
属性
原实体的所有属性,在 Rafy 实体中都使用属性代码段来生成同名的实体属性代码即可。
6. BLL、DAL 层代码转换
- 转换查询数据的代码
在原代码中 BLL、DAL 两层中,都有许多的查询方法。这些方法都需要转换为新代码中对应实体的实体仓库中的查询方法。例如,原程序中通过顾客编号查询顾客的查询方法:
1: public static Customer GetCustomerById(string id)
2: {
3: Customer ct = null;
4: SQLiteParameter[] sqlparams = new SQLiteParameter[]{
5: new SQLiteParameter("@customerId",id)
6: };
7: SQLiteDataReader sdr = SQLiteHelper.GetReader("select * from T_customer where customerid=@customerId", sqlparams);
8: if (sdr.Read())
9: {
10: ct = new Customer();
11: ct.CustomerID = sdr.GetValue(0).ToString();
12: ct.CustomerName = sdr.GetValue(1).ToString();
13: ct.Socre = Convert.ToInt32(sdr.GetValue(2));
14: ct.Remark = sdr.GetValue(3).ToString();
15: }
16: sdr.Close();
17:
18: return ct;
19: }
需要转换为 Rafy 实体仓库中的新方法:
1: public Customer GetByCustomerId(string id)
2: {
3: return this.FetchFirst(new PropertiesMatchCriteria
4: {
5: { Customer.CustomerIDProperty, id },
6: });
7: }
- 转换业务逻辑代码
BLL、DAL 中,除了查询方法以外,剩下的还有一些简单对实体的增、删、改操作。这些操作已经在实体仓库基类中实现了,所以可以不用转换。
除了简单的 CRUD 操作外,系统中还有一些需要同时操作多个表的事务操作,原系统把这些业务逻辑都写到了数据层中。例如 ReGoodService.ReGoodSumbit 方法:
1: public static bool ReGoodSumbit(ReGoods regoods)
2: {
3: SQLiteParameter[] sqlparams = new SQLiteParameter[]{
4: new SQLiteParameter("@regoodsId",regoods.ReGoodsID),
5: new SQLiteParameter("@regoodsNum",regoods.ReGoodsNum),
6: new SQLiteParameter("@regoodsPrice",regoods.ReGoodsPrice),
7: new SQLiteParameter("@reNeedPay",regoods.ReNeedPay),
8: new SQLiteParameter("@reRealpay",regoods.ReRealPay),
9: new SQLiteParameter("@regoodsResult",regoods.ReGoodResult),
10: new SQLiteParameter("@userId",regoods.UserId),
11: new SQLiteParameter("@sellId",regoods.SellId),
12: new SQLiteParameter("@regoodsTime",regoods.RegoodsTime.ToString("yyyy-MM-dd HH:mm:ss"))
13: };
14:
15: string sql = "insert into T_regoods(regoodsid,regoodsNum,regoodsPrice,reNeedPay,reRealPay,regoodsResult,userId,regoodsTime,sellId) ";
16: sql+=" values(@regoodsid,@regoodsNum,@regoodsPrice,@reNeedPay,@reRealPay,@regoodsResult,@userId,@regoodsTime,@sellId)";
17: try
18: {
19: SQLiteConnection con = SQLiteHelper.GetConnection();
20: SQLiteTransaction trans = con.BeginTransaction();
21:
22: SQLiteHelper.ExecuteNonQuery(trans, sql, sqlparams);
23:
24: CustomerService.DecreaseCustomerScore(trans, regoods);
25: StockService.IncreaseStocskNumByGoodId(regoods.ReGoodsID, regoods.ReGoodsNum);
26:
27: trans.Commit();
28: return true;
29: }
30: catch (Exception ex)
31: {
32: return false;
33: throw ex;
34: }
35: }
可以看到,这段代码中,不但有业务逻辑的控制,还有数据库连接的控制,事务的控制,Sql 语句的拼装。显得非常混乱。而这种业务逻辑,在 Rafy 框架中,可以使用领域服务来实现。例如,刚才的逻辑,被替换为以下代码:
1: [Serializable]
2: public class SubmitRegoodService : Service
3: {
4: public Regood Regood { get; set; }
5:
6: protected override Result ExecuteTransaction()
7: {
8: if (Regood == null) throw new ArgumentNullException("Regood");
9: if (!Regood.IsNew) throw new ArgumentNullException("Regood");
10:
11: RF.Save(Regood);
12:
13: //修改库存
14: Regood.Good.StockSum += Regood.ReGoodsNum;
15: RF.Save(Regood.Good);
16:
17: //减少客户的积分
18: var ct = Regood.Sell.Customer;
19: if (ct != null)
20: {
21: ct.Score -= Regood.ReRealPay;
22: RF.Save(ct);
23: }
24:
25: return true;
26: }
27: }
可以看到,使用 Rafy 领域服务来实现后有以下好处:
- 整个代码非常直接地表现了业务逻辑,没有一点多余的代码。
- 使用了引用实体属性的懒加载功能,使得程序可以直接使用如 Regood.Sell.Customer 这样的强引用关系。
- 方便通用代码的封装。例如,事务的控制已经交给了服务基类来处理。
- 业务逻辑独立封装。每一个单独的业务都是一个服务对象,方便管理。为 SOA 提供了架构基础。
- 同时,使用领域服务还可以方便地直接使用 C/S 架构来部署。
7. 外键关系的转换
旧表中的外键引用关系,除了 Bill(销售单) 与 Sell(销售明细) 两个表间的关系,在设计 UML 时,都设计为实体间的引用关系。先区分清楚引用关系的可空性,然后就可以在相应实体中编写引用实体属性了。例如,Stock(库存)到 Good(商品)的关系,被转换为下面这个引用实体属性:
1: public static readonly RefIdProperty GoodIdProperty =
2: P<Stock>.RegisterRefId(e => e.GoodId, ReferenceType.Normal);
3: public int GoodId
4: {
5: get { return this.GetRefId(GoodIdProperty); }
6: set { this.SetRefId(GoodIdProperty, value); }
7: }
8: public static readonly RefEntityProperty<Good> GoodProperty =
9: P<Stock>.RegisterRef(e => e.Good, GoodIdProperty);
10: public Good Good
11: {
12: get { return this.GetRefEntity(GoodProperty); }
13: set { this.SetRefEntity(GoodProperty, value); }
14: }
8. 使用组合实体
Bill 和 Sell 分别表示销售订单、销售明细项。设计为组合实体后,在使用时,可以直接以组合实体的方式构造、保存、更新、删除,非常方便。
例如,在添加销售信息界面中的代码如下:
1: var bill = new Bill();
2: bill.UserId = userId;
3: bill.BillTime = now;
4:
5: foreach (Sell sell in list)
6: {
7: sell.CustomerId = customerId;
8: sell.SellTime = now;
9:
10: bill.SellList.Add(sell);
11: }
12:
13: var svc = new AddBillService { Bill = bill };
14: svc.Invoke();
15: if (svc.Result)
16: {
17: MessageBox.Show("提交订单成功!", "提示");
18: this.Close();
19: ucGSM.bindDgvSellRecordToday();
20: }
先构造了组合对象,然后提交给领域服务 AddBillService以执行添加销售信息逻辑。此服务代码如下:
1: [Serializable]
2: public class AddBillService : Service
3: {
4: public Bill Bill { get; set; }
5:
6: protected override Result ExecuteTransaction()
7: {
8: if (Bill == null) throw new ArgumentNullException("Bill");
9: if (!Bill.IsNew) throw new ArgumentException("Bill");
10:
11: //调用仓库保存整个销售单
12: var repo = RF.Concrete<BillRepository>();
13: repo.Save(Bill);
14:
15: //修改库存
16: foreach (var sell in Bill.SellList)
17: {
18: sell.Good.StockSum -= sell.SellNum;
19: RF.Save(sell.Good);
20: }
21:
22: //添加客户的积分
23: var ctRepo = RF.Concrete<CustomerRepository>();
24: foreach (var sell in Bill.SellList)
25: {
26: var ct = sell.Customer;
27: if (ct != null)
28: {
29: ct.Score += sell.RealPay;
30: ctRepo.Save(ct);
31: }
32: }
33:
34: return true;
35: }
36: }
转换后实体项目结构
待每一个实体修改并替换完毕后,再删除原来的传统三层项目后,解决方案中就只剩下了两个项目,一个 Rafy 领域实体项目“CS”;一个原程序中的界面层项目 “ClothesSys”。
截止到现在,已经完成了 ClothesSys 的完整转换。转换后的系统已经可以正常的运行,实现了与原系统一致的功能。
下载该示例代码后,只需要修改 app.config 文件中的连接字符串中的用户名和密码后,就可以直接运行示例,程序即会自动创建数据库并成功运行!
下一篇,将展示转换为使用 Rafy 实体框架后,带来的新功能。
Rafy 领域实体框架示例(1) - 转换传统三层应用程序的更多相关文章
- Rafy 领域实体框架演示(4) - 使用本地文件型数据库 SQLCE 绿色部署
本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> ...
- Rafy 领域实体框架演示(3) - 快速使用 C/S 架构部署
本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> ...
- Rafy 领域实体框架演示(2) - 新功能展示
本文的演示需要先完成上一篇文章中的演示:<Rafy 领域实体框架示例(1) - 转换传统三层应用程序>.在完成改造传统的三层系统之后,本文将讲解使用 Rafy 实体框架后带来的一些常用功能 ...
- Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成
前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要 ...
- Rafy 领域实体框架简介
按照最新的功能,更新了最新版的<Rafy 领域实体框架的介绍>,内容如下: 本文包含以下章节: 简介 特点 优势 简介 Rafy 领域实体框架是一个轻量级 ORM 框架. 与一般的 ORM ...
- Rafy 领域实体框架 - 公司内部培训视频
本月给公司内部一个项目做架构重构,其中使用到了 Rafy 框架.所以我培训了 Rafy 领域实体框架的使用方法,过程中录制了视频,方便其他同事查看.现在把视频放到园里来分享下,有兴趣的朋友可以看看,有 ...
- Rafy 领域实体框架 - 树型实体功能(自关联表)
在 Rafy 领域实体框架中,对自关联的实体结构做了特殊的处理,下面对这一功能进行讲解. 场景 在开发数据库应用程序时,往往会遇到自关联表的场景.例如,分类信息.组织架构中的部门.文件夹信息等,都 ...
- Rafy 领域实体框架 - 领域模型设计器(建模工具)设计方案
去年4月,我们为 Rafy 框架添加了领域模型设计器组件.时隔一年,谨以本文,简要说明该领域模型设计器的设计思想. 设计目标 Rafy 实体框架中以领域驱动设计作为指导思想.所以在开发时,以领域建模为 ...
- 福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!
距离“上次框架完整发布”已经过去了一年半了,应群中的朋友要求,决定在国庆放假之际,把最新的框架发布出来,并把帮助文档整理出来,这样可以方便大家快速上手. 发布内容 注意,本次发布,只包含 Rafy ...
随机推荐
- 【腾讯Bugly干货分享】Android ImageView 正确使用姿势
本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5832602d7196970d65901d76 导语 本文主要介绍了ImageV ...
- SOA相关资料整理分享
昨@幸福框架同学问能否推荐SOA一些资,.想想之前看过不少资料文档,就整理分享下,有需要的可以参考下. 文章链接 理解面向服务的体系结构中企业服务总线场景和解决方案,第 1 部分 SOA 和 web ...
- Linux sudo
200 ? "200px" : this.width)!important;} --> 介绍 本篇文章主要介绍sudo配置和用法,为了给某个用户控制权限比如执行某个命令或者关 ...
- Qt And MFC Mouse Over Tips
Qt鼠标提示分析说明 关于鼠标停留在控件上面,显示提示内容的方法. 对于Qt来说, Qt的某一个控件类, 如果属于GUI的, 那么这个控件类会有一个setToolTip(QString text)的方 ...
- Android开发学习之路-Android N新特性-多窗口模式
我们都知道,在最新的Android N系统中,加入了一个新的功能,就是多窗口模式.多窗口模式允许我们在屏幕上显示两个窗口,每个窗口显示的内容不同,也就是说,我们可以一遍看电视剧,一边聊微信. 这里我们 ...
- Ajax_03之接收数据
1.使用XHR接收服务器返回的数据--text 服务器端: header('Content-Type:text/plain'); echo 'xxx'; 客户端: xhr.responseTex ...
- WebView 与PC机Chrome配合调试
参考自http://www.cnblogs.com/terrylin/p/4606277.html 移动端WebView开发调试:Chrome远程调试 Chrome DevTools调试移动设备Bro ...
- Android中Path类的lineTo方法和quadTo方法画线的区别
转载:http://blog.csdn.net/stevenhu_223/article/details/9229337 当我们需要在屏幕上形成画线时,Path类的应用是必不可少的,而Path类的li ...
- 访问外网 ML2 的配置 - 每天5分钟玩转 OpenStack(103)
通过 router 可以实现位于不同 vlan 中的 instance 之间的通信. 接下来要探讨的问题是 instance 如何与外部网络通信. 这里的外部网络是指的租户网络以外的网络. 租户网络是 ...
- Cmd Markdown编辑器简明语法手册
标签: Cmd-Markdown 1. 斜体和粗体 使用 * 和 ** 表示斜体和粗体. 示例: 这是 斜体,这是 粗体. 2. 分级标题 使用 === 表示一级标题,使用 --- 表示二级标题. 示 ...