博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
轻量级 C# 可复用系统架构的研究与总结 (MVX+MEF+EF6)
阅读量:4576 次
发布时间:2019-06-08

本文共 35383 字,大约阅读时间需要 117 分钟。

0 前言

  在企业级应用开发周期中,普遍存在随着应用程序规模扩大、版本更迭、甚至人员不稳定等带来整个应用程序系统代码结构混乱不堪、重复代码爆炸、超级函数量陡升等现象,最终影响项目按时交付和验收。笔者在经历过许多这样的开发历程过后,简单的整理形成本文。

1 概述

  本应用程序基础架构是通用型数据处理应用程序(系统)代码架构,采用轻量级架构,适用于团队项目的开展和版本迭代。在整套架构结构中,广泛采用IOC结构,采用精简和可重用的模板代码替代反复重叠且容易出错的工作。

2 结构说明

2.1 应用程序包(子项目)结构

  如上图所示,项目分为

  1. UI(界面)
  2. DataEntity(数据实体)
  3. IDataBiz(数据操作业务接口)
  4. IDataOperate(数据操作接口)
  5. DataBiz(数据业务实现)
  6. DataOperate(数据操作实现)
  7. Utils(工具集)

  共七部分。其中,各包之间依赖关系如下:

 

  由上图可见,数据访问实现模块与业务模块之间不存在依赖关系,各实现模块均依赖于其相对应的抽象接口模块;而依赖仅存在于接口之间(除实体模块和工具模块外),而UI模块仅依赖于数据访问接口。

3 程序集(模块)设计

  本章描述各程序集的主要设计思路,通过简化的代码与说明,阐述各程序集实现细则。由于Utils程序集受到其他各实现模块的引用,所以本章先从Utils开始,顺序依次为Utils->Entity->IDataOperate->IDataBiz->DateOperate->DataBiz,本文不对UI部分进行描述。

3.1 Utils模块

Utils 模块为整个系统提供基础算法支持、对象类型转化、对象克隆、字符串处理等各种功能函数。本文使用三个工具类:实体对象工具(EntityObjectUtil)、KMP算法工具(KMPUtil,用于快速匹配大量字符串)、对象深拷贝工具(ObjectCloneUtil)。具体代码如下:

3.1.1 实体对象工具类

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace OpenTCM.Utils{    public class EntityObjUtils    {        ///         /// 获取对象所有属性键值对        ///         /// 
对象类型
/// 目标对象 ///
对象键值对集
public Dictionary
GetObjProperties
(T t) { Dictionary
res = new Dictionary
(); if (t == null) { return null; } var properties = t.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); if (properties.Length <= 0) { return null; } foreach (var item in properties) { string name = item.Name; object value = item.GetValue(t, null); if (item.PropertyType.IsValueType || item.PropertyType.Name.StartsWith("String")) { res.Add(name, value); } else { GetObjProperties(value); } } return res; } ///
/// 实体类转SQL where 子串(不包含Where) /// 实体类必须可序列化,属性必须仅为值类型和字符串类型(不支持其他类型) /// ///
实体类对象类型,必须实现IEquatable接口
///
作为条件的实体类 ///
各属性间逻辑运算符(如AND,OR) ///
是否为模糊查询 ///
SQL条件部分语句
public string EntityToSqlCondition
(T entity, string logicOperator = "and", bool isFuzzy = false) where T:class, IEquatable
, new() { if (entity == null) { return null; } if (entity.Equals(new T())) { return null; } //对象属性序列 var properties = entity.GetType() .GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) .AsEnumerable().Where((p) => { bool res = p.GetValue(entity, null) != null; if (res) { res = !string.IsNullOrWhiteSpace(p.GetValue(entity, null).ToString()); } return res; }); if (properties.Count() <= 0) { return null; } StringBuilder sbCondition = new StringBuilder(" "); int index = 0; foreach (var item in properties) { string propName = item.Name; object propValue = item.GetValue(entity, null); if (index != properties.Count() - 1)//不是最后一个属性 { if (item.PropertyType.IsValueType) { sbCondition.AppendFormat("{0}={1} {2} ", propName, propValue, logicOperator); } else if (item.PropertyType.Name.StartsWith("String")) { if (propValue.ToString().IndexOf('\'') != -1) { //SQL注入 throw new FormatException("Detect risk of SQL injection in this entity class"); } if (isFuzzy)//生成模糊查询 { sbCondition.AppendFormat("{0} like '%{1}%' {2} ", propName, propValue, logicOperator); } else { sbCondition.AppendFormat("{0}='{1}' {2} ", propName, propValue, logicOperator); } } } else//最后一个属性 { if (item.PropertyType.IsValueType) { sbCondition.AppendFormat(" {0}={1} ", propName, propValue); } else if (item.PropertyType.Name.StartsWith("String")) { if (propValue.ToString().IndexOf('\'') != -1) { //SQL注入 throw new FormatException("Detect risk of SQL injection in this entity class"); } if (isFuzzy)//生成模糊查询 { sbCondition.AppendFormat(" {0} like '%{1}%' ", propName, propValue); } else { sbCondition.AppendFormat(" {0}='{1}' ", propName, propValue); } } } index++; } return sbCondition.ToString(); } ///
/// 是否存在SQL注入敏感因素(参数为空同样返回True) /// ///
字符串样本 ///
存在:True, 不存在:False
public bool DetectSQLInj(string strProp) { if (string.IsNullOrWhiteSpace(strProp)) { return true;//不存在注入风险 } string lower = strProp.ToLower(); bool res = false; if (strProp.Length < 4) { return false; } if (strProp.Length < 1024)//少量数据用IndexOf { res = lower.IndexOf("exec") != -1 || lower.IndexOf("declare") != -1 || lower.IndexOf("insert") != -1 || lower.IndexOf("delete") != -1 || lower.IndexOf("update") != -1 || lower.IndexOf("select") != -1; } else//大量数据用KMP算法 { res = KMPUtil.GetAllOccurences("exec", strProp).Count > 0 || KMPUtil.GetAllOccurences("declare", strProp).Count > 0 || KMPUtil.GetAllOccurences("insert", strProp).Count > 0 || KMPUtil.GetAllOccurences("delete", strProp).Count > 0 || KMPUtil.GetAllOccurences("update", strProp).Count > 0 || KMPUtil.GetAllOccurences("select", strProp).Count > 0; } if (res) { throw new FormatException("Detect risk of SQL injection in this entity class"); } return res; } ///
/// 比较不依赖其他实体的实体对象是否相等 /// ///
实体类型
///
实体类A ///
实体类B ///
是否相等
public bool EntityEquals
(T entityA, T entityB) where T : new() { if (entityA == null || entityB == null) { return false; } var entA = GetObjProperties(entityA); var entB = GetObjProperties(entityB); if (entA.Count != entB.Count || entA.Count == 0 || entB.Count == 0) { return false; } foreach (var para in entA) { string nameA = para.Key; object valueA = para.Value; if (!entB.Keys.Contains(nameA)) { return false; } object valueB = entB[nameA]; if (valueA == null || valueB == null) { if (valueA == null && valueB == null) { continue; } else { return false; } } else if (valueA.ToString() != valueB.ToString()) { return false; } } return true; } }}
View Code

 

3.1.2 KMP算法工具类

namespace OpenTCM.Utils{    public sealed class KMPUtil    {        private KMPUtil() { }        ///         /// Finds all the occurences a pattern in a a string        ///         /// The pattern to search for        /// The target string to search for        /// 
/// Return an Arraylist containing the indexs where the /// patternn occured ///
public static List
GetAllOccurences(string pattern, string targetString) { return GetOccurences(pattern, targetString); } ///
/// Finds all the occurences a pattern in a string in reverse order /// ///
The pattern to search for ///
/// The target string to search for. This string is actually reversed /// ///
/// Return an Arraylist containing the indexs where the /// patternn occured ///
public static List
GetOccurencesForReverseString(string pattern, string targetString) { char[] array = pattern.ToCharArray(); Array.Reverse(array); return GetOccurences(new string(array), targetString); } ///
/// Finds all the occurences a pattern in a a string /// ///
The pattern to search for ///
The target string to search for ///
/// Return an Arraylist containing the indexs where the /// patternn occured ///
private static List
GetOccurences(string pattern, string targetString) { List
result; int[] transitionArray; char[] charArray; char[] patternArray; charArray = targetString.ToLower().ToCharArray(); patternArray = pattern.ToLower().ToCharArray(); result = new List
(); PrefixArray prefixArray = new PrefixArray(pattern); transitionArray = prefixArray.TransitionArray; //Keeps track of the pattern index int k = 0; for (int i = 0; i < charArray.Length; i++) { //If there is a match if (charArray[i] == patternArray[k]) { //Move the pattern index by one k++; } else { //There is a mismatch..so move the pattern //The amount to move through the pattern int prefix = transitionArray[k]; //if the current char does not match //the char in the pattern that concides //when moved then shift the pattern entirley, so //we dont make a unnecssary comparision if (prefix + 1 > patternArray.Length && charArray[i] != patternArray[prefix + 1]) { k = 0; } else { k = prefix; } } //A complet match, if kis //equal to pattern length if (k == patternArray.Length) { //Add it to our result result.Add(i - (patternArray.Length - 1)); //Set k as if the next character is a mismatch //therefore we dont mis out any other containing //pattern k = transitionArray[k - 1]; } } return result; } } public class PrefixArray { ///
/// The pattern to compute the /// array /// private string pattern; private int[] hArray; ///
/// Constructs a prefix array /// ///
/// The to be used to construct /// the prefix array public PrefixArray(string pattern) { if (pattern == null || pattern.Length == 0) { throw new ArgumentException ("The pattern may not be null or 0 lenght", "pattern"); } this.pattern = pattern; hArray = new int[pattern.Length]; ComputeHArray(); } ///
/// Computes the prefix array /// private void ComputeHArray() { /*Array to keep track of the sub string in each iteration*/ char[] temp = null; //An array containing the characters of the string char[] patternArray = pattern.ToCharArray(); //The first character in the string... //At this point the patern length is validated to be atleast 1 char firstChar = patternArray[0]; //This defaults to 0 hArray[0] = 0; for (int i = 1; i < pattern.Length; i++) { temp = SubCharArray(i, patternArray); hArray[i] = GetPrefixLegth(temp, firstChar); } } private static int GetPrefixLegth(char[] array, char charToMatch) { for (int i = 2; i < array.Length; i++) { //if it is a match if (array[i] == charToMatch) { if (IsSuffixExist(i, array)) { //Return on the first prefix which is the largest return array.Length - i; } } } return 0; } ///
/// Tests whether a suffix exists from the specified index /// ///
/// The index of the char[] to start looking /// for the prefix /// ///
The source array ///
/// A bool; true if a prefix exist at the /// specified pos
private static bool IsSuffixExist(int index, char[] array) { //Keep track of the prefix index int k = 0; for (int i = index; i < array.Length; i++) { //A mismatch so return if (array[i] != array[k]) { return false; } k++; } return true; } ///
/// Creates a sub char[] from the source array /// ///
/// The end index to until which /// the copying should occur ///
The source array ///
A sub array
private static char[] SubCharArray(int endIndex, char[] array) { char[] targetArray = new char[endIndex + 1]; for (int i = 0; i <= endIndex; i++) { targetArray[i] = array[i]; } return targetArray; } ///
/// Gets the transition array /// public int[] TransitionArray { get { return hArray; } } }}
View Code

 

3.1.3 对象克隆工具类

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Xml.Serialization;using System.IO;using System.Runtime.Serialization.Formatters.Binary;using System.Runtime.Serialization;namespace OpenTCM.Utils{    public class ObjectCloneUtil    {        ///         /// 深度克隆对象        ///         /// 
待克隆类型
/// 待克隆对象 ///
新对象
public static T Clone
(T obj) { T ret = default(T); if (obj != null) { XmlSerializer cloner = new XmlSerializer(typeof(T)); MemoryStream stream = new MemoryStream(); cloner.Serialize(stream, obj); stream.Seek(0, SeekOrigin.Begin); ret = (T)cloner.Deserialize(stream); } return ret; } ///
/// 克隆对象 /// ///
待克隆对象 ///
新对象
public static object CloneObject(object obj) { using (MemoryStream memStream = new MemoryStream()) { BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); binaryFormatter.Serialize(memStream, obj); memStream.Seek(0, SeekOrigin.Begin); return binaryFormatter.Deserialize(memStream); } } }}
View Code

 

3.2 实体模块

实体模块负责映射数据库数据表结构,每张表对应一个实体类。

注意:数据库各字段建议仅为字符串类型或数值类型,日期类型建议使用Int 64数据类型存储(格式为:yyyyMMddHHmmssfff),这样的设计有助于优化数据查询速度。本文采用的示例仅支持上述类型,读者可根据自身实际情况采纳本文思想,而修改其具体实现方案。

注意:实体类存在一对一、一对多、多对多等关系,但本文中实体类仅描述原始的数据库表结构,实体类之间不存在依赖关系。数据关联通过程序逻辑判断。(目的在于降低耦合,避免循环依赖)。

实体模块用于存放各数据库表映射的实体类,实体模块中为各实体类提供了统一管理接口。命名为I<ProjectName>Entity。

本示例模拟为中医院药材提供进销存管理功能,项目名为OpenTCM,实体模块各类关系如下:

IOpenTCMEntity接口仅用于规范实体类,并对实体类进行统一标记。该接口必须遵循如下约定:

  1. 必须为泛型接口。
  2. 泛型类型必须为其自身。
  3. 泛型类型通过where约束为class和new(),指定实体类必须是类类型,并具有无参的个构造函数。
  4. 该接口可以不声明任何函数或属性。

这样可以约束实体类,并通过统一实现某接口(如IEquatable<T>)实现对每个实体类的修改。

对实体类使用接口约束,可以便于其他模块对实体模块的抽象引用。即:不引用具体实体,而引用接口,这主要用于声明泛型类型,详情参见后文。

其中各部分代码如下:

3.2.1 IOpenTCMEntity

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace OpenTCM.DataEntity{    ///     /// 实体类实现接口    /// 实体类必须仅包含数值类型和字符串类型    ///     /// 
实体对象类型
public interface IOpenTCMEntity
: IEquatable
where T : class, IOpenTCMEntity
, new() { }}
View Code

 

3.2.2 Herb(草药)

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace OpenTCM.DataEntity{    ///     /// 中草药药材    /// TODO:    /// https://so.gushiwen.org/guwen/book_12.aspx    ///     [Serializable]    public class Herb : IOpenTCMEntity
{ public Herb() { } ///
/// 主键ID /// public string ID { get; set; } ///
/// 原料名,不重复 /// public string HerbName { get; set; } ///
/// 释名『多个资料用JSON表示』 /// public string GenericName { get; set; } ///
/// 外键,HerbsSupplier /// public string HerbsSupplierID { get; set; } ///
/// 参考资料『多个资料用JSON表示』 /// public string TcmReferences { get; set; } ///
/// 性味『多个资料用JSON表示』 /// public string MaterialOdour { get; set; } ///
/// 适用症『多个资料用JSON表示』 /// public string ApplicableSymptoms { get; set; } ///
/// 附方『多个资料用JSON表示』 /// public string AncillaryPrescription { get; set; } ///
/// 药材编号 /// public string PackingNo { get; set; } ///
/// 最后一次修改时间 /// public long? LastModifiedTime { get; set; } ///
/// 本记录状态 /// public int? RecordStatus { get; set; } ///
/// 乐观锁版本号 /// public int? DataVersion { get; set; } public bool Equals(Herb other) { if (other == null) { return false; } if (other == this) { return true; } bool res = other.ID == ID && other.HerbName == HerbName && other.GenericName == GenericName && other.HerbsSupplierID == HerbsSupplierID && other.TcmReferences == TcmReferences && other.MaterialOdour == MaterialOdour && other.ApplicableSymptoms == ApplicableSymptoms && other.AncillaryPrescription == AncillaryPrescription && other.PackingNo == PackingNo && other.LastModifiedTime == LastModifiedTime && other.RecordStatus == RecordStatus && other.DataVersion == DataVersion; return res; } }}
View Code

  

3.3 数据访问接口模块

数据访问接口模块声明了对各数据库表的访问操作,本接口不提供任何实现方式,仅声明数据库CRUD相关函数。本模块包含一个基础泛型接口,该泛型类型为实体类接口即IOpenTCMEntity。代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.DataEntity;namespace OpenTCM.IDataOperate{    ///     /// 提供基本的数据增删改查声明    ///     /// 
数据库实体类
public interface IBaseDAO
where E : class, IOpenTCMEntity
, new() { ///
/// 是否存在ID所对应的记录 /// ///
///
bool Exists(string id); ///
/// 是否存在参数对象所描述的记录(属性为空表示该属性不作为查询条件) /// ///
///
bool Exists(E condition); ///
/// 将参数描述的实体对象添加到数据库中 /// ///
///
bool AddEntity(E po); ///
/// 将参数描述的实体对象集合添加到数据库中 /// ///
///
int AddEntity(List
pos); ///
/// 根据ID从数据库中删除实体 /// ///
///
bool DelEntity(string id); ///
/// 依据参数描述的数据库记录更新数据库, /// 本操作需要先从数据库获取具体的对象。 /// ///
///
bool UpdateEntity(E po); ///
/// 依据参数描述的对象为条件,获取记录数。 /// ///
///
long GetCount(E condition); ///
/// 获取总记录数 /// ///
long GetCount(); ///
/// 获取所有实体对象集合 /// ///
List
QueryEntity(); ///
/// 获取分页对象集合 /// ///
///
///
List
QueryEntity(int beg, int len); ///
/// 根据实体条件分页查询 /// ///
条件实体对象 ///
开始位置 ///
数据条数 ///
List
QueryEntity(E condition, int beg, int len); ///
/// 根据ID获取一个实体对象 /// ///
主键ID字段 ///
E QueryEntity(string id); ///
/// 依据条件获取实体集合 /// ///
条件实体 ///
List
QueryEntity(E condition); ///
/// 根据条模糊查询 /// ///
条件实体 ///
List
FuzzyQuery(E condition); List
FuzzyQuery(E condition, int beg, int len); }}
View Code

 

数据库各表对应的操作接口均继承自该基础接口,以实现高级抽象,从而达到代码复用性目的。

数据库Herb表对应的操作接口代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.DataEntity;namespace OpenTCM.IDataOperate{    public interface IHerbDAO : IBaseDAO
{ }}
View Code

 

通过以上代码,IHerbDAO实现自IBaseDAO,并传递Herb实体类作为泛型参数。通过上述代码分析可理解IBaseDAO<E>的高级抽象初衷。

 

3.4 业务接口模块

 业务接口模块用于存放包括:数据库访问业务和其他自定义业务,本文仅描述数据操作业务内容,其他内容扩展方式会在本章进行说明。

业务接口模块使用一个高级抽象的基础接口:IBaseBO,业务访问接口模块依赖于数据库访问接口和实体数据接口,所以本基础接口需要两个泛型参数:IOpenTCMEntity和IBaseDAO,接口代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.IDataOperate;using OpenTCM.DataEntity;namespace OpenTCM.IDataBiz{    ///     /// 业务基础接口    ///     /// 
实体类
///
持久化类
public interface IBaseBO
where E : class, IOpenTCMEntity
, new() where DAO : IBaseDAO
{ ///
/// 是否存在ID所对饮的记录 /// ///
///
bool Exists(string id); ///
/// 是否存在参数对象所描述的记录(属性为空表示该属性不作为查询条件) /// ///
///
bool Exists(E condition); ///
/// 将参数描述的实体对象添加到数据库中 /// ///
///
bool AddEntity(E po); ///
/// 将参数描述的实体对象集合添加到数据库中 /// ///
///
int AddEntity(List
pos); ///
/// 根据ID从数据库中删除实体 /// ///
///
bool DelEntity(string id); ///
/// 依据参数描述的数据库记录更新数据库, /// 本操作需要先从数据库获取具体的对象。 /// ///
///
bool UpdateEntity(E po); ///
/// 依据参数描述的对象为条件,获取记录数。 /// ///
///
long GetCount(E condition); ///
/// 获取总记录数 /// ///
long GetCount(); ///
/// 获取所有实体对象集合 /// ///
List
QueryEntity(); ///
/// 获取分页对象集合 /// ///
///
///
List
QueryEntity(int beg, int len); ///
/// 根据ID获取一个实体对象 /// ///
///
E QueryEntity(string id); ///
/// 依据条件获取实体集合 /// ///
///
List
QueryEntity(E condition); ///
/// 按实体条件查询并分页 /// ///
实体条件 ///
起始位置 ///
每页数据量 ///
List
QueryEntity(E condition, int beg, int len); ///
/// 模糊查询 /// ///
///
List
FuzzyQuery(E condition); ///
/// 根据实体条件模糊查询,并分页 /// ///
实体条件 ///
起始位置 ///
每页数据量 ///
List
FuzzyQuery(E condition, int beg, int len); }}
View Code

 

其他数据库表对应的业务接口均实现自该基础接口,Herbs表对应业务接口代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.DataEntity;using OpenTCM.IDataOperate;namespace OpenTCM.IDataBiz{    public interface IHerbBO : IBaseBO
{ }}
View Code

 

通过上述代码,子接口通过传递具体类型泛型参数,实现对父类的进一步具象化。

 

3.5 数据库访问实现模块

本节描述数据库访问实现模块,是对数据库访问接口的终极具象化。通过上文图中说明可知,数据库访问模块仅使用实体模块和工具模块。

注意:本节描述的数据库访问实现模块,使用EF6作为数据库访问中间层,但考虑到EF6的内部逻辑,本文仅用EF6作为中间层,但尽量小规模采用EF6的ORM特性,即能用SQL的地方就用SQL,适合用ORM的地方再用ORM。

EF6需要用到数据库上下文,所以本模块定义一个OpenTCMContext作为数据上下文类。代码如下:

using System;using System.Collections.Generic;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Data.SQLite;using System.IO;using System.Linq;using System.Text;using System.Threading.Tasks;using OpenTCM.DataEntity;using System.Data.SQLite.EF6;using System.Data.SQLite.Linq;using System.Data.SQLite.Generic;using System.ComponentModel.Composition;namespace OpenTCM.DataOperate{    public class OpenTCMContext : DbContext    {        public OpenTCMContext()            : base("OpenTCM")        {        }        public DbSet
HerbDataSet { get; set; } }}
View Code

 

数据库访问类与数据库访问接口存在实现关系。但由于数据库操作无外乎增删改查等固定操作,针对每张表都需要重复的代码,这样不利于集中管理。鉴于此,本规范描述了一种尽可能集中化的项目架构规范。基本结构如下:

从上图可了解到,IBaseDAO定义了所有数据库操作的动作规范,即各类型增删改查动作。

IHerbDAO接口继承自接口IBaseDAO,并以Herb实体类作为其父接口IBaseDAO的泛型参数,以此确定IHerbDAO为针对Herbs表的操作接口。

而ACommonDAO提供了IBaseDAO描述的抽象函数的通用的默认实现方式(通过抽象的TableName标识不同数据库操作SQL,由EF进行封装,以抽象的EntityDataSet提供访问)。

HerbDAO实现类继承自ACommonDAO——为继承其默认函数实现;其实现自IHerbDAO,以作为其自身的导出标记接口(参阅MEF的Export概念)。

IOpenTCMEntity接口全程作为抽象层泛型参数声明占位,其实现类以Herb为例,作为抽象层次向实现层次过度的泛型参数(如IHerbDAO)。

注意:上述数据库访问的架构方式在功能上可以仅用ACommonDAO扮演接口角色,但为减少对具体实现的完全依赖(ACommonDAO有太多具体的默认实现),仍要求HerbDAO继承自没有任何函数实现的IHerbDAO接口。

根据以上类图,代码示例如下:

3.5.1 ACommonDAO代码示例

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.IDataOperate;using OpenTCM.DataEntity;using System.ComponentModel.Composition;using System.Data.Entity;using OpenTCM.Utils;using System.ComponentModel.Composition.Hosting;using System.Reflection;namespace OpenTCM.DataOperate{    ///     /// 通用数据库访问抽象类    /// 本类提供抽象且通用的数据库访问功能    ///     /// 
实体类
public abstract class ACommonDAO
: IBaseDAO
where Entity : class, IOpenTCMEntity
, new() { public ACommonDAO() { Utils = new EntityObjUtils(); Context = new OpenTCMContext(); } protected readonly EntityObjUtils Utils;//工具对象 protected readonly OpenTCMContext Context;//EF6数据上下文 ///
/// 数据库表名(用于生成SQL) /// protected abstract string TableName { get; set; } ///
/// 该数据库访问对象对应的数据库表上下文数据集 /// protected abstract DbSet
EntityDataSet { get; set; } ///
/// 数据库是否存在ID所对应记录 /// ///
///
public bool Exists(string id) { if (Utils.DetectSQLInj(id)) { //TODO: Log the sql attack record return false; } string sql = string.Format("select count(1) from {0} where ID = '{1}'", TableName, id); long count = Context.Database.SqlQuery
(sql).FirstOrDefault(); return count > 0; } ///
/// 数据库是否存在实体所描述记录 /// ///
///
public bool Exists(Entity condition) { string strWhere = Utils.EntityToSqlCondition
(condition); if (string.IsNullOrWhiteSpace(strWhere)) { return false; } string sql = string.Format("select count(1) from {0} where {1}", TableName, strWhere); long count = Context.Database.SqlQuery
(sql).FirstOrDefault(); return count > 0; } ///
/// 将实体对象添加到数据库 /// ///
///
public bool AddEntity(Entity po) { EntityDataSet.Add(po); int res = Context.SaveChanges(); return res > 0; } ///
/// 将实体集合添加到数据库 /// ///
///
public int AddEntity(List
pos) { EntityDataSet.AddRange(pos); int res = Context.SaveChanges(); return res; } ///
/// 从数据库中删除ID所对应数据 /// ///
///
public bool DelEntity(string id) { if (Utils.DetectSQLInj(id)) { //TODO: Log the sql attack record return false; } string sql = string.Format("delete from {0} where ID ='{1}'", TableName, id); int res = Context.Database.ExecuteSqlCommand(sql); return res > 0; } ///
/// 按实体对象的描述更新数据库(实体对象必须有真实ID) /// ///
///
public bool UpdateEntity(Entity po) { EntityDataSet.Attach(po); Context.Entry(po).State = EntityState.Modified; int res = Context.SaveChanges(); return res > 0; } ///
/// 根据实体对象的描述,查找数据库中对应的数据记录数 /// ///
///
public long GetCount(Entity condition) { string strWhere = Utils.EntityToSqlCondition
(condition); if (Utils.DetectSQLInj(strWhere)) { //TODO: Log the sql attack record return 0; } string sql = string.Format("select count(1) from {0} where {1}", TableName, strWhere); long count = Context.Database.SqlQuery
(sql).FirstOrDefault(); return count; } ///
/// 查找本表所有数据记录数 /// ///
public long GetCount() { string sql = "select count(1) from " + TableName; long count = Context.Database.SqlQuery
(sql).FirstOrDefault(); return count; } ///
/// 以实体对象方式获取本表所有数据 /// ///
public List
QueryEntity() { var ls = EntityDataSet.ToList(); return ls; } ///
/// 根据开始位置和数量获取分页数据 /// ///
///
///
public List
QueryEntity(int beg, int len) { var subLs = EntityDataSet.ToList().Skip(beg).Take(len); return subLs.ToList(); } ///
/// 根据ID值获取唯一的实体对象 /// ///
///
public Entity QueryEntity(string id) { if (Utils.DetectSQLInj(id)) { //TODO: Log the sql attack record return null; } var ls = EntityDataSet.SqlQuery(string.Format("select * from {0} where ID='{1}'", TableName, id)); return ls.FirstOrDefault(); } ///
/// 根据实体对象的描述,查找实体数据列表 /// ///
///
public List
QueryEntity(Entity condition) { string strWhere = Utils.EntityToSqlCondition
(condition); if (Utils.DetectSQLInj(strWhere)) { //TODO: Log the sql attack record return new List
(); } string sql = string.Format("select * from {0} where {1}", TableName, strWhere); var res = EntityDataSet.SqlQuery(sql); return res.ToList(); } ///
/// 根据实体对象的描述,直行模糊查询 /// ///
///
public List
FuzzyQuery(Entity condition) { string strWhere = Utils.EntityToSqlCondition
(condition); if (Utils.DetectSQLInj(strWhere)) { //TODO: Log the sql attack record return new List
(); } string sql = string.Format("select * from {0} where {1}", TableName, strWhere); var res = EntityDataSet.SqlQuery(sql); return res.ToList(); } ///
/// 根据实体查询数据并分页 /// ///
实体条件 ///
起始位置 ///
每页容量 ///
public List
QueryEntity(Entity condition, int beg, int len) { string strWhere = Utils.EntityToSqlCondition
(condition); if (Utils.DetectSQLInj(strWhere)) { //TODO: Log the sql attack record return new List
(); } string sql = string.Format("select * from {0} where {1}", TableName, strWhere); var res = EntityDataSet.SqlQuery(sql).ToList().Skip(beg).Take(len); return res.ToList(); throw new NotImplementedException(); } ///
/// 根据实体模糊查询数据并分页 /// ///
实体条件 ///
起始位置 ///
每行数据容量 ///
public List
FuzzyQuery(Entity condition, int beg, int len) { string strWhere = Utils.EntityToSqlCondition
(condition); if (Utils.DetectSQLInj(strWhere)) { //TODO: Log the sql attack record return new List
(); } string sql = string.Format("select * from {0} where {1}", TableName, strWhere); var res = EntityDataSet.SqlQuery(sql).Skip(beg).Take(len); return res.ToList(); } }}
View Code

 

3.5.2 HerbDAO代码示例

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.IDataOperate;using OpenTCM.DataEntity;using System.ComponentModel.Composition;using System.Data.Entity;using OpenTCM.Utils;using System.ComponentModel.Composition.Hosting;using System.Reflection;namespace OpenTCM.DataOperate{    using Entity = Herb;//实体对象类型别名    [Export(typeof(IHerbDAO))]    public class HerbDAO :ACommonDAO
, IHerbDAO { public HerbDAO() { TableName = "Herbs"; } protected override string TableName { get; set; } protected override DbSet
EntityDataSet { get { return Context.HerbDataSet; } set { Context.HerbDataSet = value; } } }}
View Code

 

3.6 业务实现模块 

业务实现模块目的在于对数据库访问的进一步封装,但根据依赖倒转原则,业务实现模块不直接依赖于数据访问实现模块,而依赖于数据访问接口。通过MEF将依赖注入到业务实现模块中。业务实现模块中各组件与其他模块依赖如下:

上图与数据访问实现模块基本结构与设计思路类似。限于篇幅将UML类图调整一下。

详细代码如下:

3.6.1 ACommonBO

ACommonBO类封装了基本的数据库接口调用的通用实现。其具有两个泛型参数Entity和IDAO,Entity是IOpenTCMEntity<Entity>的实现类,即实体类;IDAO是IBaseDAO<Entity>,即针对某实体的数据库操作接口。并具有一个Protected修饰的IDAO类型的DbOperator属性,用于子类实现为具体的实体(表)访问接口。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using OpenTCM.DataEntity;using OpenTCM.IDataBiz;using OpenTCM.IDataOperate;using System.ComponentModel.Composition;using System.ComponentModel.Composition.Hosting;using System.Reflection;namespace OpenTCM.DataBiz{    public abstract class ACommonBO
: IBaseBO
where Entity : class, IOpenTCMEntity
, new() where IDAO : IBaseDAO
{ public ACommonBO() { var catalog = new DirectoryCatalog("./"); var container = new CompositionContainer(catalog); container.ComposeParts(this); } ///
/// 数据库操作接口(依赖注入,不需要手动实现) /// protected abstract IDAO DbOperator { get; set; } public virtual bool Exists(string id) { return DbOperator.Exists(id); } public virtual bool Exists(Entity condition) { return DbOperator.Exists(condition); } public virtual bool AddEntity(Entity po) { return DbOperator.AddEntity(po); } public virtual int AddEntity(List
pos) { return DbOperator.AddEntity(pos); } public virtual bool DelEntity(string id) { return DbOperator.DelEntity(id); } public virtual bool UpdateEntity(Entity po) { return DbOperator.UpdateEntity(po); } public virtual long GetCount(Entity condition) { return DbOperator.GetCount(condition); } public virtual long GetCount() { return DbOperator.GetCount(); } public virtual List
QueryEntity() { return DbOperator.QueryEntity(); } public virtual List
QueryEntity(int beg, int len) { return DbOperator.QueryEntity(beg, len); } public virtual Entity QueryEntity(string id) { return DbOperator.QueryEntity(id); } public virtual List
QueryEntity(Entity condition) { return DbOperator.QueryEntity(condition); } public virtual List
FuzzyQuery(Entity condition) { return DbOperator.FuzzyQuery(condition); } public virtual List
QueryEntity(Entity condition, int beg, int len) { return DbOperator.QueryEntity(condition, beg, len); } public virtual List
FuzzyQuery(Entity condition, int beg, int len) { return DbOperator.FuzzyQuery(condition, beg, len); } }}
View Code

 

3.6.2 HerbBO

HerbBO继承自AcommonBO抽象类,以继承ACommonBO的默认接口函数实现,当业务需要其他的实现方式,可以自行重写父类函数;实现自IHerbBO接口,用以标记导出接口。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ComponentModel.Composition;using System.ComponentModel.Composition.Hosting;using OpenTCM.DataEntity;using OpenTCM.IDataBiz;using OpenTCM.IDataOperate;namespace OpenTCM.DataBiz{    [Export(typeof(IHerbBO))]    public class HerbBO : ACommonBO
, IHerbBO { public HerbBO() : base() { } [Import] protected override IHerbDAO DbOperator { get; set; } }}
View Code

 

 

4 总结

本文所描述的基于C#语言的轻量级可扩展应用程序架构,在抽象层积累了大量逻辑与依赖关系,在具象层积累大量互相独立的数据与业务逻辑,用以极力遵循DIP原则,但笔者并无意为某种价值体系摇旗呐喊,仅对长期以来项目经验的粗陋积累,做一次总结和提炼,前路漫漫,所谓路漫漫其修远兮,吾将上下而求索。大道至简,笃笃前行。

 

转载于:https://www.cnblogs.com/sam-snow-v/p/10456545.html

你可能感兴趣的文章
检查点
查看>>
转:QQ群年终工作总结
查看>>
图文详解linux如何搭建lamp服务环境
查看>>
Java 7中报错
查看>>
获取外网IP
查看>>
关于新标签dialog
查看>>
最大正方形
查看>>
万径人踪灭(FFT+manacher)
查看>>
技术规格说明书
查看>>
图写成一个类(2)
查看>>
Segmentation fault (core dumped) 错误的一种解决场景
查看>>
hdu1150 Machine Schedule (匈牙利算法模版)
查看>>
惠普 hpssacli 工具使用
查看>>
记录常用的git命令
查看>>
关于linux的/var/www/html
查看>>
Maven 环境搭建
查看>>
UIPickerView的使用
查看>>
报表属性定义
查看>>
Solr综合案例深入练习
查看>>
关于strcpy的实现.
查看>>