Enterprise Solution 管理软件开发框架流程实战
1 定义模块和功能
执行系统功能(SAUTSF),在系统主文件(System Master File SAMF)模块中增加功能SAMFEM,Employee Master。
给有权限的用户组分配功能SAMFEM的权限,包含新增,删除,修改,打印,过帐权限,这是功能权限。
如果需要增加字段权限,比如可以编辑员工薪资字段,应该修改用户表(User)增加权限字段。
2 定义导航菜单
把增加的功能放置到合适的功能菜单中,菜单项的位置根据需要而定。比如员工主文件,可以放置到主档定义中,还可以放置一份到人事管理的设定菜单项中。定义一个菜单项位置,系统会生成三个地方的导航菜单项:标题栏下的主菜单(ToolStrip), 导航树右边的列表视图(ListView),导航图的上下文菜单(ContextMenu),三种方式帮助用户快速查找与执行功能。
3 设计数据库表
以员工表为例子,员工表的数据库脚本如下:
CREATE TABLE [dbo].[Employee] ( [Recnum] [numeric] (18, 0) NOT NULL IDENTITY(1, 1), [EmployeeNo] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Department] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [ProductionGroup] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [CompanyCode] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [BranchCode] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Name] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [Account] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Password] [nvarchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Gender] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Birthday] [datetime] NULL, [Mobile] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Address] [nvarchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [HomeTelephone] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Post] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Type] [char] (4) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [Suspended] [char] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [DF__tEmployee__Enabl__32CB82C6] DEFAULT ((1)), [JobTitle] [nvarchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [JoinDate] [datetime] NULL, [LeftDate] [datetime] NULL, [Photo] [image] NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO ALTER TABLE [dbo].[Employee] WITH NOCHECK ADD CONSTRAINT [CKC_TYPE_TEMPLOYE] CHECK (([Type]=(1) OR [Type]=(0))) GO ALTER TABLE [dbo].[Employee] ADD CONSTRAINT [PK_TEMPLOYEE] PRIMARY KEY CLUSTERED ([EmployeeNo]) ON [PRIMARY] GO ALTER TABLE [dbo].[Employee] WITH NOCHECK ADD CONSTRAINT [FK_Employee_Department] FOREIGN KEY ([Department]) REFERENCES [dbo].[Department] ([DepartmentCode]) GO ALTER TABLE [dbo].[Employee] NOCHECK CONSTRAINT [FK_Employee_Department] GO
4 LLBL Gen Pro生成实体映射
执行程序LLBL Gen Pro,连接到数据库并添加Employee数据表,最后点击生成源代码文件。
一般情况下用默认的命名规则即可。比如Item_No映射的实体字段名是ItemNo,默认去掉下划线。
5 实体数据访问接口与实现生成
用Template目录中的Code Smith模板生成接口与实现类,代码如下:
数据接口类:
public interface IEmployeeManager { EmployeeEntity GetEmployee(System.String EmpNo); EmployeeEntity GetEmployee(System.String EmpNo, IPrefetchPath2 prefetchPath); EmployeeEntity GetEmployee(System.String EmpNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList); EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket); EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression); EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath); EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList); EmployeeEntity SaveEmployee(EmployeeEntity employee); EmployeeEntity SaveEmployee(EmployeeEntity employee, EntityCollection entitiesToDelete); EmployeeEntity SaveEmployee(EmployeeEntity employee, EntityCollection entitiesToDelete, string seriesCode); void DeleteEmployee(EmployeeEntity employee); bool IsEmployeeExist(System.String EmpNo); bool IsEmployeeExist(IRelationPredicateBucket filterBucket); int GetEmployeeCount(IRelationPredicateBucket filterBucket); EmployeeEntity CloneEmployee(System.String EmpNo); void PostEmployee(System.String EmpNo); void PostEmployee(EmployeeEntity employee); }
数据实现类:
[RemoteService("EmployeeManager")] public class EmployeeManager : Foundation.Common.ManagerBase, IEmployeeManager { public EmployeeEntity GetEmployee(System.String EmpNo) { IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.EmployeeEntity); prefetchPath.Add(EmployeeEntity.PrefetchPathEmployeeTrainings); return GetEmployee(EmpNo, prefetchPath); } public EmployeeEntity GetEmployee(System.String EmpNo, IPrefetchPath2 prefetchPath) { return GetEmployee(EmpNo, prefetchPath, null); } public EmployeeEntity GetEmployee(System.String EmpNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList) { EmployeeEntity employee = new EmployeeEntity(EmpNo); using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { bool found = adapter.FetchEntity(employee, prefetchPath, null, fieldList); if (!found) throw new Foundation.Common.RecordNotFoundException("Invalid Employee"); } return employee; } public EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket) { return GetEmployeeCollection(filterBucket, null); } public EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression) { return GetEmployeeCollection(filterBucket, sortExpression, null); } public EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath) { return GetEmployeeCollection(filterBucket, sortExpression, prefetchPath, null); } public EntityCollection GetEmployeeCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList) { EntityCollection employeeCollection = new EntityCollection(new EmployeeEntityFactory()); using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { adapter.FetchEntityCollection(employeeCollection, filterBucket, 0, sortExpression, prefetchPath, fieldList); } return employeeCollection; } public EmployeeEntity SaveEmployee(EmployeeEntity Employee) { return SaveEmployee(Employee, null); } public EmployeeEntity SaveEmployee(EmployeeEntity Employee, EntityCollection entitiesToDelete) { return SaveEmployee(Employee, entitiesToDelete, string.Empty); } public EmployeeEntity SaveEmployee(EmployeeEntity Employee, EntityCollection entitiesToDelete, string seriesCode) { using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { try { adapter.StartTransaction(IsolationLevel.ReadCommitted, "SaveEmployee"); adapter.SaveEntity(Employee, true, false); IEmployeeTrainingManager trainingManager = ClientProxyFactory.CreateProxyInstance<IEmployeeTrainingManager>(); foreach (var employeeTraining in Employee.EmployeeTrainings) { trainingManager.SaveEmployeeTraining(employeeTraining); } adapter.Commit(); } catch { adapter.Rollback(); throw; } } return Employee; } public void DeleteEmployee(EmployeeEntity Employee) { using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { if (!adapter.IsEntityExist<EmployeeEntity>(Employee)) return; try { adapter.StartTransaction(IsolationLevel.ReadCommitted, "DeleteEmployee"); IEmployeeTrainingManager trainingManager = ClientProxyFactory.CreateProxyInstance<IEmployeeTrainingManager>(); foreach (var employeeTraining in Employee.EmployeeTrainings) { trainingManager.DeleteEmployeeTraining(employeeTraining); } adapter.DeleteEntity(Employee); adapter.Commit(); } catch { adapter.Rollback(); throw; } } } public bool IsEmployeeExist(System.String EmpNo) { RelationPredicateBucket filterBucket = new RelationPredicateBucket(); filterBucket.PredicateExpression.Add(EmployeeFields.EmployeeNo == EmpNo); return IsEmployeeExist(filterBucket); } public bool IsEmployeeExist(IRelationPredicateBucket filterBucket) { return (GetEmployeeCount(filterBucket) > 0); } public int GetEmployeeCount(IRelationPredicateBucket filterBucket) { using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter()) { return adapter.GetDbCount<EmployeeEntity>(filterBucket); } } public EmployeeEntity CloneEmployee(System.String EmpNo) { EmployeeEntity source = this.GetEmployee(EmpNo); EmployeeEntity employee = (EmployeeEntity)CloneEntity(source); return employee; } public void PostEmployee(System.String EmpNo) { return; } public void PostEmployee(EmployeeEntity Employee) { return; } }
6 界面设计,控件数据绑定,查找与钻取
Windows窗体设计的主要流程步骤
1) 拖一个EntityCollection组件到界面中,这个组件来自于LLBL Gen Pro生成的数据库映射项目。
设置EntityFactoryToUse=Kingston.FactoryClasses.EmployeeEntityFactory。
2) 拖多个BindingSource组件到界面。每一层控件都需要一个BindingSource组件,如果界面是单笔数据操作,还需要拖一个空的BindingSource组件到界面中,并将它设为窗体的NavigatorBindingSource。
3) 依次绑定控件与数据源组件的属性。要注意选对控件,TextEditor,NumbericEditor。对于必须输入值的控件,要设置Required=true,对于映射到主键字段的控件,要设AutoFind=true。
4) 给控件增加查找与钻取。
5) 设置控件的tab index。
7 验证和计算逻辑
1) 增加实体属性的默认值。修改文件EmployeeEntity.Implementation.cs
public partial class EmployeeEntity { protected override void OnInitialized() { base.OnInitialized(); // Assign default value for new entity if (Fields.State == EntityState.New) { #region DefaultValue // __LLBLGENPRO_USER_CODE_REGION_START DefaultValue this.Fields[(int) EmployeeFieldIndex.Suspended].CurrentValue = false; // __LLBLGENPRO_USER_CODE_REGION_END #endregion
2) 增加自动带值。比如输入员工所在的部门编码,要自动带入部门名称。
private void OnChangeDepartment(string originalValue) { IDepartmentManager departmentManager =CreateProxyInstance<IDepartmentManager>(); if (departmentManager.IsDepartmentExist(Department)) { ExcludeIncludeFieldsList fieldList = new ExcludeIncludeFieldsList(false); fieldList.Add(DepartmentFields.FullName); DepartmentEntity department = departmentManager.GetDepartment(Department, null, fieldList); this.DepartmentName = department.FullName; } }
3) 增加验证代码,对需要验证的属性值进行验证。
值验证:
public override bool ValidateFieldValue(IEntityCore involvedEntity, int fieldIndex, object value) { bool result = base.ValidateFieldValue(involvedEntity, fieldIndex, value); if (!result) return false; switch ((EmployeeFieldIndex)fieldIndex) { case EmployeeFieldIndex.Department: return ValidateDepartment((string)value, (EmployeeEntity)involvedEntity); break;
private bool ValidateDepartment(string value, EmployeeEntity EGEntity) { if (!string.IsNullOrWhiteSpace(value)) { IDepartmentManager departmentManager = CreateProxyInstance<IDepartmentManager>(); departmentManager.ValidateDepartment(value); }
return true; }
删除验证:
public override void ValidateEntityBeforeDelete(IEntityCore involvedEntity) { base.ValidateEntityBeforeDelete(involvedEntity); EmployeeEntity employeeEntity = (EmployeeEntity)involvedEntity; ISalesmanManager salesmanManager = CreateProxyInstance<ISalesmanManager>(); RelationPredicateBucket filterBucket = new RelationPredicateBucket(); filterBucket.PredicateExpression.Add(SalesmanFields.EmployeeNo == employeeEntity.EmployeeNo); if (salesmanManager.IsSalesmanExist(filterBucket)) { throw new FieldValidationException("Employee No. already used in salesman"); }
保存前验证:
public override void ValidateEntityBeforeSave(IEntityCore involvedEntity) { base.ValidateEntityBeforeSave(involvedEntity); EmployeeEntity employeeEntity = (EmployeeEntity)involvedEntity; if (string.IsNullOrWhiteSpace(employeeEntity.EmployeeNo)) { throw new EntityValidationException("Employee No. is required"); }
8 生成并发布程序
打开Visual Studio 2015或用MSBUILD生成程序。
执行.NET Reactor批处理脚本,将生成的文件复制到客户的更新服务器中。