C# 轻量级系统基础架构 (MVP MEF + EF6)
0 综述
1 mvp各模块规范
1.1 实体模块规范
1.1.1 命名规范
实体类封装到单独的dll中,dll命名遵循<projectname>.dataentity。数据库实体类名遵循:数据库表名去掉复数后缀,如“s”、“es”等(数据库表的名称必须是名词复数)。
1.1.2 架构规范
实体模块中必须包含一个泛型实体接口,供所有数据实体类实现该接口,该接口所继承接口亦为各实体类所统一实现(例如该接口实现自iequatable<t>),该接口泛型参数必须为本接口类型,该接口可以不声明任何成员。例如:
1 namespace testproj.dataentity 2 { 3 public interface itestprojentity<t> : iequatable<t> 4 where t : itestprojentity<t> 5 { 6 } 7 }
该接口用于规范实体类编写。
该接口命名规范遵循:i+<项目名>+entity形式,该接口必须为泛型接口,该接口泛型参数必须是本接口。实体类只映射数据库实际结构,实体类之间主外键关联用程序逻辑维护,即:不采用实体间依赖的方式表示主外键关联(实体类不包含其他实体类的实例对象、列表、集合等)。注意:实体类所有值类型(例如 int、bool、long)必须指定为可为空类型(即诸如int?, bool?, long?),这样便于通过反射,将实体类生成查询sql语句,即所有为空字段不生成条件。
实体类示例代码如下:
public class patient : iopentcmentity<patient> { public string id { get; set; } //主键id public string name { get; set; } //姓名 public bool equals(patient other) { if (other == null) { return false; } if (other == this) { return true; } bool res = other.id == id && other.name == name; return res; } }
1.2 数据库访问接口
1.2.1 命名规范
数据库访问接口必须封装到单独的dll中,dll命名遵循 i + <projectname>.dataoperate。数据库访问类命名遵循:i + <相对应实体类命名> + “dao”。
1.2.2 架构规范
数据库访问接口必须包含一个泛型接口,该泛型接口用于统一声明所有公共的数据库acid函数,该接口泛型参数必须是数据库实体类所共同实现的接口类型,该接口命名必须为ibasedao。示例代码如下:
1 public interface ibasedao<e> 2 where e : iopentcmentity<e> 3 { 4 /// <summary> 5 /// 是否存在id所对应的记录 6 /// </summary> 7 /// <param name="id"></param> 8 /// <returns></returns> 9 bool exists(string id); 10 /// <summary> 11 /// 是否存在参数对象所描述的记录(属性为空表示该属性不作为查询条件) 12 /// </summary> 13 /// <param name="condition"></param> 14 /// <returns></returns> 15 bool exists(e condition); 16 /// <summary> 17 /// 将参数描述的实体对象添加到数据库中 18 /// </summary> 19 /// <param name="po"></param> 20 /// <returns></returns> 21 bool addentity(e po); 22 /// <summary> 23 /// 将参数描述的实体对象集合添加到数据库中 24 /// </summary> 25 /// <param name="pos"></param> 26 /// <returns></returns> 27 bool addentity(list<e> pos); 28 /// <summary> 29 /// 根据id从数据库中删除实体 30 /// </summary> 31 /// <param name="id"></param> 32 /// <returns></returns> 33 bool delentity(string id); 34 /// <summary> 35 /// 依据参数描述的数据库记录更新数据库, 36 /// 本操作需要先从数据库获取具体的对象。 37 /// </summary> 38 /// <param name="po"></param> 39 /// <returns></returns> 40 bool updateentity(e po); 41 /// <summary> 42 /// 依据参数描述的对象为条件,获取记录数。 43 /// </summary> 44 /// <param name="condition"></param> 45 /// <returns></returns> 46 long getcount(e condition); 47 /// <summary> 48 /// 获取总记录数 49 /// </summary> 50 /// <returns></returns> 51 long getcount(); 52 /// <summary> 53 /// 获取所有实体对象集合 54 /// </summary> 55 /// <returns></returns> 56 list<e> selectentity(); 57 /// <summary> 58 /// 获取分页对象集合 59 /// </summary> 60 /// <param name="beg"></param> 61 /// <param name="len"></param> 62 /// <returns></returns> 63 list<e> selectentity(int beg, int len); 64 /// <summary> 65 /// 根据id获取一个实体对象 66 /// </summary> 67 /// <param name="id"></param> 68 /// <returns></returns> 69 e selectentity(string id); 70 /// <summary> 71 /// 依据条件获取实体集合 72 /// </summary> 73 /// <param name="condition"></param> 74 /// <returns></returns> 75 list<e> selectentity(e condition); 76 }
针对每个数据库表创建对应的数据访问接口,该接口继承自ibasedao,泛型参数为该数据表对应的实体类。代码示例如下:
1 public interface itestdao:ibasedao<test> 2 { 3 }
1.3 数据业务接口规范
1.3.1 命名规范
业务接口必须封装到单独的dll中,dll命名遵循<projectname>.databiz。业务类命名遵循:相对应实体类+"bo"。
1.3.2 架构规范
数据库业务接口必须包含一个公用的泛型接口,该泛型接口用于统一声明所有公共的数据库acid函数,该接口泛型参数必须是数据库实体类所共同实现的接口类型,和一个用于访问数据库的ibasedao类型属性。示例代码如下:
1 public interface ibasebo<e, dao> 2 where e : iopentcmentity<e> 3 where dao : ibasedao<e> 4 { 5 /// <summary> 6 /// 数据库操作对象 7 /// </summary> 8 dao dboperator { get; set; } 9 /// <summary> 10 /// 是否存在id所对饮的记录 11 /// </summary> 12 /// <param name="id"></param> 13 /// <returns></returns> 14 bool exists(string id); 15 /// <summary> 16 /// 是否存在参数对象所描述的记录(属性为空表示该属性不作为查询条件) 17 /// </summary> 18 /// <param name="condition"></param> 19 /// <returns></returns> 20 bool exists(e condition); 21 /// <summary> 22 /// 将参数描述的实体对象添加到数据库中 23 /// </summary> 24 /// <param name="po"></param> 25 /// <returns></returns> 26 bool addentity(e po); 27 /// <summary> 28 /// 将参数描述的实体对象集合添加到数据库中 29 /// </summary> 30 /// <param name="pos"></param> 31 /// <returns></returns> 32 bool addentity(list<e> pos); 33 /// <summary> 34 /// 根据id从数据库中删除实体 35 /// </summary> 36 /// <param name="id"></param> 37 /// <returns></returns> 38 bool delentity(string id); 39 /// <summary> 40 /// 依据参数描述的数据库记录更新数据库, 41 /// 本操作需要先从数据库获取具体的对象。 42 /// </summary> 43 /// <param name="po"></param> 44 /// <returns></returns> 45 bool updateentity(e po); 46 /// <summary> 47 /// 依据参数描述的对象为条件,获取记录数。 48 /// </summary> 49 /// <param name="condition"></param> 50 /// <returns></returns> 51 long getcount(e condition); 52 /// <summary> 53 /// 获取总记录数 54 /// </summary> 55 /// <returns></returns> 56 long getcount(); 57 /// <summary> 58 /// 获取所有实体对象集合 59 /// </summary> 60 /// <returns></returns> 61 list<e> selectentity(); 62 /// <summary> 63 /// 获取分页对象集合 64 /// </summary> 65 /// <param name="beg"></param> 66 /// <param name="len"></param> 67 /// <returns></returns> 68 list<e> selectentity(int beg, int len); 69 /// <summary> 70 /// 根据id获取一个实体对象 71 /// </summary> 72 /// <param name="id"></param> 73 /// <returns></returns> 74 e selectentity(string id); 75 /// <summary> 76 /// 依据条件获取实体集合 77 /// </summary> 78 /// <param name="condition"></param> 79 /// <returns></returns> 80 list<e> selectentity(e condition); 81 }
针对每个数据库表创建对应的数据访问接口,该接口继承自ibasedao,泛型参数为该数据表对应的实体类。代码示例如下:
public interface isymptombo : ibasebo<pathology, isymptomdao> { //todo:type your specific business logical code }
该接口中可以声明特定与该数据表的特定业务函数、属性。
1.4 数据访问模块
1.4.1 命名规范
数据访问模块必须封装为单独的dll,dll文件命名规范遵循<项目名>.dataoperate,数据访问类遵循 <表名单数形式> + dao 的命名规范。
1.4.2 架构规范
数据访问模块需要引用system.componentmodel.composition命名空间。数据访问模块若使用ef6,则必须包含一个数据上下文类,该类命名个规范为<project>context,该类用于提供entityframework6的数据上下文。
数据上下文类结构如下:
public class opentcmcontext : dbcontext { public opentcmcontext() : base("testconnstr") { } public dbset<testtablea> testtableacontext { get; set; } public dbset<testtablea> testtablebcontext { get; set; } }
数据访问类之间不互相依赖,每个数据访问类实现自按数据库表对应的数据访问接口,并具备一个私有只读的数据上下文对象。数据访问类每个实现自ibasedao的函数不互相引用。
数据访问类若使用ef6的形式,就需要辩证的看待数据库增删改查的方式,某些简单的数据库操作若使用纯ef6方式,数据库交互数量级有按指数翻倍的可能。本文建议的数据库访问方式,尽量采用sql,不涉及大量重复操作数据库的情况下,兼顾ef6的便捷操作。数据访问类需要向mef容器导出其自身,鉴于每个数据访问接口皆对应一个单独的实现类,这里采用[export(typeof(<父接口>))]的方式。
数据访问类部分代码示意如下:
[export(typeof(itestdao))] public class testdao : itestdao { public testdao() { context = new opentcmcontext(); } private readonly testprojcontext context; public bool exists(string id) { var res = context.testcontext.sqlquery("id='{0}'", id); if (res.firstordefault() == null) { return false; } int count = res.count(); return count > 0; } //... }
1.5 数据库业务模块
1.5.1 命名规范
数据库业务模块必须封装为单独的dll,dll文件命名规范为<项目名>.databiz,数据库业务类命名遵循<对应实体类名> + bo。
1.5.2 架构规范
数据库业务类实现自其对应的业务接口,其必须实现接口规定的数据库操作抽象接口对象属性,并标注从mef容器注入该对象实例。并在构造函数中显式构建该接口对象。该类也需要向mef容器导出其自身,供*业务构建其示例。数据库业务类参考代码示例如下:
[export("testbo", typeof(itestbo))] public class testbo : itestbo { public testbo() { var catalog = new directorycatalog("./"); var container = new compositioncontainer(catalog); container.composeparts(this); } [import] public itestdao dboperator { get; set; } public bool exists(string id) { return dboperator.exists(id); } //... }
2 总结
至此,一个基于.net + mef + ef6 的轻量级系统基础架构就完成了。其中业务接口依赖于数据操作接口,业务模块与数据操作模块分别依赖于其所对应接口,具体模块之间没有依赖关系。各接口、具体模块均需添加实体模块的引用。