我给MIS类型的软件分四个实现层次,三层架构。
BusinessLogic 业务实体 由LLBL Gen 生成业务实体,代码生成器生成
Interface 数据访问接口 根据实体产生的数据访问接口,由Code Smith生成
Manager 接口实现 根据实体产生的数据访问接口的实现代码,由Code Smith生成
UI 界面层 拖拉控件,绑定数据到界面中
Business Logic 业务实体层
以ORM作为数据访问基础技术,业务实体中包含数据之间的关系逻辑,而不再是用于填充数据的实体。
以上结构由LLBL Gen自动生成,它已经为我们生成了实体,实体验证类型,数据访问接口和相关的辅助类型。
公司注册中的公司实体,它的定义如下代码所示
[Serializable]
public partial class CompanyEntity : CommonEntityBase
// __LLBLGENPRO_USER_CODE_REGION_START AdditionalInterfaces
// __LLBLGENPRO_USER_CODE_REGION_END
{
#region Class Member Declarations
private EntityCollection<ModuleEntity> _modules;
// __LLBLGENPRO_USER_CODE_REGION_START PrivateMembers
// __LLBLGENPRO_USER_CODE_REGION_END
#endregion
#region Statics
private static Dictionary<string, string> _customProperties;
private static Dictionary<string, Dictionary<string, string>> _fieldsCustomProperties;
/// <summary>All names of fields mapped onto a relation. Usable for in-memory filtering</summary>
public static partial class MemberNames
{
/// <summary>Member name Modules</summary>
public static readonly string Modules = "Modules";
}
#endregion
/// <summary> Static CTor for setting up custom property hashtables. Is executed before the first instance of this entity class or derived classes is constructed. </summary>
static CompanyEntity()
{
SetupCustomPropertyHashtables();
}
/// <summary> CTor</summary>
public CompanyEntity():base("CompanyEntity")
{
InitClassEmpty(null, null);
}
.....
}
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
LLBL Gen设计器生成的实体代码有几个特点
- 生成有多个用途的构造方法(ctor)。我们经常用到的是不带参数的构造方法,和带有主键值参数的方法。
[EditorBrowsable(EditorBrowsableState.Never)]
protected CompanyEntity(SerializationInfo info, StreamingContext context) : base(info, context)
{
if(SerializationHelper.Optimization != SerializationOptimization.Fast)
{
_modules = (EntityCollection<ModuleEntity>)info.GetValue("_modules", typeof(EntityCollection<ModuleEntity>));
this.FixupDeserialization(FieldInfoProviderSingleton.GetInstance());
}
// __LLBLGENPRO_USER_CODE_REGION_START DeserializationConstructor
// __LLBLGENPRO_USER_CODE_REGION_END
}
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
这个构造方法用在序列化对象时发生,比如.net Remoting远程返回对象时。
- 生成包含自定义属性的字段 自定义属性常用用属性的特殊设置。比如CompanyEntity.CompanyCode,实际中为了不区分CompanyCode的大小写,统一要求为大写,我们可以在此添加自定义属性RequiredCap,再到程序运行时读取此属性,并设置控件的字母大小写特性。
private static Dictionary<string, string> _customProperties;
private static Dictionary<string, Dictionary<string, string>> _fieldsCustomProperties;
private static void SetupCustomPropertyHashtables()
{
_customProperties = new Dictionary<string, string>();
_fieldsCustomProperties = new Dictionary<string, Dictionary<string, string>>();
Dictionary<string, string> fieldHashtable;
fieldHashtable = new Dictionary<string, string>();
_fieldsCustomProperties.Add("CompanyCode", fieldHashtable);
......
Dictionary<string, string> fieldCustomProperties = CompanyEntity.FieldsCustomProperties["CompanyCode"];
string requiredCap = fieldCustomProperties["RequiredCap"];
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
读取自定义属性RequiredCap的值为true时,设置控件的CharachterCasing属性。
界面和逻辑分离
再来看业务实体的业务计算如何发生。示例代码如下所示
protected override void OnFieldValueChanged(object originalValue, IEntityField2 field)
{
base.OnFieldValueChanged(originalValue, field);
switch ((CompanyFieldIndex)field.FieldIndex)
{
case CompanyFieldIndex.DriverAssembly:
OnChangeDriverAssembly((string)originalValue);
break;
}
}
private void OnChangeDriverAssembly(string originalValue)
{
if (this.DriverAssembly == originalValue || String.IsNullOrEmpty(DriverAssembly)) return;
this.DriverType = BaseCommon.GetProjectName(ModuleType.BusinessLogic, DriverAssembly);
}
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
当我在界面中改变当前界面插件程序集时,它会为我自动读取这个程序集的类型信息,项目命名信息。要理解这种方式,需要先理解.NET开发中的数据绑定技术。数据源控件相当于一个桥梁,连接数据实体和界面控件,当给数据源控件赋值时,控件会读取数据实体的值,当界面中的控件值发生改变时,借助于数据源控件,自动把更改后的数据回写到数据实体中。所以,当数据实体中值发生改变后,我们可以注册相应的改变事件,作出业务逻辑处理,数据源控件会读取改变之后的数据实体值,呈现在界面上。几乎所有的业务逻辑是依照此方式编程,也实现了界面和逻辑分离。
界面和逻辑分离后,界面中的作用就是将控件绑定到数据源控件,再以Code Smith来生成数据读写接口:
public override EntityBase2 LoadEntity(string refNo)
{
IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
ItemEntity customer = manager.GetItem(refNo);
return customer;
}
public override void DeleteEntity(EntityBase2 entity)
{
ItemEntity user = (ItemEntity)entity;
IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
manager.DeleteItem(user);
}
public override void SaveEntity(EntityBase2 entity)
{
ItemEntity user = (ItemEntity)entity;
IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
manager.SaveItem(user);
}
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
系统中所有与数据库读写相关的界面代码均是以此方式实现。
Interface/Implementation 接口层和接口实现层
接口与它的实体均以Code Smith模板生成,效率高。如下所示的供应商接口
public interface IVendorManager
{
VendorEntity GetVendor(System.String VendorNo);
VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath);
VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);
EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket);
EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression);
EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath);
EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);
VendorEntity SaveVendor(VendorEntity vendor);
VendorEntity SaveVendor(VendorEntity vendor, EntityCollection entitiesToDelete);
VendorEntity SaveVendor(VendorEntity vendor, EntityCollection entitiesToDelete, string seriesCode);
void SaveCollection(EntityCollection vendors);
void DeleteVendor(VendorEntity vendor);
bool IsVendorExist(System.String VendorNo);
bool IsVendorExist(IRelationPredicateBucket filterBucket);
int GetVendorCount(IRelationPredicateBucket filterBucket);
VendorEntity CloneVendor(System.String VendorNo);
void PostVendor(System.String VendorNo);
void PostVendor(VendorEntity vendor);
void ApprovalItem(EntityCollection vendors);
}
实现接口的Manager类型代码例子如下
public class VendorManager : Foundation.Common.ManagerBase, IVendorManager
{
public VendorEntity GetVendor(System.String VendorNo)
{
return GetVendor(VendorNo, null);
}
public VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath)
{
return GetVendor(VendorNo, prefetchPath, null);
}
public VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList)
{
VendorEntity _Vendor = new VendorEntity(VendorNo);
using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter())
{
bool found = adapter.FetchEntity(_Vendor, prefetchPath, null, fieldList);
if (!found) throw new Foundation.Common.RecordNotFoundException("Invalid Vendor");
}
return _Vendor;
}
<style type="text/css"><br/>.csharpcode, .csharpcode pre<br/>{<br/>font-size: small;<br/>color: black;<br/>font-family: consolas, "Courier New", courier, monospace;<br/>background-color: #ffffff;<br/>/*white-space: pre;*/<br/>}<br/>.csharpcode pre { margin: 0em; }<br/>.csharpcode .rem { color: #008000; }<br/>.csharpcode .kwrd { color: #0000ff; }<br/>.csharpcode .str { color: #006080; }<br/>.csharpcode .op { color: #0000c0; }<br/>.csharpcode .preproc { color: #cc6633; }<br/>.csharpcode .asp { background-color: #ffff00; }<br/>.csharpcode .html { color: #800000; }<br/>.csharpcode .attr { color: #ff0000; }<br/>.csharpcode .alt <br/>{<br/>background-color: #f4f4f4;<br/>width: 100%;<br/>margin: 0em;<br/>}<br/>.csharpcode .lnum { color: #606060; }</style>
界面层中或是实体层,使用下面的接口来访问接口:
ICompanyManager _companyManager = ClientProxyFactory.CreateProxyInstance<ICompanyManager>();
CompanyEntity _company = _companyManager.GetCompany(“Kingston”)
如果没有采用分布式技术(.net Remoting,WCF),CreateProxyInstance方法直接返回ICompanyManager接口的实体类型的实例,供接口调用。如果有应用.net Remoting技术,则先以下面的方法产生服务器对象:客户端产生的实体对象会是一个远程代理,指向远程对象:
RemotingConfiguration.RegisterActivatedServiceType(type);
接口与实现分离的好处在这里体现的很明显,简单的切换部署模式(单机,分布式)不需要改变代码。
