开发需求:
同一套系统,需要根据客户需求采用不同的数据库。
一般实现:
开发多套系统,每套系统对应一个数据库。
缺点:
需要同时维护多套系统,难度大。
解决:
使用抽象工厂模式,同一套系统里面开发多个数据库的DAL,根据客户需求来确定使用哪个数据库。
抽象工厂设计模式
基于抽象工厂模式设计DAL方案
项目各层之间的引用
代码示例
根据以上,建立各层,各层引用参考上面:
从底层开始构建代码:
编写不同数据库的DAL,我这里仅仅是文件夹形式将Access、SQLServer数据DAL模块分类,实际开发可以使用dll等分层。
仅做了查询Service
IDAL接口定义
DAL需要基于接口开发,所以先定义接口,我们这里仅使用到了两个接口:
using Models; namespace IDAL { public interface IClassService { List<StudentClass> GetAllClasses(); } } namespace IDAL { public interface IStudentService { List<Student> GetStudentsByClassId(int classId); } }
基于接口开发DAL:
Access数据库
AccessHelper:
1 using System.Data; 2 using System.Data.OleDb; 3 using System.Configuration; 4 5 namespace DAL.Access 6 { 7 class AccessHelper 8 { 9 private static readonly string connString = ConfigurationManager.ConnectionStrings["AccessConnString"].ToString(); 10 public static OleDbDataReader GetReader(string sql) 11 { 12 OleDbConnection conn = new OleDbConnection(connString); 13 OleDbCommand cmd = new OleDbCommand(sql, conn); 14 try 15 { 16 conn.Open(); 17 return cmd.ExecuteReader(CommandBehavior.CloseConnection); 18 } 19 catch (OleDbException ex) 20 { 21 if (conn.State == ConnectionState.Open) 22 conn.Close(); 23 throw new Exception("应用程序与数据库连接异常:" + ex.Message); 24 } 25 catch (Exception ex) 26 { 27 if (conn.State == ConnectionState.Open) 28 conn.Close(); 29 throw ex; 30 } 31 } 32 } 33 }
ClassService:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using IDAL; 7 using Models; 8 using System.Data; 9 using System.Data.OleDb; 10 11 namespace DAL.Access 12 { 13 class ClassService : IClassService 14 { 15 public List<StudentClass> GetAllClasses() 16 { 17 string sql = "select * from StudentClass"; 18 OleDbDataReader objReader = AccessHelper.GetReader(sql); 19 List<StudentClass> objStuClass = new List<StudentClass>(); 20 while (objReader.Read()) 21 { 22 objStuClass.Add(new StudentClass 23 { 24 ClassId = Convert.ToInt32(objReader["ClassId"]), 25 ClassName = objReader["ClassName"].ToString() 26 }); 27 } 28 objReader.Close(); 29 return objStuClass; 30 } 31 } 32 }
StudentService:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using IDAL; 7 using System.Data; 8 using System.Data.OleDb; 9 using Models; 10 11 namespace DAL.Access 12 { 13 public class StudentService : IStudentService 14 { 15 public List<Student> GetStudentsByClassId(int classId) 16 { 17 StringBuilder sqlBuilder = new StringBuilder("select StudentId,StudentName,Gender,StudentIdNo,Birthday,PhoneNumber,ClassName,Students.ClassId from Students "); 18 sqlBuilder.Append("inner join StudentClass on StudentClass.ClassId=Students.ClassId "); 19 if (classId > 0) 20 sqlBuilder.Append($"where Students.ClassId={classId} "); 21 List<Student> stuList = new List<Student>(); 22 OleDbDataReader objReader = null; 23 try 24 { 25 objReader = AccessHelper.GetReader(sqlBuilder.ToString()); 26 while (objReader.Read()) 27 { 28 stuList.Add(new Student 29 { 30 StudentId = Convert.ToInt32(objReader["StudentId"]), 31 StudentName = objReader["StudentName"].ToString(), 32 Gender = objReader["Gender"].ToString(), 33 StudentIdNo = objReader["StudentIdNo"].ToString(), 34 Birthday = Convert.ToDateTime(objReader["Birthday"]), 35 PhoneNumber = objReader["PhoneNumber"].ToString(), 36 ClassName = objReader["ClassName"].ToString(), 37 ClassId = Convert.ToInt32(objReader["ClassId"]) 38 }); 39 } 40 return stuList; 41 } 42 catch (Exception ex) 43 { 44 throw ex; 45 } 46 finally 47 { 48 objReader.Close(); 49 } 50 } 51 } 52 }
SQLServer数据库
SQLHelper:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Configuration; 7 using System.Data; 8 using System.Data.SqlClient; 9 10 namespace DAL.SQLServer 11 { 12 class SQLHelper 13 { 14 private static readonly string connString=ConfigurationManager.ConnectionStrings["SqlConnString"].ToString(); 15 16 /// <summary> 17 /// 增、删、改,返回受影响行数 18 /// </summary> 19 /// <param name="sql"></param> 20 /// <returns></returns> 21 public static int Update(string sql) 22 { 23 SqlConnection conn = new SqlConnection(connString); 24 SqlCommand cmd = new SqlCommand(sql, conn); 25 try 26 { 27 conn.Open(); 28 return cmd.ExecuteNonQuery(); 29 } 30 catch (SqlException ex) 31 { 32 if (ex.Number == 547) 33 throw new Exception("该数据已被其它表引用,不能直接删除!"); 34 else 35 throw new Exception("数据库操作异常:" + ex.Message); 36 } 37 catch (Exception ex) 38 { 39 throw ex; 40 } 41 finally 42 { 43 conn.Close(); 44 } 45 } 46 47 48 /// <summary> 49 /// 仅得到一条数据的查询 50 /// </summary> 51 /// <param name="sql"></param> 52 /// <returns></returns> 53 public static object GetSingleResult(string sql) 54 { 55 SqlConnection conn = new SqlConnection(connString); 56 SqlCommand cmd = new SqlCommand(sql, conn); 57 try 58 { 59 conn.Open(); 60 return cmd.ExecuteScalar(); 61 } 62 catch (SqlException ex) 63 { 64 throw new Exception("数据库操作异常:" + ex.Message); 65 } 66 catch (Exception ex) 67 { 68 throw ex; 69 } 70 finally 71 { 72 conn.Close(); 73 } 74 } 75 76 /// <summary> 77 /// 查询得到结果集 78 /// </summary> 79 /// <param name="sql"></param> 80 /// <returns></returns> 81 public static SqlDataReader GetReader(string sql) 82 { 83 SqlConnection conn = new SqlConnection(connString); 84 SqlCommand cmd = new SqlCommand(sql, conn); 85 try 86 { 87 conn.Open(); 88 return cmd.ExecuteReader(CommandBehavior.CloseConnection); 89 } 90 catch (SqlException ex) 91 { 92 if (conn.State == ConnectionState.Open) 93 conn.Close(); 94 throw new Exception("应用程序与数据库连接异常:" + ex.Message); 95 } 96 catch (Exception ex) 97 { 98 if (conn.State == ConnectionState.Open) 99 conn.Close(); 100 throw ex; 101 } 102 } 103 104 /// <summary> 105 /// 查询得到数据集,前台需要多次筛选数据时使用 106 /// </summary> 107 /// <param name="sql"></param> 108 /// <returns></returns> 109 public static DataSet GetDataSet(string sql) 110 { 111 SqlConnection conn = new SqlConnection(connString); 112 SqlCommand cmd = new SqlCommand(sql, conn); 113 SqlDataAdapter da = new SqlDataAdapter(cmd); 114 DataSet ds = new DataSet(); 115 try 116 { 117 conn.Open(); 118 da.Fill(ds); 119 return ds; 120 } 121 catch (SqlException ex) 122 { 123 throw new Exception("数据库操作异常:" + ex.Message); 124 } 125 catch (Exception ex) 126 { 127 throw ex; 128 } 129 finally 130 { 131 conn.Close(); 132 } 133 } 134 } 135 }
ClassService:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using IDAL; 7 using Models; 8 using System.Data; 9 using System.Data.SqlClient; 10 11 namespace DAL.SQLServer 12 { 13 public class ClassService : IClassService 14 { 15 public List<StudentClass> GetAllClasses() 16 { 17 string sql = "select * from StudentClass"; 18 SqlDataReader objReader = SQLHelper.GetReader(sql); 19 List<StudentClass> objStuClass = new List<StudentClass>(); 20 while (objReader.Read()) 21 { 22 objStuClass.Add(new StudentClass 23 { 24 ClassId = Convert.ToInt32(objReader["ClassId"]), 25 ClassName = objReader["ClassName"].ToString() 26 }); 27 } 28 objReader.Close(); 29 return objStuClass; 30 } 31 } 32 }
StudentService:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using IDAL; 7 using Models; 8 using System.Data; 9 using System.Data.SqlClient; 10 11 namespace DAL.SQLServer 12 { 13 public class StudentService : IStudentService 14 { 15 public List<Student> GetStudentsByClassId(int classId) 16 { 17 StringBuilder sqlBuilder = new StringBuilder("select StudentId,StudentName,Gender,StudentIdNo,Birthday,PhoneNumber,ClassName,Students.ClassId from Students "); 18 sqlBuilder.Append("inner join StudentClass on StudentClass.ClassId=Students.ClassId "); 19 if (classId >= 0)//当==-1,说明是选的全部班级,则不需要筛选 20 sqlBuilder.Append($"where Students.ClassId={classId} "); 21 List<Student> stuList = new List<Student>(); 22 SqlDataReader objReader = null; 23 try 24 { 25 objReader = SQLHelper.GetReader(sqlBuilder.ToString()); 26 while (objReader.Read()) 27 { 28 stuList.Add(new Student 29 { 30 StudentId = Convert.ToInt32(objReader["StudentId"]), 31 StudentName = objReader["StudentName"].ToString(), 32 Gender = objReader["Gender"].ToString(), 33 StudentIdNo = objReader["StudentIdNo"].ToString(), 34 Birthday = Convert.ToDateTime(objReader["Birthday"]), 35 PhoneNumber = objReader["PhoneNumber"].ToString(), 36 ClassName = objReader["ClassName"].ToString(), 37 ClassId = Convert.ToInt32(objReader["ClassId"]), 38 }); 39 } 40 return stuList; 41 } 42 catch (Exception ex) 43 { 44 throw ex; 45 } 46 finally 47 { 48 objReader.Close(); 49 } 50 } 51 } 52 }
App.Config配置
数据库用到了App.Config节点:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <startup> 4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 5 </startup> 6 <connectionStrings> 7 <add name="SqlConnString" connectionString="server=.;DataBase=StudentManageDB;Uid=sa;Pwd=warrenwell"/> 8 <add name="AccessConnString" connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=StudentManageDB.mdb"/> 9 <!--Office版本2007以上使用以上Provider;以下:Microsoft.JET.OLEDB.4.0--> 10 </connectionStrings> 11 <appSettings> 12 <add key="DBType" value="SQLServer"/> 13 <!--<add key="DBType" value="Access"/>--> 14 </appSettings> 15 </configuration>
Models实体类
使用到的实体类
StudentClass:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Models 8 { 9 public class StudentClass 10 { 11 public int ClassId { get; set; } 12 public string ClassName { get; set; } 13 } 14 }
Students:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Models 8 { 9 public class Student 10 { 11 public int StudentId { get; set; } 12 public string StudentName { get; set; } 13 public string Gender { get; set; } 14 public DateTime Birthday { get; set; } 15 public string StudentIdNo { get; set; } 16 public int Age { get; set; } 17 public string CardNo { get; set; } 18 public string PhoneNumber { get; set; } 19 public int ClassId { get; set; } 20 21 public string ClassName { get; set; } 22 } 23 }
DALFactory
DALFactory根据以上App.Config里appSettings节点的key<"DBType">来选择数据库
DALAccess数据库数据访问创建类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Configuration; 7 using IDAL; 8 using System.Reflection; 9 10 namespace DALFactory 11 { 12 public class DataAccess 13 { 14 private static string dbType = "DAL."+ConfigurationManager.AppSettings["DBType"].ToString(); 15 16 public static IDAL.IClassService CreateClassService() 17 { 18 return (IDAL.IClassService)Assembly.Load("DAL").CreateInstance(dbType + ".ClassService"); 19 } 20 21 public static IDAL.IStudentService CreateStudentService() 22 { 23 return (IDAL.IStudentService)Assembly.Load("DAL").CreateInstance(dbType + ".StudentService"); 24 } 25 } 26 }
应用到了反射,里面Load()里面是数据库的命名空间名称,CreateInstance()里面是相应DAL的完全限定名:命名空间名称.相应Serivce名。
BLL
我们把数据访问的内容全部放到BLL里面,从而完全断绝UI与DAL的耦合。
ClassManager:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using DALFactory; 7 using Models; 8 using IDAL; 9 10 namespace BLL 11 { 12 public class ClassManager 13 { 14 IDAL.IClassService objService = DALFactory.DataAccess.CreateClassService(); 15 public List<StudentClass> GetAllClasses() 16 { 17 return objService.GetAllClasses(); 18 } 19 } 20 }
StudentManager:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using DALFactory; 7 using Models; 8 using IDAL; 9 10 namespace BLL 11 { 12 public class StudentManager 13 { 14 IDAL.IStudentService objService = DALFactory.DataAccess.CreateStudentService(); 15 public List<Student> GetStudentsByClassId(int classId) 16 { 17 return objService.GetStudentsByClassId(classId); 18 } 19 } 20 }
UI
仅写了查询班级与学生(UI设计见下面结果展示):
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using BLL; 11 using Models; 12 13 namespace AbstractFactoryDemo 14 { 15 public partial class FrmQuery : Form 16 { 17 public FrmQuery() 18 { 19 InitializeComponent(); 20 21 List<StudentClass> stuClass = new List<StudentClass>() 22 { 23 new StudentClass {ClassId=-1,ClassName="全部班级" } 24 } ; 25 stuClass.AddRange(new ClassManager().GetAllClasses()); 26 27 this.cboStudentClass.DataSource = stuClass; 28 this.cboStudentClass.DisplayMember = "ClassName"; 29 this.cboStudentClass.ValueMember = "ClassId"; 30 31 this.dgvStudentList.AutoGenerateColumns = false; 32 } 33 34 private void btnQuery_Click(object sender, EventArgs e) 35 { 36 this.dgvStudentList.DataSource = new StudentManager().GetStudentsByClassId(Convert.ToInt32(this.cboStudentClass.SelectedValue)); 37 } 38 } 39 }
结果展示
我们可以通过配置App.Config的相应节点来控制访问哪个数据库。
比如,我们先访问SQLServer,就这样:
访问结果:
再去访问Access:
访问结果:
故意将两个数据库的数据格式不一致,比较容易看出来差别。
至此,抽象工厂设计模式完成。
相应源代码+数据库脚本
注意点
有个地方需要注意,关于DAL.dll文件,如果提示不存在,则需要手动到DAL程序集下将该DAL.dll复制到项目下;如果提示“源文件与模块不一致”这样的错误,则表示你的DAL有做修改,重新生成一下DAL,再次重新将dll文件复制过去。这是经验之谈,暂未更好的解决方案,等待指教。