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 实体框架后,带来的新功能。

时间: 2024-10-27 09:11:15

Rafy 领域实体框架示例(1) - 转换传统三层应用程序的相关文章

Rafy领域实体框架示例(1) 转换传统三层应用程序

Rafy 领域实体框架发布后,虽然有帮助文档,许多朋友还是反映学习起来比较复杂,希望能开发一个示例程序,展示如何使用 Rafy 领域实体框架所以,本文通过使用 Rafy 领域实体框架来改造一个传统的三层架构应用程序--"服装进销存"系统,来讲解如何使用 Rafy 领域实体框架进行数据库应用程序的快速开发,以及替换为使用 Rafy 框架后带来的一些新功能. 完整示例包下载地址:http://pan.baidu.com/s/1AB9TL,其中包含本次改造前.改造后的源代码,以及转换说明文档

Rafy 领域实体框架演示(4) - 使用本地文件型数据库 SQLCE 绿色部署

本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> <Rafy 领域实体框架示例(1) - 转换传统三层应用程序> <Rafy 领域实体框架演示(2) - 新功能展示> <Rafy 领域实体框架演示(3) - 快速使用 C/S 架构部署>   前言 支持一款与 Access 类似的文件型数据库,对于一些绿色安装的应用程序

Rafy领域实体框架演示(2) 新功能展示

本文的演示需要先完成上一篇文章中的演示:<Rafy 领域实体框架示例(1) - 转换传统三层应用程序>.在完成改造传统的三层系统之后,本文将讲解使用 Rafy 实体框架后带来的一些常用功能. 数据库自动生成 在程序转换转换完毕后.由于已经配置好数据库的连接字符串,所以直接运行整个程序,Rafy 会同时生成对应的数据库.表.字段,以及相应的外键关系等. 该库满足数据库的第三范式要求.同时,开发过程中新添加的属性,也会每次自动同步到这个数据库中. 以下是服装管理系统生成的数据库结构图: 监控执行的

Rafy 领域实体框架演示(3) - 快速使用 C/S 架构部署

本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> <Rafy 领域实体框架示例(1) - 转换传统三层应用程序> <Rafy 领域实体框架演示(2) - 新功能展示> 以 Rafy 开发的应用程序,其实体.仓库.服务代码不需要做任何修改,即可同时支持单机部署.C/S 分布式部署.本文将说明如果快速使用 C/S 分布式部署.   前

Rafy领域实体框架演示(3) 快速使用 C/S 架构部署

本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. 以 Rafy 开发的应用程序,其实体.仓库.服务代码不需要做任何修改,即可同时支持单机部署.C/S 分布式部署.本文将说明如果快速使用 C/S 分布式部署. 前言 截止到上一篇,我们开发的应用程序都是采用直接连接数据库的模式: 接下来,将通过一些简单的调整,使得这个应用程序支持以 C/S 架构部署.整个过程只需要少量的代码: 包含以下步骤: 添加服务端控制台应用程序项目 修改客户

Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成

前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要的部分,那里 Rafy 的核心是产品线工程.模型驱动开发.界面生成等.所以当时,我们简单地采用了一个开源的小型 ORM 框架:<Lite ORM Library>.这个 ORM 框架可以生成比较简单的 Sql 语句,以处理一般性的情况. 随着不断使用,我们也不断对 ORM 的源码做了不少改动,让它

Rafy领域实体框架演示(4) 使用本地文件型数据库SQLCE绿色部署

前言 支持一款与 Access 类似的文件型数据库,对于一些绿色安装的应用程序来说是非常必须的.使用 Rafy 领域实体框架开发的应用程序,可以在不变更一行代码的情况下,直接由大型数据库管理系统,移植到使用简单的 SqlCE 4 文件型数据库.(关于选择使用 SQLCE 4 作为文件型数据库的原因,详见:<OEA 2.11 支持单机版数据库 - SQLite与SQLCE对比>.) 本文说明如何快速配置 Rafy 应用程序,使得不需要修改任何代码的同时,让原本支持分布式部署.连接 SqlServ

Rafy 领域实体框架 - 树型实体功能(自关联表)

在 Rafy 领域实体框架中,对自关联的实体结构做了特殊的处理,下面对这一功能进行讲解.   场景 在开发数据库应用程序时,往往会遇到自关联表的场景.例如,分类信息.组织架构中的部门.文件夹信息等,都是不限制层级的.如下图中操作系统的文件夹: 在开发这类程序时,往往是设计一张表,表中的一个可空的外键直接引用这张表本身.对应的实体如下图: 而针对这样的场景,许多ORM框架都不做默认的处理,开发者往往每次都要做重复的工作:建立类似结构的表,编写关系处理代码,编写查询代码--而这种场景经常会出现,所以

Rafy 领域实体框架 - 公司内部培训视频

本月给公司内部一个项目做架构重构,其中使用到了 Rafy 框架.所以我培训了 Rafy 领域实体框架的使用方法,过程中录制了视频,方便其他同事查看.现在把视频放到园里来分享下,有兴趣的朋友可以看看,有什么问题可以进 Rafy QQ 群询问.   关于 Rafy 开源框架的详细介绍:<Rafy 领域实体框架 2.22.2067 发布!>. 如何下载并使用框架:<使用 NuGet 下载最新的 Rafy 框架及文档>.   本次培训一共有 7 个视频: 1.老系统架构讲解.新系统架构思想