1,什么是三層?
2,為什么使用三層?
3,三層與以往使用的兩層相比有什么不同?它的優勢在哪里?
4,如何學好三層?如何應用三層?
先了解:
1、什么是三層
UI(表現層):主要是指與用戶交互的界面。用於接收用戶輸入的數據和顯示處理后用戶需要的數據。
BLL:(業務邏輯層):UI層和DAL層之間的橋梁。實現業務邏輯。業務邏輯具體包含:驗證、計算、業務規則等等。
DAL:(數據訪問層):與數據庫打交道。主要實現對數據的增、刪、改、查。將存儲在數據庫中的數據提交給業務層,同時將業務層處理的數據保存到數據庫。(當然這些操作都是基於UI層的。用戶的需求反映給界面(UI),UI反映給BLL,BLL反映給DAL,DAL進行數據的操作,操作后再一一返回,直到將用戶所需數據反饋給用戶)
每一層都各負其責,那么該如何將三層聯系起來呢?
1>單項引用(見下圖)
2>這時候實體層(Entity)來了。(注:當然,實體層的作用不止這些)
Entity(實體層):它不屬於三層中的任何一層,但是它是必不可少的一層。
Entity在三層架構中的作用(也叫模型層model):
1,實現面向對象思想中的"封裝";
2,貫穿於三層,在三層之間傳遞數據;
(注:確切的說實體層貫穿於三層之間,來連接三層)
3,對於初學者來說,可以這樣理解:每張數據表對應一個實體,即每個數據表中的字段對應實體中的屬性(注:當然,事實上不是這樣。為什么?1>,可能我們需要的實體在數據表對應的實體中並不存在;2>,我們完全可以將所有數據表中的所有字段都放在一個實體里)
4,每一層(UI—>BLL—>DAL)之間的數據傳遞(單向)是靠變量或實體作為參數來傳遞的,這樣就構造了三層之間的聯系,完成了功能的實現。
但是對於大量的數據來說,用變量做參數有些復雜,因為參數量太多,容易搞混。比如:我要把員工信息傳遞到下層,信息包括:員工號、姓名、年齡、性別、工資....用變量做參數的話,那么我們的方法中的參數就會很多,極有可能在使用時,將參數匹配搞混。這時候,如果用實體做參數,就會很方便,不用考慮參數匹配的問題,用到實體中哪個屬性拿來直接用就可以,很方便。這樣做也提高了效率。(字段轉成對象的屬性)
(注:這里為什么說可以暫時理解為每個數據表對應一個實體??答:大家都知道,我們做系統的目的,是為用戶提供服務,用戶可不關心你的系統后台是怎么工作的,用戶只關心軟件是不是好用,界面是不是符合自己心意。用戶在界面上輕松的增、刪、改、查,那么數據庫中也要有相應的增、刪、改、查,而增刪改查具體操作對象就是數據庫中的數據,說白了就是表中的字段。所以,將每個數據表作為一個實體類,實體類封裝的屬性對應到表中的字段,這樣的話,實體在貫穿於三層之間時,就可以實現增刪改查數據了)
思想來源於生活:
服務員:只管接待客人;
廚師:只管做客人點的菜;
采購員:只管按客人點菜的要求采購食材;
他們各負其職,服務員不用了解廚師如何做菜,不用了解采購員如何采購食材;廚師不用知道服務員接待了哪位客人,不用知道采購員如何采購食材;同樣,采購員不用知道服務員接待了哪位客人,不用知道廚師如何做菜。
他們三者是如何聯系的?
比如:廚師會做:炒茄子、炒雞蛋、炒面——此時構建三個方法( cookEggplant()、cookEgg()、cookNoodle())
顧客直接和服務員打交道,顧客和服務員(UI層)說:我要一個炒茄子,而服務員不負責炒茄子,她就把請求往上遞交,傳遞給廚師(BLL層),廚師需要茄子,就把請求往上遞交,傳遞給采購員(DAL層),采購員從倉庫里取來茄子傳回給廚師,廚師響應cookEggplant()方法,做好炒茄子后,又傳回給服務員,服務員把茄子呈現給顧客。
這樣就完成了一個完整的操作。
在此過程中,茄子作為參數在三層中傳遞,如果顧客點炒雞蛋,則雞蛋作為參數(這是變量做參數)。如果,用戶增加需求,我們還得在方法中添加參數,一個方法添加一個,一個方法設計到三層;何況實際中並不止設計到一個方法的更改。所以,為了解決這個問題,我們可以把茄子、雞蛋、面條作為屬性定義到顧客實體中,一旦顧客增加了炒雞蛋需求,直接把雞蛋屬性拿出來用即可,不用再去考慮去每層的方法中添加參數了,更不用考慮參數的匹配問題。
這樣講,不知道大家是不是可以明白。(待會實例解釋吧)
2,為什么使用三層?
使用三層架構的目的:解耦!!!
同樣拿上面飯店的例子來講:
(1)服務員(UI層)請假——另找服務員;廚師(BLL層)辭職——招聘另一個廚師;采購員(DAL)辭職——招聘另一個采購員;
(2)顧客反映:1>你們店服務態度不好——服務員的問題。開除服務員;
2>你們店菜里有蟲子——廚師的問題。換廚師;
任何一層發生變化都不會影響到另外一層!!!
3,與兩層的區別??
兩層:
(當任何一個地方發生變化時,都需要重新開發整個系統。“多層”放在一層,分工不明確耦合度高——難以適應需求變化,可維護性低、可擴展性低)
三層:
(發生在哪一層的變化,只需更改該層,不需要更改整個系統。層次清晰,分工明確,每層之間耦合度低——提高了效率,適應需求變化,可維護性高,可擴展性高)
綜上:三層架構的
優勢:1,結構清晰、耦合度低,2,可維護性高,可擴展性高;3,利於開發任務同步進行;容易適應需求變化
劣勢:1、降低了系統的性能。這是不言而喻的。如果不采用分層式結構,很多業務可以直接造訪數據庫,以此獲取相應的數據,如今卻必須通過中間層來完成。
2、有時會導致級聯的修改。這種修改尤其體現在自上而下的方向。如果在表示層中需要增加一個功能,為保證其設計符合分層式結構,可能需要在相應的業務邏輯層和數據訪問層中都增加相應的代碼
3、增加了代碼量,增加了工作量
4,三層的具體表現形式??
models:定義用戶實體模型
namespace WeatherStationManager.Models { public class User { int userId; public int UserId { get { return userId; } set { userId = value; } } string userName; public string UserName { get { return userName; } set { userName = value; } } string userPassWord; public string UserPassWord { get { return userPassWord; } set { userPassWord = value; } } bool isDel; public bool IsDel { get { return isDel; } set { isDel = value; } } DateTime addTime; public DateTime AddTime { get { return addTime; } set { addTime = value; } } } }
DAL:唯一直接與數據庫交互的層
1創建數據庫訪問公共類,負責完成 數據庫訪問對象的建立,數據庫連接建立,sql命令的執行方法
namespace WeatherStationManager.DAL { public class DBHelpSQL { private SqlConnection conn;//創建一個sql數據庫打開的連接,參數是連接字符串 /// <summary> /// 數據庫連接對象屬性 /// </summary> /// public SqlConnection Conn//封裝為屬性 { get { // string connectionString = ConfigurationManager.ConnectionStrings["ConnString"].ToString(); string connectionString = "server=.;database=WeatherStation;uid=sa;pwd=sasasa"; if (conn == null) { conn = new SqlConnection(connectionString); conn.Open(); } else if (conn.State == System.Data.ConnectionState.Closed) { conn.Open(); } else if (conn.State == System.Data.ConnectionState.Broken) { conn.Close(); conn.Open(); } return conn; } } /// <summary> /// 關閉數據庫連接 /// </summary> public void CloseDB() { if (conn.State == System.Data.ConnectionState.Open || conn.State == System.Data.ConnectionState.Broken) { conn.Close(); } }
// 創建sqlDataReader對象(每次向數據庫只讀一條)必須調用 SqlCommand 對象的 ExecuteReader 方法,read()方法讀取 ,每次讀一條數據 ,查詢 語句 public SqlDataReader ExecuteReader(string sql, params SqlParameter[] parmaeters) { // SqlConnection conn = new SqlConnection(connctionString); SqlCommand cmd = new SqlCommand(sql, Conn); if (parmaeters != null) { cmd.Parameters.AddRange(parmaeters); } // conn.Open(); return cmd.ExecuteReader(CommandBehavior.CloseConnection);//返回sqlDataReader對象 } /// <summary> /// 執行sql語句 返回數據表 ,查詢 /// </summary> /// <param name="safeSql">sql語句</param> /// <returns>數據表</returns> public DataTable GetDataTable(string safeSql) { //SqlConnection conn = new SqlConnection(connctionString); DataSet ds = new DataSet(); SqlCommand cmd = new SqlCommand(safeSql,Conn);
//SqlDataAdapter是 DataSet和 SQL Server之間的橋接器 SqlDataAdapter da = new SqlDataAdapter(cmd);//SqlCommand是sql命令,執行后通過sqlDataAdapter返回填入DataSet, da.Fill(ds); return ds.Tables[0];//將dataset的第一張表返回也就是datatable ,因為dataset是數據表集合 ,相當於內存中的數據庫 } /// <summary> /// 執行sql語句 返回數據表 , 查詢語句 /// </summary> /// <param name="sql">sql語句</param> /// <param name="values">sql參數列表</param> /// <returns>數據表</returns> public DataTable GetDataTable(string sql, params SqlParameter[] values) { DataSet ds = new DataSet(); SqlCommand cmd = new SqlCommand(sql, Conn); cmd.Parameters.AddRange(values); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); cmd.Parameters.Clear(); return ds.Tables[0]; } /// <summary> /// 執行Command , 執行增刪改 /// </summary> /// <param name="sql">sql語句</param> /// <param name="values">sql參數數組</param> /// <returns></returns> public int ExecuteCommand(string sql, params SqlParameter[] values) { SqlCommand cmd = new SqlCommand(sql, Conn); cmd.Parameters.AddRange(values); int result = cmd.ExecuteNonQuery(); //執行(增刪改)的方法,返回執行命令所影響的行數(return int類型) cmd.Parameters.Clear(); return result; } /// <summary> /// 執行帶sql參數的語句 /// </summary> /// <param name="sql">sql語句</param> /// <param name="values">sql參數列表</param> /// <returns></returns> public int GetScalar(string sql, params SqlParameter[] values) { object obj = null; try { SqlCommand cmd = new SqlCommand(sql, Conn); cmd.Parameters.AddRange(values); obj = cmd.ExecuteScalar();//獲得查詢到的結果集的第一個單元格的值,為obj類型 cmd.Parameters.Clear(); } catch (Exception ex) { throw ex; } finally { CloseDB(); } if (obj == null) return 0; else return Convert.ToInt32(obj); } } }
2增刪改查
using System; using System.Collections.Generic; using System.Linq; using System.Text; using WeatherStationManager.Models; using System.Data.SqlClient; using System.Data; using System.Reflection; namespace WeatherStationManager.DAL { public class UserServices { DBHelpSQL dbHelper = null; //DAL層需要用到數據庫訪問公共類 ,這里建立dbhelp對象 public UserServices() { dbHelper = new DBHelpSQL(); } #region 修改 /// <summary> /// 修改學員信息 /// </summary> /// <param name="model">學員實體</param> /// <returns></returns> public bool ModifyStudent(Models.User model) { StringBuilder sbSql = new StringBuilder("update [User] set ");// user 與系統關鍵字沖突 用[] 括起來 Type modeType = model.GetType();//獲得對象的類型 PropertyInfo[] pros = modeType.GetProperties(); // 得到類型的所有公共屬性 List<SqlParameter> paras = new List<SqlParameter>(); foreach (PropertyInfo pi in pros) //反射獲得屬性和屬性的值 { if (!pi.Name.Equals("UserId") && !pi.Name.Contains("AddTime"))//如果不是主鍵則追加sql字符串 { if (pi.GetValue(model, null) != null && !pi.GetValue(model, null).ToString().Equals(""))//判斷屬性值是否為空 { sbSql.Append(pi.Name + "=@" + pi.Name + ",");//SID=@SID paras.Add(new SqlParameter("@" + pi.Name, pi.GetValue(model, null))); } } } string strSql = sbSql.ToString().Trim(','); //去掉兩邊的, strSql += " where UserId=@UserId"; paras.Add(new SqlParameter("@UserId", model.UserId)); return dbHelper.ExecuteCommand(strSql, paras.ToArray()) > 0; //執行語句 } #endregion #region 修改密碼 /// <summary> /// 修改學員信息 /// </summary> /// <param name="model">學員實體</param> /// <returns></returns> public bool ModifyPwd(Models.User model) { string Sql = "update [User] set UserPassWord=@userpwd where UserName=@username "; Type modeType = model.GetType(); SqlParameter[] parms = { new SqlParameter("@username",model.UserName), new SqlParameter("@userpwd",model.UserPassWord) }; return dbHelper.ExecuteCommand(Sql, parms) > 0; } #endregion #region 根據查詢條件 返回 學生實體 列表 ,查詢 /// <summary> /// 根據查詢條件 返回 學生實體 列表 where SName=@SName and SPwd=@SPwd and SPwd=@SPwd /// </summary> /// <returns></returns> public List<Models.User> QueryListByCondition(SqlParameter[] paras) { Models.User model = null; StringBuilder sbSql = new StringBuilder("select * from [User] where IsDel=0 "); if (paras != null)//如果參數數組不為空,則循環生成sql的條件語句,追加查詢條件 { for (int i = 0; i < paras.Length; i++)//循環所有參數(如: and PWD=@SPWD) { SqlParameter p = paras[i]; sbSql.Append(" and ");//第二個參數開始 在前面加 and sbSql.Append(p.ParameterName.Substring(1));//獲得參數所對應的列名 sbSql.Append("=" + p.ParameterName); } } //讀取數據庫 返回查詢到的數據表(如果參數為null,則直接執行sql語句,否則帶參數執行sql語句) DataTable dt = (paras == null) ? dbHelper.GetDataTable(sbSql.ToString()) : dbHelper.GetDataTable(sbSql.ToString(), paras); //准備要返回的泛型集合 List<Models.User> list = null; if (dt.Rows.Count > 0)//如果查詢到的行數大於0 { list = new List<Models.User>();//實例化集合對象 foreach (DataRow dr in dt.Rows)//循環臨時表的行記錄 { model = new Models.User();//每循環一行生成一個實體 SetDr2Model(dr, model);//將行數據填入實體對應的屬性 list.Add(model);//將實體對象加入集合 } } return list; } #endregion #region 新增 public int ADD(Models.User MOD) { string sql = "insert into [User] (UserName,UserPassWord) values (@UserName,@UserPassWord);select @@identity";
//insert into 后獲得自動插入的id(select @@identity) SqlParameter[] pars ={ new SqlParameter("@UserName",MOD.UserName), new SqlParameter("@UserPassWord",MOD.UserPassWord) }; int res = dbHelper.GetScalar(sql, pars); //獲得插入id return res; } #endregion #region 根據id刪除指定行 public int DelById(string UserId) { int res = dbHelper.ExecuteCommand("delete [User] where UserId=@UserId", new SqlParameter("@UserId", UserId)); return res; } #endregion #region 返回單個學生實體 public User GetUserByCondition(string UserName, string UserPwd) { string sql = "select * from [User] where UserName=@username and UserPassWord=@userpwd"; SqlParameter[] parms = { new SqlParameter("@username",UserName), new SqlParameter("@userpwd",UserPwd) }; SqlDataReader dr = dbHelper.ExecuteReader(sql, parms); User oneUser = null; //轉換成實體對象 if (dr.Read()) { oneUser = new User(); oneUser.UserId = Convert.ToInt32(dr["UserId"]); oneUser.UserName = dr["UserName"].ToString(); oneUser.UserPassWord = dr["UserPassWord"].ToString(); oneUser.IsDel = Convert.ToBoolean(dr["IsDel"]); oneUser.AddTime = Convert.ToDateTime(dr["AddTime"]); } dr.Close();// dataReader 用完一定要關閉 return oneUser; } #endregion #region 將 數據行 轉換 成 實體對象 /// <summary> /// 將 數據行 轉換 成 實體對象,用於將dataset 的每一行轉成實體對象 /// </summary> /// <param name="dr">數據行</param> /// <param name="model">實體對象</param> public void SetDr2Model(DataRow dr, Models.User model) { if (dr["UserId"].ToString() != "") { model.UserId = int.Parse(dr["UserId"].ToString()); } if (dr["UserName"].ToString() != "") { model.UserName = dr["UserName"].ToString(); } model.UserPassWord = dr["UserPassWord"].ToString(); if (dr["IsDel"].ToString() != "") { model.IsDel = bool.Parse(dr["IsDel"].ToString()); } if (dr["AddTime"].ToString() != "") { model.AddTime = DateTime.Parse(dr["AddTime"].ToString()); } } #endregion } }
BLL:UI層與DAL層交互的橋梁
namespace WeatherStationManager.BLL { public class UserManager { UserServices userServices = new UserServices(); //BLL層只需要與DAL層交互 ,這里建立 DAL層的對象 /* public int AddUser(User addUser) { if (!this.CheckExists(addUser.UserName,addUser.UserPassWord)) { //返回false表示不存在,則新增 return userServices.AddUser(addUser); } else { throw new Exception("用戶名已存在!"); } }*/ #region 修改密碼 /// <summary> /// 修改學員信息 /// </summary> /// <param name="model">學員實體</param> /// <returns></returns> public bool ModifyPwd(Models.User model) { return userServices.ModifyPwd(model); } #endregion #region del public bool DelById(string UserId) { int res = userServices.DelById(UserId); bool resBool = false; if (res > 0) resBool = true; else resBool = false; return resBool; } #endregion #region add public bool ADD(Models.User mod) { int newId = userServices.ADD(mod); return newId > 0; } #endregion #region updata /// <summary> /// 修改學員信息 /// </summary> /// <param name="model">學員實體</param> /// <returns></returns> public bool ModifyStudent(Models.User model) { return userServices.ModifyStudent(model); } #endregion #region 獲得所有user /// <summary> /// 獲得所有列表 /// </summary> /// <returns></returns> public List<Models.User> GetAllUsers() { return userServices.QueryListByCondition(null); } #endregion #region 是否存在 /// <summary> /// 檢測userName在數據庫中是否存在,如果存在返回true,否則返回false /// </summary> /// <param name="typeName"></param> /// <returns></returns> public bool CheckExists(string userName, string userPwd) { User oneUser = userServices.GetUserByCondition(userName, userPwd); if (oneUser != null) { return true; } else { return false; } } #endregion } }
我這里由於業務比較簡單,其實必要的話 ,業務邏輯層是會加上一些復雜的邏輯判斷的。
這樣的分層 數據層 只是訪問 數據庫 , 業務層做一些邏輯判斷 , UI層負責顯示 與輸入
UI層 : 用戶交互
1 string uName = tb1.Text; //獲取用戶輸入 2 string uPwd = tb2.Text; 3 string apwd = tb3.Text; 4 bool res = false; 5 UserManager userManager = new UserManager(); //由於UI層只需要與BLL層交互 ,這里建立BLL對象 6 Models.User model=new Models.User(); 7 if (uPwd!=apwd) 8 { 9 Response.Write("<script>alert('兩次輸入的密碼不一致!')</script>"); 10 } 11 else 12 { 13 model.UserName = uName; 14 model.UserPassWord = uPwd; 15 res = userManager.ModifyPwd(model); 16 Response.Write("<script>alert('修改成功!')</script>"); 17 }