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.DataEntity2 {3 public interface ITestProjEntity<T> : IEquatable<T>4 where T : ITestProjEntity<T>5  {6  }7 }

    该接口用于规范实体类编写。

    该接口命名规范遵循:I+<项目名>+Entity形式,该接口必须为泛型接口,该接口泛型参数必须是本接口。实体类只映射数据库实际结构,实体类之间主外键关联用程序逻辑维护,即:不采用实体间依赖的方式表示主外键关联(实体类不包含其他实体类的实例对象、列表、集合等)。

    实体类示例代码如下:

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 的轻量级系统基础架构就完成了。其中业务接口依赖于数据操作接口,业务模块与数据操作模块分别依赖于其所对应接口,具体模块之间没有依赖关系。各接口、具体模块均需添加实体模块的引用。

 

相关文章