管理软件的实体设计
1 管理软件的实体设计
本文介绍管理软件研发进行实体设计时候一般考虑的问题,然后介绍笔者的一个作品中的实体设计。
1.1 实体的主要职责
序号 |
职责 |
说明 |
附加说明 |
1 |
领域模型 |
实体本身是一个业务对象,是对解决的业务场景(问题领域)进行的抽象建模,其属性和关系具体明确地表达了业务相关的静态概念。 |
这个功能开发每天都在做,不在赘述 |
2 |
持久化 |
实体是软件运行时内存中的瞬时状态,实体一般需要持久化到数据库中,实体本身拥有持久化时与数据库的映射信息,为数据库的永久存储和从数据库中查询生成内存的实体提供描述性质的映射信息。 |
即持久层(有时也叫ORM工具)的角度关心的静态结构. 数据库关心的是表、字段、表间关系(主外键)。实体关系的是类、属性、类关系(继承、关联、依赖,其中继承细分为继承和泛化,关联又细分为引用、组合、聚合) |
3 |
序列化 |
C#中对象的传递(比如方法的对象型参数)在同一进程内,通过对象的地址进行传递。而跨进程的数据传递(或者是通过网络跨机器的数据传递),数据需要序列化,即发送方把内存中的实体序列化成固定的格式(如Json和XML还有的是二进制等),接收方根据此格式再反序列化成内存对象。 |
序列化一般发生在分布式体系中,甚嚣尘上的SOA则必须解决跨互联网的数据传输。
有的开发者把进行数据传输的实体有一个独立的概念叫DTO,很多人也把DTO和业务实体从代码和设计上完全独立开。还有JAVA下的POJO (贫血、充血、高血压、脑血栓[后两个瞎说的])也都是说此内容。
序列化关系的主要问题有两个,一是标准化,二是性能,很多时候也需要考虑安全性 |
4 |
界面绑定 |
管理软件中界面显示的数据一般是和实体字段有比较严格的对应关系,所以设计上一般提供一种框架来处理这种映射,即把实体数据显示到界面上,同时界面数据也能生成一个实体对象。 |
开发者说的MVP、MVVP、MVVVVVVVVPPPPPPP(瞎编的)等模式就是解决此类场景的问题。 |
5 |
触发器 |
触发器解决的问题是实体的属性改变之后,其他的属性跟着改变。比如单价修改了之后,自动计算金额。 触发器首先一般处理的是实体自身的小逻辑(大的逻辑如流程类的功能,通过业务处理类来处理);触发器的逻辑一般依附在实体上,对于服务和客户端都是用C#的情况下,代码可以服用;触发器中被动改变的属性要自动体现到界面上,这是配合前文的绑定功能实现的。 |
触发器、条件控制、验证在Netsharp中称之为实体的三驾马车 |
6 |
状态控制 |
状态控制也称之为条件控制,是界面层的框架,就是解决在某种状态下,界面元素的状态。状态有三种情况,正常显示、置灰、隐藏。 |
状态控制的广义概念上会处理界面的样式,包括颜色、字体、位置等等。本文的状态控制只处理三种情况,这是管理软件的最核心的状态控制。 |
7 |
业务验证 |
业务验证就是输入时候的业务约束,也是界面层的框架,比如必输、邮件格式、手机号码或者身份证号码格式等,管理软件中更多的是面向业务的验证。 |
|
|
|
|
|
1.2 常见的实体设计
上文介绍了实体设计主要考虑的维度,一般的技术文章会从单一的角度来进行考虑,这些维度一般是界面绑定、分布式序列化、持久化组件. 本节分析几种典型的实体设计.
序号 |
特点 |
特点 |
1 |
csla |
|
2 |
hibernate |
|
3 |
微软dataset |
|
4 |
activerecord |
|
5 |
wpf |
|
6 |
dto |
|
1.3 Netsharp实体设计
先看一段实体的声明代码,下面代码的实体是DatagridProject,他是一个列表页面的实体元数据类。
- 支持继承关系,他的父类是UiDescription
- 支持ORM,映射,比如有TableMapAttribute特性,以及关联对象设置特性ReferenceObjectAttribute
1 /// <summary> 2 /// 列表项目 3 /// </summary> 4 [Netsharp.Core.TableMapAttribute("System_UI_DatagridProject", "Id", "", "列表项目", "Icons.16x16.Report.List.png")] 5 public partial class DatagridProject : UiDescription 6 { 7 8 protected bool isPublic; 9 10 protected System.Guid idDatagridSolution; 11 12 protected Netsharp.Core.DatagridSolution datagridSolution; 13 14 protected Subs<Netsharp.Core.DatagridColumn> datagridColumns; 15 16 /// <summary> 17 /// 公有 18 /// </summary> 19 public bool IsPublic 20 { 21 get 22 { 23 return this.isPublic; 24 } 25 set 26 { 27 object oldValue; 28 oldValue = this.isPublic; 29 if (this.OnPropertyChanging("IsPublic", oldValue, value)) 30 { 31 this.isPublic = value; 32 this.ProtectedPropertyChanged("IsPublic", oldValue, value); 33 } 34 } 35 } 36 37 /// <summary> 38 /// DatagridSolution 39 /// </summary> 40 public System.Guid IdDatagridSolution 41 { 42 get 43 { 44 return this.idDatagridSolution; 45 } 46 set 47 { 48 object oldValue; 49 oldValue = this.idDatagridSolution; 50 if (this.OnPropertyChanging("IdDatagridSolution", oldValue, value)) 51 { 52 this.idDatagridSolution = value; 53 this.ProtectedPropertyChanged("IdDatagridSolution", oldValue, value); 54 } 55 } 56 } 57 58 /// <summary> 59 /// DatagridSolution 60 /// </summary> 61 [Netsharp.Core.ReferenceObjectAttribute(typeof(Netsharp.Core.DatagridSolution), "Id", "IdDatagridSolution", "列表方案")] 62 public Netsharp.Core.DatagridSolution DatagridSolution 63 { 64 get 65 { 66 if ((this.datagridSolution == null)) 67 { 68 this.datagridSolution = ((Netsharp.Core.DatagridSolution)(this.Reference("DatagridSolution"))); 69 } 70 return this.datagridSolution; 71 } 72 set 73 { 74 this.Reference("DatagridSolution", value); 75 } 76 } 77 78 /// <summary> 79 /// DatagridColumns 80 /// </summary> 81 [Netsharp.Core.SubObjectAttribute(typeof(Subs<Netsharp.Core.DatagridColumn>), "Id", "IdDatagridProject", "DatagridProject", "DatagridProject", "网格列")] 82 public Subs<Netsharp.Core.DatagridColumn> DatagridColumns 83 { 84 get 85 { 86 if ((this.datagridColumns == null)) 87 { 88 this.datagridColumns = ((Subs<Netsharp.Core.DatagridColumn>)(this.Subs("DatagridColumns"))); 89 } 90 return this.datagridColumns; 91 } 92 set 93 { 94 object oldValue; 95 oldValue = this.datagridColumns; 96 if (this.OnPropertyChanging("DatagridColumns", oldValue, value)) 97 { 98 this.datagridColumns = value; 99 } 100 } 101 } 102 }
1.3.1 实体字段类型
下面是产品支持的数据类型列表:
Name | Csharp | SqlServer | MySql | Oracle | DefaultValue | SizeDb | PrecisionDb | SizeDisplay | PrecisionDisplay |
整数(32) | System.Int32 | int | MEDIUMINT | NULL | NULL | 0 | 0 | NULL | NULL |
短整数(16) | System.Int16 | SMALLINT | SMALLINT | NULL | NULL | 0 | 0 | NULL | NULL |
长整数(64) | System.Int64 | BIGINT | INT | NULL | NULL | 0 | 0 | NULL | NULL |
定长文本 | System.String | char | CHAR | NULL | NULL | 10 | 0 | NULL | NULL |
二进制 | System.Byte[] | Image | LONGTEXT | NULL | NULL | 200 | 0 | NULL | NULL |
时间戳 | System.Byte[] | timestamp | TIMESTAMP | NULL | NULL | 0 | 0 | NULL | NULL |
时间 | System.DateTime | Time | TIME | NULL | NULL | 0 | 0 | NULL | NULL |
非中文字符 | System.String | varchar | VARCHAR | NULL | NULL | 50 | 0 | NULL | NULL |
GUID | System.Guid | uniqueidentifier | CHAR(36) | NULL | NULL | 0 | 0 | NULL | NULL |
字节 | System.Byte | tinyint | TINYINT | NULL | NULL | 200 | 0 | NULL | NULL |
浮点小数 | System.Single | Float | FLOAT | NULL | NULL | 28 | 14 | NULL | NULL |
日期+时间 | System.DateTime | datetime | DATETIME | NULL | NULL | 0 | 0 | NULL | NULL |
是否 | System.Boolean | BIT | TINYINT | NULL | NULL | 0 | 0 | NULL | NULL |
枚举 | Netsharp.Core.EnumItem | uniqueidentifier | CHAR(36) | NULL | NULL | 0 | 0 | NULL | NULL |
长文本 | System.String | NTEXT | MEDIUMTEXT | NULL | NULL | 8000 | 0 | NULL | NULL |
日期 | System.DateTime | smalldatetime | DATE | NULL | NULL | 0 | 0 | NULL | NULL |
文本 | System.String | nvarchar | VARCHAR | NULL | NULL | 50 | 0 | NULL | NULL |
数值 | System.Decimal | Decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
金额 | System.Decimal | decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
数量 | System.Decimal | decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
单价 | System.Decimal | decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
换算率 | System.Decimal | decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 6 |
发票单价 | System.Decimal | Decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 4 |
百分比 | System.Decimal | Decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
汇率 | System.Decimal | Decimal | DECIMAL | NULL | NULL | 28 | 14 | 15 | 2 |
1.3.2 实体关系
暂无
1.3.3 常用的抽象实体
下面类图介绍了一般常用的实体基类,对于具体的业务一般也有自己的基类,处理特殊领域的抽象字段。
此类图是使用Netsharp的实体模型工具创建,他描述了Netsharp立足管理软件从平台角度抽象的一些抽象类。为什么会有如此复杂继承关系的实体基类?原因是每一种基类对应着一种业务场景,每种业务场景除了静态模型结构一直外,还有相同相同的业务逻辑,有了静态模型后紧跟着就是写公用的业务逻辑代码。下面再介绍类图里关键的一些类。
1.可持久化实体基类( Entity)
类名 | Netsharp.Core.Entity | |||
名称 | 可持久化实体基类 | |||
说明 | Entity是抽象类,他是Netsharp一切持久化类的基类。Netsharp的实体模型设计时支持类的继承关系,如果不选择基类,则默认从此类继承。所以Entity类和.NET的Object类比较类似。 | |||
相关字段 | ||||
属性名 | 属性字段 | 类型名称 | Csharp类型 | SqlServer类型 |
Id | Id | GUID | System.Guid | uniqueidentifier |
创建人 | Creator | 文本 | System.String | nvarchar |
创建时间 | CreateTime | 日期+时间 | System.DateTime | datetime |
创建人Id | IdCreator | GUID | System.Guid | uniqueidentifier |
修改人 | Updator | 文本 | System.String | nvarchar |
修改时间 | UpdateTime | 日期+时间 | System.DateTime | datetime |
修改人Id | IdUpdator | GUID | System.Guid | uniqueidentifier |
时间戳 | Ts | 时间戳 | System.Byte[] | timestamp |
顺序 | Seq | 数值 | System.Decimal | Decimal |
2. 业务实体基类(BusinessEntity)
类名 | Netsharp.Core.BusinessEntity | |||
名称 | 业务实体 | |||
说明 | 此类携带一些业务的常用信息,包括编码、名称、备注 | |||
相关字段 | ||||
属性名 | 属性字段 | 类型名称 | Csharp类型 | SqlServer类型 |
编码 | Code | 文本 | System.String | nvarchar |
名称 | Name | 文本 | System.String | nvarchar |
备注 | Memoto | 文本 | System.String | nvarchar |
编码顺序 | CodeSeq | 整数(32) | System.Int32 | int |
3.档案基类(ArchiveEntity)
类名 | Netsharp.Core.ArchiveEntity | |||
名称 | 档案基类 | |||
说明 | 档案指的是商品、仓库、计量单位等类型实体 | |||
相关字段 | ||||
属性名 | 属性字段 | 类型名称 | Csharp类型 | SqlServer类型 |
停用 | Disabled | 是否 | System.Boolean | BIT |
助记码 | Shorthand | 文本 | System.String | nvarchar |
4.分类实体基类(CategoryEntity)
类名 | Netsharp.Core.CategoryEntity | |||
名称 | 分类实体基类 | |||
说明 | 此类实体在界面上一般以树的方式进行展示,如组织机构 | |||
相关字段 | ||||
属性名 | 属性字段 | 类型名称 | Csharp类型 | SqlServer类型 |
上级 | IdParent | GUID | System.Guid | uniqueidentifier |
层码 | PathCode | 整数(32) | System.Int32 | int |
内部层码 | PathCodes | 文本 | System.String | nvarchar |
路径名称 | PathName | 文本 | System.String | nvarchar |
末级节点 | IsLeaf | 是否 | System.Boolean | BIT |
路径节点 | IsPath | 是否 | System.Boolean | BIT |
5.单据实体基类(VoucherEntity)
类名 | Netsharp.Core.VoucherEntity | |||
名称 | 业务单据 | |||
说明 | 业务单据指的是销售订单、发货单、出库单、生产加工单这种实体 | |||
相关字段 | ||||
属性名 | 属性字段 | 类型名称 | Csharp类型 | SqlServer类型 |
单据日期 | VoucherDate | 日期 | System.DateTime | smalldatetime |
单据状态 | VoucherState | 枚举 | Netsharp.Core.EnumItem | uniqueidentifier |
打印次数 | PrintCount | 整数(32) | System.Int32 | int |
打印日期 | PrintDate | 日期 | System.DateTime | smalldatetime |
审核日期 | AuditedDate | 日期 | System.DateTime | smalldatetime |
来源单据Id | SourceId | GUID | System.Guid | uniqueidentifier |
来源单据号 | SourceCode | 文本 | System.String | nvarchar |
来源单日期 | SourceDate | 日期 | System.DateTime | smalldatetime |
中止人 | Closer | 文本 | System.String | nvarchar |
中止日期 | CloseDate | 日期 | System.DateTime | smalldatetime |
中止人Id | IdCloser | GUID | System.Guid | uniqueidentifier |
变更人 | Changer | 文本 | System.String | nvarchar |
变更日期 | ChangeDate | 日期 | System.DateTime | smalldatetime |
变更人Id | IdChanger | GUID | System.Guid | uniqueidentifier |
1.3.4 实体字段扩展
暂无
1.3.5 实体建模工具
一般的实体设计结果会通过xml或者数据库维护实体的元数据,维护这些元数据会提供工具,这些工具包括自动生成代码,同步数据库,格式验证、自动生成界面等。在Netsharp中提供了实体模型工具进行维护实体元数据,下面几张图概要介绍此工具。
1. 实体模型图
2. 实体设置
3. 实体高级设置
4. 实体关系设置
1.4 实体集合(SET方案)
一般C#操作实体集合的时候,如有一个User类,User集合一般用List<User>表示,在Netsharp中集合在涉及界面绑定、序列化、持久化的时候则用Table<User>,而且User需要从Entity继承。这种方式本文称之为SET方案。
SET是类似微软提供的DataSet(DataSet包括DataTable、DataRelation、DataColumn和DataRow等对象)的一套实体容器方案,Netsharp自己实现了一套类似的结构,对应的类有Set、ITable、Column、IRow,Netsharp实体的基类为Entity他实现的IRow接口。
1.4.1 使用SET的目的
- 提升性能,尤其在持久化(大数据查询)和序列化(减少数据冗余、减少运行时建立对象的关联关系)时
- 易于查询,同时拥有数据库(这在做抽象处理如平台级别的功能体现尤为明显)和面向对象的查询特性
- 解决缓存的级联更新问题
1.4.2 SET结构
1.4.2.1 SET和ITable的关系
SET包括如下几个部分:
- SET容器
- ITable和IRow。
ITable和IRow为C#接口。ITable的子类有Table<Row>和Table<Entity>;IRow的子类有Entity和Row。
- 关系(TableRelation)
- 列和主健
- 约束(暂未实现)
1.4.2.2 SET和实体相关的集合类
1.4.2.3 Table、Row、实体的关系
1.4.2.4 名词解
接口说明
序号 |
类名 |
名称 |
父接口/类 |
子接口/类 |
说明 |
1 |
IRows |
行集合接口 |
|
ISubs、ITable |
|
2 |
ISubs |
子对象接口 |
|
Subs |
主外键关系中,一个主健对应的多个外键 组合聚合关系中,一个对象对应的多个子对象 |
3 |
|
|
|
|
|
4 |
|
|
|
|
|
5 |
|
|
|
|
|
6 |
|
|
|
|
|
7 |
|
|
|
|
|
1.4.3 内存中创建对象
Netsharp中的实体必须属于一个Table,一般Table属于一个SET。
Netsharp中不支持使用new关键字创建一个对象,因为实体模型生成的实体类构造函数可见性为protected。Netsharp中可以使用EntityService.CreateInstance()方法和IRows.New**()方法创建一个实体。
Netsharp中创建实体对象有三种方式:
- 创建实体和Table
- 从Table<T>中创建实体
- 从ISubs中创建实体
1.4.3.1 创建实体
1 Invoice invoice = EntityService.CreateInstance<Invoice>(null); 2 { 3 invoice.EntityState = EntityState.New; 4 invoice.BusinessCode = saleOrder.Code; 5 invoice.CustomerName = saleOrder.InvoiceName; 6 invoice.CustomerContactInfo = saleOrder.ReceiveInfor; 7 }
1.4.3.2 实体集合创建实体
1.4.3.2.1 实体集合创建实体的四种方式
从集合中创建一个实体有四中方法,比较常用的是IRows<T>的NewItem方法。下面是四种方法的说明:
编号 |
方法 |
支持的类 |
说明 |
1 |
NewLine() : IRow |
IRows子类 |
新增行 新增的行会自动添加到集合中 不生成默认值、不添加马车,以提升性能 |
2 |
NewRow() : IRow |
IRows子类 |
新增行 新增的行会自动添加到集合中 自动生成默认值,自动添加配置的三驾马车 |
2 |
NewEntity() : IEntity |
IRows子类 |
同NewRow,返回类型为IEntity |
4 |
NewItem() : T |
IRows<T>子类 |
同NewRow,返回类型为T |
1.4.3.2.2 示例代码
1 Table<Invoice> table = EntityService.CreateTable<Invoice>(null); 2 3 for (int i = 0; i < 10; i++) 4 { 5 Invoice invoice = table.NewItem(); 6 { 7 invoice.EntityState = EntityState.New; 8 invoice.BusinessCode = saleOrder.Code; 9 invoice.CustomerName = saleOrder.InvoiceName; 10 invoice.CustomerContactInfo = saleOrder.ReceiveInfor; 11 } 12 13 //table.Add(invoice); //不需要此代码 14 }
1.4.3.3 从ISubs中创建实体
下面代码演示了创建发票,同时创建10条发表明细,发票和发票明细需要同时维护在同一个SET中。两种代码的效果是一样的,推荐使用第一种代码,第二种是为了进行和第一种代码的比较。
1.4.3.3.1 示例代码一
1 Invoice invoice = EntityService.CreateInstance<Invoice>(null); 2 { 3 invoice.EntityState = EntityState.New; 4 invoice.BusinessCode = saleOrder.Code; 5 invoice.CustomerName = saleOrder.InvoiceName; 6 invoice.CustomerContactInfo = saleOrder.ReceiveInfor; 7 } 8 9 foreach (SetInventoryDetail setInventoryDetail in saleOrder.SetInventoryDetails) 10 { 11 InvoiceDetail invoiceDetail = invoice.InvoiceDetails.NewItem(); 12 { 13 invoiceDetail.EntityState = EntityState.New; 14 //invoiceDetail.IdInvoice = invoice.Id; //不需要此代码 15 //invoiceDetail.Invoice = invoice; //不需要此代码 16 17 invoiceDetail.TaxRate = setInventoryDetail.TaxRate ?? 0; 18 invoiceDetail.TaxSubject = SalesOption.Instance.TaxSubject; 19 invoiceDetail.Tax = setInventoryDetail.TaxAmount ?? 0 * setInventoryDetail.TaxRate ?? 0; 20 invoiceDetail.DiscountAmount = 0; 21 } 22 23 //invoice.InvoiceDetails.Add(invoiceDetail); //不需要此代码 24 }
1.4.3.3.2 示例代码二
1 Invoice invoice = EntityService.CreateInstance<Invoice>(null); 2 { 3 invoice.EntityState = EntityState.New; 4 invoice.BusinessCode = saleOrder.Code; 5 invoice.CustomerName = saleOrder.InvoiceName; 6 invoice.CustomerContactInfo = saleOrder.ReceiveInfor; 7 } 8 9 foreach (SetInventoryDetail setInventoryDetail in saleOrder.SetInventoryDetails) 10 { 11 InvoiceDetail invoiceDetail = EntityService.CreateInstance<InvoiceDetail>(invoice.Set); 12 { 13 invoiceDetail.EntityState = EntityState.New; 14 invoiceDetail.IdInvoice = invoice.Id; //注释的两行代码任意一行即可 15 invoiceDetail.Invoice = invoice; //注释的两行代码任意一行即可 16 17 invoiceDetail.TaxRate = setInventoryDetail.TaxRate ?? 0; 18 invoiceDetail.TaxSubject = SalesOption.Instance.TaxSubject; 19 invoiceDetail.Tax = setInventoryDetail.TaxAmount ?? 0 * setInventoryDetail.TaxRate ?? 0; 20 invoiceDetail.DiscountAmount = 0; 21 } 22 23 //invoice.InvoiceDetails.Add(invoiceDetail); //不需要此代码 24 }
1.4.4 数据交换
数据交换指的是两个SET或者两个实体之间的数据复制、覆盖等功能,Netsharp支持两种场景的数据交换:SET查询和导入。
1.4.4.1 SET查询
SET查询是把SET看做内存数据库,执行查询,得到结果。查询的语句不是SQL而是Setql对象。返回的结构是一个新的SET,此SET是源SET的副本。
1 /// <summary> 2 /// SET查询语言 3 /// CascadeTypes和Selectes可以只设置一个,Selectes优先级高 4 /// </summary> 5 public class Setql 6 { 7 /// <summary> 8 /// 查询的实体 9 /// 仅仅支持实体,不支持字段 10 /// </summary> 11 public string[] Selectes { get; set; } 12 13 /// <summary> 14 /// 查询主实体的Id集合 15 /// </summary> 16 public Guid[] Filters { get; set; } 17 18 /// <summary> 19 /// 查询主实体EntityId 20 /// </summary> 21 public string EntityId { get; set; } 22 23 /// <summary> 24 /// 连接类型 25 /// </summary> 26 public CascadeTypes CascadeTypes { get; set; } 27 28 /// <summary> 29 /// 行字段选择模式 30 /// </summary> 31 public SetPropertySelection PropertySelection { get; set; } 32 }
1.4.4.1.1 根据指定的实体角色路径查询
1 Setql sql = new Setql() 2 { 3 Selectes = new string[] { "Description", "Description.PartProperties" }, 4 Filters = new Guid[] { this.Description .Id}, 5 }; 6 7 Set set= Description.Set.Select(sql);
1.4.4.1.2 根据智能解析的实体角色路径查询
1 IEntity currentItem = this.CurrentItem as IEntity; 2 3 Setql sql = new Setql() 4 { 5 EntityId=Description.EntityId, 6 CascadeTypes=CascadeTypes.All, 7 PropertySelection=SetPropertySelection.AllProperties, 8 Filters = new Guid[] { currentItem.Id } 9 }; 10 11 Set set= currentItem.Set.Select(sql);
1.4.4.2 SET导入
SET的导入是把来源的SET数据同步复制到目的SET中,来源的SET将会被内存释放掉。
SET导入方法的声明为:
1 /// <summary> 2 /// 把来源SET克隆到当前实体 3 /// 来源SET数据可能将被破坏 4 /// </summary> 5 /// <param name="duplicateObject">来源对象</param> 6 /// <param name="cascadeTypes">级联类型</param> 7 /// <param name="importMode">克隆模式</param> 8 public void Import(Set sourceSet, CascadeTypes cascadeTypes, ImportMode importMode)
CascadeTypes为导入来源SET时确定要导入来源SET的哪些表,其枚举明细如下声明:
1 /// <summary> 2 /// 实体关系级联类型 3 /// </summary> 4 public enum CascadeTypes 5 { 6 /// <summary> 7 /// 未设置 8 /// </summary> 9 None=0, 10 11 /// <summary> 12 /// 单个实体,不考所有关系 13 /// </summary> 14 Single=1, 15 16 /// <summary> 17 /// 关联组合关系(多级次) 18 /// </summary> 19 Composition=2, 20 21 /// <summary> 22 /// 所有关系,即关联组合和引用关系(组合多几次,引用一级) 23 /// </summary> 24 All=3, 25 }
ImportMode确定行数据在导入时的处理模式,其枚举明细如下声明:
1 /// <summary> 2 /// 数据集合根据数据库记录进行克隆方式 3 /// </summary> 4 public enum ImportMode 5 { 6 /// <summary> 7 /// 不克隆 8 /// </summary> 9 Never=0, 10 11 /// <summary> 12 /// 行克隆,不克隆属性 13 /// 新增的行也克隆 14 /// </summary> 15 Row=1, 16 17 /// <summary> 18 /// 克隆且【不保留】已经修改的字段 19 /// 新增的行也克隆 20 /// </summary> 21 AllProperties=2, 22 23 /// <summary> 24 /// 只克隆被修改的字段 25 /// </summary> 26 ChangedProperties=3, 27 }
1.4.4.3 实体导入
实体数据导入和SET的导入类似,不同的是,一次只对一个实体进行导入。SET导入来源只有一个实体的情况,使用SET导入性能更高。下面是实体导入的接口声明。
1 public interface IEntity 2 { 3 /// <summary> 4 /// 把来源对象导入到当前实体 5 /// </summary> 6 /// <param name="duplicateObject">来源对象</param> 7 /// <param name="cascadeTypes">级联类型</param> 8 /// <param name="importMode">导入模式</param> 9 void Import(IEntity sourceEntity, CascadeTypes cascadeTypes, ImportMode importMode); 10 }
1.4.4.4 SET常见数据交换场景
|
名称 |
终端 |
场景 |
分析 |
级联关系 |
导入方式 |
保留S |
可Select |
状态 |
1 |
懒加载后填充 |
界面 |
1.列表数据显示是只查询可见字段 2.点击某行时填充完整数据 |
|
All |
AllProperties |
否 |
否 |
Clear |
2 |
刷新 |
界面 |
1.列表或者表单 2.重新查询后刷新数据 |
|
All |
AllProperties |
否 |
否 |
Clear |
3 |
生成DTO |
界面 |
1.界面操作,提取DTO |
1.点击界面操作后需要立即生成DTO,因为在提交服务前要对进行一些前置处理或者校验,可能会改变实体数据,一旦操作失败,这些影响必须失效.如果没有撤销回退机制, 2.在提交保存时可以只序列化修改的字段 |
All
Composite |
Rows
Changed Properties |
是 |
是 |
复制 |
4 |
服务数据同步 |
服务 |
1.从数据库查询对象 2.同步从前台传递过来的数据 |
1.只需要同步修改的字段 |
Composite |
Changed Properties |
否 |
否 |
复制 |
5 |
界面操作后置 |
界面端口 |
1.界面提交服务后,返货数据对象 2.返回数据交换到界面数据源上 |
|
All |
AllProperties |
否 |
否 |
Clear |
6 |
界面调用服务,但未持久化 |
界面/ 服务 |
1.前台调用后台服务 2.后台服务调用复杂算法,但是不持久化 3,用户确认后提交服务持久化 此处分析第二步 |
1.删除状态的实体保留(单品明细) 2.修改状态的保留 |
All |
AllProperties |
是 |
是 |
复制 |
7 |
生单\回写 |
服务 |
1.自身保存 2.回写上游 |
1.持久化时删除状态的实体保留 |
|
|
|
|
不 处理 |
8 |
读缓存 |
界面/服务 |
|
1.删除状态的实体删除 2.处理组合关系的数据 |
All? |
AllProperties |
是 |
是 |
Clear |
9 |
写缓存 |
|
|
|
Composite |
AllProperties |
可能是 |
否 |
Clear |
10 |
数据合并 |
服务 |
插件导出/雄鹰升级 |
1.行级别增量添加 |
All |
Row |
否 |
否 |
Clear |
1.1 附录:Netsharp实体设计相关接口说明
1.1.1 IRow接口
1 using System.Collections.Generic; 2 using System; 3 using System.ComponentModel; 4 5 namespace Netsharp.Core 6 { 7 /// <summary> 8 /// 列表行 9 /// 子类有两种情况,Row和任意的平台实体 10 /// </summary> 11 public interface IRow : IDisposable, IDataErrorInfo 12 { 13 /// <summary> 14 /// 行对应的列表 15 /// </summary> 16 ITable Table { get; set; } 17 18 /// <summary> 19 /// 当前行所属的SET 20 /// </summary> 21 Set Set { get; } 22 23 /// <summary> 24 /// 实体状态 25 /// </summary> 26 EntityState EntityState { get; set; } 27 28 /// <summary> 29 /// Row中为行的所有字段值,实体中为扩展属性值 30 /// </summary> 31 Dictionary<string, object> InnerValues { get; } 32 33 /// <summary> 34 /// 属性索引 35 /// </summary> 36 /// <param name="propertyName">属性名称</param> 37 object this[string propertyName] { get; set; } 38 39 /// <summary> 40 /// 设置成员变量的值 41 /// 特征:1,批量读写属性值时性能更高 42 /// 2,记录ChangedProperties 43 /// 3,维护EntityState 44 /// <param name="propertyName">属性名称,不为成员的名称,而为成员对应的属性的名称</param> 45 /// <param name="value">成员值</param> 46 /// <param name="isNotify">是否执行触发器,默认为false</param> 47 void Field(string propertyName, object value, bool isNotify = false); 48 49 /// <summary> 50 /// 得到引用对象 51 /// 如果当前行属于Set,且当前行所在的表有引用的元数据信息,则根据元数据到引用表中查找对应的引用对象 52 /// </summary> 53 /// <param name="name">引用对象角色名称</param> 54 /// <returns>引用对象</returns> 55 IRow Reference(string name); 56 57 /// <summary> 58 /// 得到当前行的某个组合关系的子对象集合 59 /// 如果当前行属于Set,且当前行所在的表有组合的元数据信息,则根据元数据到子表中查找对应的子对象 60 /// </summary> 61 /// <param name="roleName">子对象的角色名称</param> 62 /// <returns>子对象集合列表</returns> 63 ISubs Subs(string roleName); 64 65 /// <summary> 66 /// 取消当前行与其他行的关系 67 /// 取消组合与引用关系的对象关联,不影响外键数据 68 /// </summary> 69 void Disconnect(); 70 71 /// <summary> 72 /// 发生值变化的属性集合 73 /// Key为字段名;Value为修改前属性值 74 /// </summary> 75 Dictionary<string, object> ChangedProperties { get; } 76 77 /// <summary> 78 /// 基于路径的属性索引器 79 /// </summary> 80 object this[string[] paths] { get; } 81 82 /// <summary> 83 /// 基于路径的属性对象,如SaleOrder.Customer.Name 84 /// </summary> 85 object FullProperty(string propertyName); 86 87 /// <summary> 88 /// 是否支持验证,默认为否 89 /// 如果支持验证,如果有错误则在界面上显示 90 /// </summary> 91 bool Validatable { get; set; } 92 93 /// <summary> 94 /// 验证的错误信息 95 /// Key为字段名;Value为错误信息 96 /// </summary> 97 Dictionary<string, string> Errors { get; } 98 99 /// <summary> 100 /// 属性触发器 101 /// </summary> 102 List<IPropertyTrigger> Triggers { get; } 103 104 /// <summary> 105 /// 属性条件控制器 106 /// </summary> 107 List<IPropertyCondition> Conditions { get; } 108 109 /// <summary> 110 /// 属性条件验证控制器 111 /// </summary> 112 List<IPropertyValidation> Validations { get; } 113 114 /// <summary> 115 /// 属性条件控制 116 /// </summary> 117 UiElementState Condition(string property); 118 119 /// <summary> 120 /// 判断字段是否被修改过 121 /// </summary> 122 bool IsPropertyChanged(string propertyName); 123 124 /// <summary> 125 /// 压缩,移除所有删除的组合子对象 126 /// </summary> 127 /// <returns>压缩的记录数</returns> 128 int Trim(); 129 130 /// <summary> 131 /// 组合子对象创建后置处理 132 /// 执行此方法时,此对象已经被添加到SET,且设置好了关联关系 133 /// ISubs的NewRow、NewEntity、NewItem时触发此方法 134 /// </summary> 135 void OnCreated(); 136 } 137 }
1.1.2 IEntity接口
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 5 namespace Netsharp.Core 6 { 7 /// <summary> 8 /// 实体接口 9 /// 职责:持久化、序列化、界面绑定、状态控制、字段扩展、触发器、验证控制 10 /// </summary> 11 public interface IEntity : IRow, INotifyPropertyChanged, IEditableObject 12 { 13 /// <summary> 14 /// 实体的唯一标识,对应数据库中主键 15 /// </summary> 16 Guid Id { get; set; } 17 18 /// <summary> 19 /// 创建时间 20 /// </summary> 21 DateTime? CreateTime { get; set; } 22 23 /// <summary> 24 /// 最后一次修改时间 25 /// </summary> 26 DateTime? UpdateTime { get; set; } 27 28 /// <summary> 29 /// 创建人名称 30 /// </summary> 31 string Creator { get; set; } 32 33 /// <summary> 34 /// 最后一次修改人名称 35 /// </summary> 36 string Updator { get; set; } 37 38 /// <summary> 39 /// 创建人Id 40 /// </summary> 41 Guid IdCreator { get; set; } 42 43 /// <summary> 44 /// 最后一次修改人Id 45 /// </summary> 46 Guid? IdUpdator { get; set; } 47 48 /// <summary> 49 /// 时间戳,用于并发控制 50 /// </summary> 51 byte[] Ts { get; set; } 52 53 /// <summary> 54 /// 序号,用户界面显示排序 55 /// </summary> 56 decimal? Seq { get; set; } 57 58 /// <summary> 59 /// 复制 60 /// 复制的实体状态为New状态,Id新创建 61 /// 只复制组合关系,不复制引用关系 62 /// 复制的对象和当前实体在同一个SET中 63 /// ChangedProperties的值不复制 64 /// 65 /// 相当于调用IEnitty.Copy(true)方法 66 /// </summary> 67 /// <returns>当前实体的副本</returns> 68 IEntity Copy(); 69 70 /// <summary> 71 /// 复制 72 /// 复制的实体状态为New状态,Id新创建 73 /// 只复制组合关系,不复制引用关系 74 /// 75 /// ChangedProperties的值不复制 76 /// </summary> 77 /// <param name="isNocopyMode"> 78 /// 是否启用不复制模式; 79 /// 为true时,实体元数据中设置为“不复制”的属性不进行复制; 80 /// 为false时,除了Id字段,其他字段都进行复制 81 /// </param> 82 /// <returns>当前实体的副本</returns> 83 IEntity Copy(bool isNocopyMode); 84 85 /// <summary> 86 /// 克隆当前实体 87 /// 复制的实体状态不变,Id不变 88 /// 关系的复制根据EntityCascadeTypes设置 89 /// 复制对象放在新的SET中 90 /// </summary> 91 /// <param name="cascadeTypes">级联类型</param> 92 /// <param name="propertySelection">字段选择模式</param> 93 /// <returns>副本</returns> 94 IEntity Clone(CascadeTypes cascadeTypes, SetPropertySelection propertySelection); 95 96 /// <summary> 97 /// 把来源对象导入到当前实体 98 /// </summary> 99 /// <param name="sourceEntity">来源对象</param> 100 /// <param name="cascadeTypes">级联类型</param> 101 /// <param name="importMode">导入模式</param> 102 void Import(IEntity sourceEntity, CascadeTypes cascadeTypes, ImportMode importMode); 103 104 /// <summary> 105 /// 单个对象导入 106 /// 包括Id和EntityState 107 /// </summary> 108 /// <param name="sourceEntity">来源实体</param> 109 /// <param name="importMode">导入模式</param> 110 void SingleImport(IEntity sourceEntity, ImportMode importMode); 111 } 112 }
1.1.3 ITable接口
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Data; 5 6 namespace Netsharp.Core 7 { 8 /// <summary> 9 /// 表接口 10 /// </summary> 11 public interface ITable : IRows 12 { 13 /// <summary> 14 /// 表的所有列 15 /// </summary> 16 ColumnCollection Columns { get; set; } 17 18 /// <summary> 19 /// 当前表对应的EntityId 20 /// </summary> 21 string EntityId { get; set; } 22 23 /// <summary> 24 /// 当前表对应的数据库表名 25 /// </summary> 26 string TableName { get; set; } 27 28 /// <summary> 29 /// 当前表对应的EntityId的简名称 30 /// </summary> 31 string Code { get; set; } 32 33 /// <summary> 34 /// 当前表对应实体的显示 35 /// </summary> 36 string Name { get; set; } 37 38 /// <summary> 39 /// 当前表在数据库中的排序字段 40 /// </summary> 41 string Orderby { get; set; } 42 43 /// <summary> 44 /// 并发控制 45 /// </summary> 46 bool IsConcurrency { get; set; } 47 48 /// <summary> 49 /// 总记录数,分页时使用 50 /// </summary> 51 int TotalCount { get; set; } 52 53 /// <summary> 54 /// 当前表对应的行的代码类型 55 /// </summary> 56 Type RowType { get; } 57 58 /// <summary> 59 /// 当前表所属的Set 60 /// </summary> 61 Set Set { get; set; } 62 63 /// <summary> 64 /// 当前表和字表的组合关系描述 65 /// 当当前表和子表同属于一个Set时,可以通过IRow的Subs()方法得到当前行的子对象列表 66 /// </summary> 67 Dictionary<string, TableComposite> Compositions { get; } 68 69 /// <summary> 70 /// 当前表和引用表的引用关系描述 71 /// 当当前表和引用表同属于一个Set时,可以通过IRow的Reference()方法得到当前行的引用对象 72 /// </summary> 73 Dictionary<string, TableReference> References { get; } 74 75 /// <summary> 76 /// 新增列 77 /// </summary> 78 /// <param name="columnName">列名</param> 79 /// <param name="dateType">数据类型</param> 80 /// <param name="defaultValue">默认值</param> 81 /// <returns>新增的列</returns> 82 Column NewColumn(string columnName, DataType dateType, string defaultValue, bool isCustomer); 83 84 /// <summary> 85 /// ITable克隆 86 /// </summary> 87 /// <param name="isCopyValue">是否克隆表中的数据行,如果为否则只克隆表结构信息</param> 88 /// <returns>副本</returns> 89 ITable Clone(bool isCopyValue); 90 91 /// <summary> 92 /// 从来源ITable中复制数据到当前ITable 93 /// </summary> 94 /// <param name="table">来源ITable</param> 95 void Clone(ITable table); 96 97 /// <summary> 98 /// 当前ITable对应实体的触发器 99 /// </summary> 100 [SerializeIgnore, Exclusive] 101 List<IPropertyTrigger> Triggers { get; set; } 102 103 /// <summary> 104 /// 当前ITable对应实体的条件 105 /// </summary> 106 [SerializeIgnore, Exclusive] 107 List<IPropertyCondition> Conditions { get; set; } 108 109 /// <summary> 110 /// 当前ITable对应实体的验证 111 /// </summary> 112 [SerializeIgnore, Exclusive] 113 List<IPropertyValidation> Validations { get; set; } 114 115 /// <summary> 116 /// 添加实体元数据的马车到实体中 117 /// </summary> 118 void RegistTorika(IRow entity); 119 120 /// <summary> 121 /// 取消表中行行与其他行的关系 122 /// 取消组合与引用关系的对象关联,不影响外键数据 123 /// </summary> 124 void Disconnect(); 125 126 /// <summary> 127 /// 压缩 128 /// 把状态为Deleted的行从当前表中删除,删除时同时会删除组合的子对象 129 /// </summary> 130 /// <returns></returns> 131 int Trim(); 132 133 /// <summary> 134 /// 弱类型实体集合 135 /// 实体类型和EntityId不一致 136 /// </summary> 137 bool IsWeakType { get; } 138 139 /// <summary> 140 /// 是否视图 141 /// </summary> 142 bool IsView { get; set; } 143 144 /// <summary> 145 /// 是否引用检查 146 /// </summary> 147 bool IsRefcheck { get; set; } 148 149 /// <summary> 150 /// 转换成DataTable 151 /// </summary> 152 /// <returns></returns> 153 DataTable ToDataTable(); 154 void ToDataTable(DataTable dataTable); 155 } 156 }
1.1.4 Set类
1 #region 程序集 Netsharp.Core.dll, v4.0.30319 2 // F:\SVN\panda\trunk\src\runtime\platform\cs\Netsharp.Core.dll 3 #endregion 4 5 using System; 6 using System.Collections.Generic; 7 using System.Data; 8 using System.Reflection; 9 10 namespace Netsharp.Core 11 { 12 // 摘要: 13 // 多个Table的集合容器 类似微软的DataSet 14 [Enterprise(typeof(SetSerializer))] 15 public class Set : IDisposable 16 { 17 public Set(); 18 19 // 摘要: 20 // 主Table中的第一行数据 21 [SerializeIgnore] 22 public IRow FirstRow { get; } 23 // 24 // 摘要: 25 // 主Table对应的EntityId 26 public string Main { get; set; } 27 // 28 // 摘要: 29 // 主Table SET中可以存放多个Table,但是在很多场景下一般会有一个主的Table 如订单列表显示界面,SET中的Table有订单、订单明细、商品、客户、仓库等,主Table为订单 30 [SerializeIgnore] 31 public ITable MainTable { get; } 32 // 33 // 摘要: 34 // SET中的ITable集合 键:ITable的EntityId 值:Table 35 [SerializeIgnore] 36 public Dictionary<string, ITable> Tables { get; } 37 38 // 摘要: 39 // ITable索引器 40 // 41 // 参数: 42 // entityId: 43 // ITable的EntityId 44 [SerializeIgnore] 45 public ITable this[string entityId] { get; } 46 47 // 摘要: 48 // 添加一个ITable到当前SET中 49 // 50 // 参数: 51 // table: 52 public void AddTable(ITable table); 53 // 54 // 摘要: 55 // 克隆 56 // 57 // 参数: 58 // isData: 59 // 是否克隆Set中的数据,为False是只克隆的Table结构 60 // 61 // 返回结果: 62 // 克隆对象的副本Set 63 public Set Clone(bool isData); 64 // 65 // 摘要: 66 // 当前SET中是否包括某个ITable 67 // 68 // 参数: 69 // enitytId: 70 // EntityId 71 // 72 // 返回结果: 73 // 是否包括 74 public bool Contains(string enitytId); 75 public void Disconnect(); 76 // 77 // 摘要: 78 // 内存释放 79 public void Dispose(); 80 // 81 // 摘要: 82 // 得到主Table的第一行,并以泛型方式返回 83 // 84 // 类型参数: 85 // T: 86 // 泛型约束 87 // 88 // 返回结果: 89 // 主Table的第一行 90 public T FirstItem<T>() where T : IRow; 91 // 92 // 摘要: 93 // 得到一个ITable 94 // 95 // 类型参数: 96 // T: 97 // ITable对应实体类型的泛型声明 98 public Table<T> GetTable<T>() where T : IEntity; 99 // 100 // 摘要: 101 // 根据EntityId 102 // 103 // 参数: 104 // Entityid: 105 public ITable GetTable(string Entityid); 106 // 107 // 摘要: 108 // 把来源SET克隆到当前实体 来源SET数据可能将被破坏 109 // 110 // 参数: 111 // duplicateObject: 112 // 来源对象 113 // 114 // importMode: 115 // 克隆模式 116 public void Import(Set sourceSet, ImportMode importMode); 117 // 118 // 摘要: 119 // 把来源对象克隆到当前实体 120 // 121 // 参数: 122 // duplicateObject: 123 // 来源对象 124 // 125 // cascadeTypes: 126 // 级联类型 127 // 128 // importMode: 129 // 克隆模式 130 // 131 // 返回结果: 132 // 导入后SET中对应的实体 133 public IEntity Import(IEntity sourceEntity, CascadeTypes cascadeTypes, ImportMode importMode); 134 // 135 // 摘要: 136 // 把来源SET克隆到当前实体 来源SET数据可能将被破坏 137 // 138 // 参数: 139 // sourceSet: 140 // 来源SET 141 // 142 // cascadeTypes: 143 // 级联类型 144 // 145 // importMode: 146 // 克隆模式 147 public void Import(Set sourceSet, CascadeTypes cascadeTypes, ImportMode importMode); 148 // 149 // 摘要: 150 // 把一个文件反序列化为Set 151 public static Set ReadXml(string fileName, bool crypt = false); 152 // 153 // 摘要: 154 // 删除一行记录,包括该行的所有组合关系 155 // 156 // 参数: 157 // entity: 158 public int Remove(IEntity entity); 159 // 160 // 摘要: 161 // Set查询 查询结果的IRow、ITable、Set被复制一份,和现有的数据在引用上没有关系 162 public Set Select(Setql sql); 163 public DataSet ToDataSet(CascadeTypes cascadeType); 164 // 165 // 摘要: 166 // 压缩,移除所有删除的组合子对象 167 public int Trim(); 168 // 169 // 摘要: 170 // 尝试根据EntityId读取ITable 171 // 172 // 参数: 173 // entityId: 174 // EntityId 175 // 176 // table: 177 // ITable 178 // 179 // 返回结果: 180 // 是否有次ITable 181 public bool Try(string entityId, out ITable table); 182 // 183 // 摘要: 184 // 更新SET 把来源实体的数据更新到当前的SET中 有触发器处理 185 // 186 // 参数: 187 // entity: 188 // 来源实体 189 // 190 // cascadeTypes: 191 // 级联类型 192 public IEntity Update(IEntity entity, CascadeTypes cascadeTypes); 193 // 194 // 摘要: 195 // 把Set序列化到一个文件中 196 public void WriteXml(string fileName, bool crypt = false); 197 // 198 // 摘要: 199 // 把Set序列化到一个文件中 200 public void WriteXml(string fileName, SerializeOption option); 201 } 202 }
作者 :秋时
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。