C#三層架構詳細解剖


深入淺出C#三層架構(轉)

C的三層架構 - 枯草傾心 - 枯草傾心

本文用一個示例來介紹如何建設一個三層架構的項目,並說明項目中各個文件所處的層次與作用。寫本文的目的,不是為了說明自己的這個方法有多對,而是希望給那些初學三層架構卻不知從何入手的朋友提供一點幫助。因為網上的文章,大多是注重理論的介紹,而忽略了具體的實踐應用,或者有示例但講得不透徹。導致看了之后,理論上又學習了一遍,但還是不知道代碼怎么寫。所以想從這個方面入手寫一下,讓從來沒做過三層架構的初學者也能照貓畫虎,寫出代碼來。文中的代碼是偽代碼,僅用來闡明思路。    正文: 一提三層架構,大家都知道是表現層(UI),業務邏輯層(BLL)和數據訪問層(DAL),而且每層如何細分也都有很多的方法。但具體代碼怎么寫,到底那些文件算在哪一層,卻是模模糊糊的。下面用一個簡單的例子來帶領大家實戰三層架構的項目,這個例子只有一個功能,就是用戶的簡單管理。 首先建立一個空白解決方案,添加如下項目及文件
1、添加ASP.NET Web Application項目,命名為UI,新建Web Form類型文件User.aspx(含User.aspx.cs)
2、添加ClassLibrary項目,命名為BLL,新建Class類型文件UserBLL.cs
3、添加ClassLibrary項目,命名為DAL,新建Class類型文件UserDAL.cs。添加SQLHelper引用。(這個是微軟的數據訪問類,也可以不用,直接編寫所有的數據訪問代碼。我一般用自己寫的數據訪問類DataAccessHelper)。
4、添加ClassLibrary項目,命名為Model,新建Class類型文件UserModel.cs
5、添加ClassLibrary項目,命名為IDAL,新建Interface類型文件IUserDAL.cs
6、添加ClassLibrary項目,命名為ClassFactory
相信大家已經看出來了,這個和Petshop的示例沒什么區別,而且更簡單,因為在下也是通過Petshop學習三層架構的。但一些朋友對於這幾個項目所處的層次,以及它們之間的關系,可能比較模糊,這里逐個說明一下:
1、User.aspx和User.aspx.cs
這兩個文件(以及文件所屬的項目,下面也是如此,不再重復強調了)都屬於表現層部分。User.aspx比較好理解,因為它就是顯示頁面了。User.aspx.cs有些人覺得不應該算,而是要划到業務邏輯層中去。如果不做分層的話,那么讓User.aspx.cs來處理業務邏輯,甚至操作數據庫都沒什么問題,但是做分層的話,這樣就不應該了。在分層結構中,User.aspx.cs僅應該處理與顯示有關的內容,其它部分都不應該涉及。
舉例:我們實現用列表方式顯示用戶的功能,那么提取信息的工作是由BLL來做的,UI(本例中是User.aspx.cs)調用BLL得到UserInfo后,通過代碼綁定到User.aspx的數據控件上,就實現了列表的顯示。在此過程中User.aspx.cs對UI沒有起到什么作用,僅是用來傳遞數據,而且因為實際編碼中大部分情況都是如此的實現,所以使有些人覺得User.aspx.cs不應該算UI,而應該並入BLL負責邏輯處理。繼續往下看,這時提出了一個新需求,要求在每個用戶的前面加一個圖標,生動地表現出用戶的性別,而且不滿18歲的用兒童圖標表示。這個需求的實現,就輪到User.aspx.cs來做了,這種情況下User.aspx.cs才算有了真正的用途。
2NewBLL.cs
添加如下方法:
public IList<UserInfo> GetUsers():返回所有的用戶信息列表
public UserInfo GetUser(int UserId):返回指定用戶的詳細信息
public bool AddUser(UserInfo User):新增用戶信息
public bool ChangeUser(UserInfo User):更新用戶信息
public void RemoveUser(int UserId):移除用戶信息
此文件就屬於業務邏輯層了,專門用來處理與業務邏輯有關的操作。可能有很多人覺得這一層唯一的用途,就是把表現層傳過來的數據轉發給數據層。這種情況確實很多,但這只能說明項目比較簡單,或者項目本身與業務的關系結合的不緊密(比如當前比較流行的MIS),所以造成業務層無事可做,只起到了一個轉發的作用。但這不代表業務層可有可無,隨着項目的增大,或者業務關系比較多,業務層就會體現出它的作用來了。
此處最可能造成錯誤的,就是把數據操作代碼划在了業務邏輯層,而把數據庫作為了數據訪問層。
舉例:有些朋友感覺BLL層意義不大,只是將DAL的數據提上來就轉發給了UI,而未作任何處理。看一下這個例子
BLL層
SelectUser(UserInfo userInfo)根據傳入的username或email得到用戶詳細信息。
IsExist(UserInfo userInfo)判斷指定的username或email是否存在。
然后DAL也相應提供方法共BLL調用
SelectUser(UserInfo userInfo)
IsExist(UserInfo userInfo)
這樣BLL確實只起到了一個傳遞的作用。
但如果這樣做:
BLL.IsExist(Userinfo userinfo)
{
UerInfo user = DAL.SelectUser(User);
return (userInfo.Id != null);
}
那么DAL就無需實現IsExist()方法了,BLL中也就有了邏輯處理的代碼。
3UserModel.cs
實體類,這個東西,大家可能覺得不好分層。包括我以前在內,是這樣理解的:UIßàModelßàBLLßàModelßàDAL,如此則認為Model在各層之間起到了一個數據傳輸的橋梁作用。不過在這里,我們不是把事情想簡單,而是想復雜了。
Model是什么?它什么也不是!它在三層架構中是可有可無的。它其實就是面向對象編程中最基本的東西:類。一個桌子是一個類,一條新聞也是一個類,int、string、doublie等也是類,它僅僅是一個類而已。
這樣,Model在三層架構中的位置,和int,string等變量的地位就一樣了,沒有其它的目的,僅用於數據的存儲而已,只不過它存儲的是復雜的數據。所以如果你的項目中對象都非常簡單,那么不用Model而直接傳遞多個參數也能做成三層架構。
那為什么還要有Model呢,它的好處是什么呢。下面是思考一個問題時想到的,插在這里:
Model在各層參數傳遞時到底能起到做大的作用?
在各層間傳遞參數時,可以這樣:
AddUser(userId,userName,userPassword,…,)
也可以這樣:
AddUser(userInfo)
這兩種方法那個好呢。一目了然,肯定是第二種要好很多。
什么時候用普通變量類型(int,string,guid,double)在各層之間傳遞參數,什么使用Model傳遞?下面幾個方法:
SelectUser(int UserId)
SelectUserByName(string username)
SelectUserByName(string username,string password)
SelectUserByEmail(string email)
SelectUserByEmail(string email,string password)
可以概括為:
SelectUser(userId)
SelectUser(user)
這里用user這個Model對象囊括了username,password,email這三個參數的四種組合模式。UserId其實也可以合並到user中,但項目中其它BLL都實現了帶有id參數的接口,所以這里也保留這一項。
傳入了userInfo,那如何處理呢,這個就需要按照先后的順序了,有具體代碼決定。
這里按這個順序處理
首先看是否同時具有username和password,然后看是否同時具有email和password,然后看是否有username,然后看是否有email。依次處理。
這樣,如果以后增加一個新內容,會員卡(number),則無需更改接口,只要在DAL的代碼中增加對number的支持就行,然后前台增加會員卡一項內容的表現與處理即可。
4UserDAL.cs
public IList<UserInfo> SelectUsers():返回所有的用戶信息列表(返回的一個泛型列表,可以進行序列化等操作)
public UserInfo SelectUser(int UserId):返回指定用戶的相信信息(通過ID,返回實體類的實例相關信息)
public bool InsertUser(UserInfo User):新增用戶信息(不同於Select,因為增加和更新是不會返回實體數據,只返回一個bool值表明操作是否成功)
public bool UpdateUser(UserInfo User):更新用戶信息(同上)
public void DeleteUser(int UserId):移除用戶信息(這個)
很多人最鬧不清的就是數據訪問層,到底那部分才算數據訪問層呢?有些認為數據庫就是數據訪問層,這是對定義沒有搞清楚,DAL是數據訪問層而不是數據存儲層,因此數據庫不可能是這一層的。也有的把SQLHelper(或其同類作用的組件)作為數據訪問層,它又是一個可有可無的東西,SQLHelper的作用是減少DAL層的重復性編碼,提高編碼效率,因此如果我習慣在乎效率或使用一個非數據庫的數據源時,可以丟棄SQLHelper,一個可以隨意棄置的部分,又怎么能成為三層架構中的一層呢。
可以這樣定義:與數據源操作有關的代碼,就應該放在數據訪問層中,屬於數據訪問層
5IUserDAL
數據訪問層接口,這又是一個可有可無的東西,因為Petshop中帶了它和ClassFactory類工廠,所以有些項目不論需不需要支持多數據源,都把這兩個東西做了進來,有的甚至不建ClassFactory而只建了IDAL,然后“IUserDAL iUserDal = new UserDAL();”,不知意義何在。這就完全是畫虎不成反類犬了。
許多人在這里有一個誤解,那就是以為存在這樣的關系:BLLßàIDALßàDAL,認為IDAL起到了BLL和DAL之間的橋梁作用,BLL是通過IDAL來調用DAL的。但實際是即使你如此編碼:“IUserDAL iUserDal = ClassFacotry.CreateUserDAL();”,那么在執行“iUserDal.SelectUsers()”時,其實還是執行的UserDAL實例,而不是IUserDAL實例,所以IDAL在三層中的位置是與DAL平級的關系。 通過上面的介紹,基本上將三層架構的層次結構說明了。其實,本人有一個判斷三層架構是否標准的方法,那就是將三層中的任意一層完全替換,都不會對其它兩層造成影響,這樣的構造基本就符合三層標准了(雖然實現起來比較難^_^)。例如如果將項目從B/S改為C/S(或相反),那么除了UI以外,BLL與DAL都不用改動;或者將SQLServer改為Oracle,只需替換SQLServerDAL到OracleDAL,無需其它操作等等。
總結:不要因為某個層對你來說沒用,或者實現起來特別簡單,就認為它沒有必要,或者摒棄它,或者挪作它用。只要進行了分層,不管是幾層,每一層都要有明確的目的和功能實現,而不要被實際過程所左右,造成同一類文件位於不同層的情況發生。也不要出現同一層實現了不同的功能的情況發生。

 我自己補充點代碼讓大家看看三層式如何調用的,(一般我從DAL開始寫,其實我覺得更應該從BLL層開始寫,那樣才更加明了你的項目的需求是哪些,BBL本來就是業務邏輯層嘛)

第一步:DAL層的一個方法:(注意看注釋)

 先給點SQLHelpher的方法給大家看看先:(注意參數和返回類型,增刪改都是返回影響行數)

/// <summary>
        /// 執行無參SQL語句,並返回執行行數
        /// </summary>
        public static int GetScalar(string safeSql)
        {
            SqlCommand cmd = new SqlCommand(safeSql, Connection);
            int result = Convert.ToInt32(cmd.ExecuteScalar());
            return result;
        }
        /// <summary>
        /// 執行有參SQL語句,並返回執行行數
        /// </summary>
        public static int GetScalar(string sql, params SqlParameter[] values)
        {
            SqlCommand cmd = new SqlCommand(sql, Connection);
            cmd.Parameters.AddRange(values);
            int result = Convert.ToInt32(cmd.ExecuteScalar());
            return result;
        }
        /// <summary>
        /// 執行無參SQL語句,並返SqlDataReader
        /// </summary>
        public static SqlDataReader GetReader(string safeSql)
        {
            SqlCommand cmd = new SqlCommand(safeSql, Connection);
            SqlDataReader reader = cmd.ExecuteReader();
            return reader;
        }

 

 再給個Model層的實體類:

[Serializable]
   public class RoomType
   {
       protected int typeId;
       protected string typeName = String.Empty;

       public RoomType()
       {
       }
      
       public int TypeId
       {
           get { return typeId; }
           set { typeId = value; }
       }

       public string TypeName
       {
           get { return typeName; }
           set { typeName = value; }
       }


}

 

第二步:.看看BLL如何調用DAL的方法(如上所說,DAL只做數據查詢,BLL才涉及到邏輯判斷和推理)

 

 /// <summary>
       /// 根據類型ID的房間信息集合
       /// </summary>
       public static IList<Room> GetAllRoomsByTypeId(int roomTypeId)
       {
           try
           {
               return RoomService.GetAllRoomsByTypeId(roomTypeId);
           }
           catch (Exception ex)
           {
               throw new Exception(ex.ToString());
           }
       }
       /// <summary>
       /// 得到房間信息集合
       /// </summary>
       public static IList<Room> GetAllRooms()
       {
           try
           {
               return RoomService.GetAllRooms();
           }
           catch (Exception ex)
           {
               throw new Exception(ex.ToString());
           }
       }
       /// <summary>
       /// 根據房間ID得到客房信息實體對象
       /// </summary>
       public static Room GetRoomByRoomId(int roomId)
       {
           try
           {
               return RoomService.GetRoomByRoomId(roomId);
           }
           catch (Exception ex)
           {
               throw new Exception(ex.ToString());
           }
       }

 

 

 第三步:Web層(UI) ,只是把BLL層得到的數據給綁定到Web控件就ok了,只做頁面布局和數據綁定相關的事情(如監聽按鈕事件,設置Style的風格,其實完全可以結合DOM、JQuery、Javascript來實現更加優異,這里只是給大家講解框架)

 

/// <summary>
    /// 執行GridView數據行綁定事件
    /// </summary>
    protected void gvRoom_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            //設置行顏色   
            e.Row.Attributes.Add("onmouseover", "currentcolor=this.style.backgroundColor;this.style.backgroundColor='#ff9900'");
            //添加自定義屬性,當鼠標移走時還原該行的背景色   
            e.Row.Attributes.Add("onmouseout", "this.style.backgroundColor=currentcolor");
            //添加刪除確認
            ImageButton imgbtn = (ImageButton)e.Row.FindControl("imgbtnDelete");
            imgbtn.Attributes.Add("onclick", "return confirm('您確認要刪除嗎?');");
        }
    }
    /// <summary>
    /// 執行GridView數據行按鈕事件
    /// </summary>
    protected void gvRoom_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        //獲取命令名稱
        string cmd = e.CommandName;
        int roomId = Convert.ToInt32(e.CommandArgument);//獲取命令參數
        if (cmd == "De")
        {
            //根據客房ID刪除客房信息
            RoomManager.DeleteRoomByRoomId(roomId);
        }
        else if (cmd == "Ed")
        {
            //跳轉到客房信息編輯頁
            //此處使用的是Transfer
            Page.Server.Transfer("EditRoom.aspx?roomid=" +roomId.ToString());
        }
        BindRoom();
    }
    /// <summary>
    /// 執行分頁事件
    /// </summary>
    protected void gvRoom_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
        gvRoom.PageIndex = e.NewPageIndex;
        BindRoom();
    }

 

 雜項總結:上面所說到的接口(Interface/Factory),其實現實中除非大中型項目最好用到,小型項目都可以不需要,因為接口總體來說也是為了更好的實現松耦合和方法的調用效率(說白了就是為了后期代碼好維護和性能的提升(說不定還要犧牲性能))  ;  基本所有框架都是為了松耦合實現代碼功能分離,不同項目類型適合不同框架,總的都是為了同一個目標:代碼維護方便;  所以很多項目是犧牲了系統性能來提升代碼的優良結構

  哦 ,多說一點,上面的泛型和相關類可以進行序列化  [Serializable] 來進行更好的網絡數據傳輸 ,相關文檔大家去看看相關文獻。

 

 引用文章:如下

·                              淺析C#中三層架構的實現

本文討論如何在C#中實現三層架構,使用MS Access數據庫存儲數據。同時在三層架構中實現一個小型的可復用的組件來保存客戶數據,並提供添加、更新、查找客戶數據的功能。

這篇文章討論如何在C#中實現三層架構,使用MS Access數據庫存儲數據。在此,我在3層架構中實現一個小型的可復用的組件保存客戶數據。並提供添加,更新,查找客戶數據的功能。

背景

首先,我介紹一些3層架構的理論知識。簡單說明:什么是3層架構?3層架構的優點是什么?

什么是三層架構?

3層架構是一種“客戶端-服務器”架構,在此架構中用戶接口,商業邏輯,數據保存以及數據訪問被設計為獨立的模塊。主要有3個層面,第一層(表現層,GUI層),第二層(商業對象,商業邏輯層),第三層(數據訪問層)。這些層可以單獨開發,單獨測試。

為什么要把程序代碼分為3層。把用戶接口層,商業邏輯層,數據訪問層分離有許多的優點。

在快速開發中重用商業邏輯組件,我們已經在系統中實現添加,更新,刪除,查找客戶數據的組件。這個組件已經開發並且測試通過,我們可以在其他要保存客戶數據的項目中使用這個組件。

系統比較容易遷移,商業邏輯層與數據訪問層是分離的,修改數據訪問層不會影響到商業邏輯層。系統如果從用SQL Server存儲數據遷移到用Oracle存儲數據,並不需要修改商業邏輯層組件和GUI組件

系統容易修改,假如在商業層有一個小小的修改,我們不需要在用戶的機器上重裝整個系統。我們只需要更新商業邏輯組件就可以了。

應用程序開發人員可以並行,獨立的開發單獨的層。

代碼

這個組件有3層,第一個層或者稱為GUI層用form實現,叫做FrmGUI。第二層或者稱為商業邏輯層,叫做BOCustomer,是Bussniess Object Customer的縮寫。最后是第三層或者稱為數據層,叫做DACustomer,是Data Access Customer的縮寫。為了方便,我把三個層編譯到一個項目中。

用戶接口層

下面是用戶接口成的一段代碼,我只選取了調用商業邏輯層的一部分代碼。

//This function get the details from the user via GUI

//tier and calls the Add method of business logic layer.

private void cmdAdd_Click(object sender, System.EventArgs e)

{

try

{

cus = new BOCustomer();

cus.cusID=txtID.Text.ToString();

cus.LName = txtLName.Text.ToString();

cus.FName = txtFName.Text.ToString();

cus.Tel= txtTel.Text.ToString();

cus.Address = txtAddress.Text.ToString();

cus.Add();

}

catch(Exception err)

{

MessageBox.Show(err.Message.ToString());

}

}

//This function gets the ID from the user and finds the

//customer details and return the details in the form of

//a dataset via busniss object layer. Then it loops through

//the content of the dataset and fills the controls.

private void cmdFind_Click(object sender, System.EventArgs e)

{

try

{

String cusID = txtID.Text.ToString();

BOCustomer thisCus = new BOCustomer();

DataSet ds = thisCus.Find(cusID);

DataRow row;

row = ds.Tables[0].Rows[0];

//via looping

foreach(DataRow rows in ds.Tables[0].Rows )

{

txtFName.Text = rows["CUS_F_NAME"].ToString();

txtLName.Text = rows["CUS_L_NAME"].ToString();

txtAddress.Text = rows["CUS_ADDRESS"].ToString();

txtTel.Text = rows["CUS_TEL"].ToString();

}

}

catch (Exception err)

{

MessageBox.Show(err.Message.ToString());

}

}

//this function used to update the customer details.

private void cmdUpdate_Click(object sender, System.EventArgs e)

{

try

{

cus = new BOCustomer();

cus.cusID=txtID.Text.ToString();

cus.LName = txtLName.Text.ToString();

cus.FName = txtFName.Text.ToString();

cus.Tel= txtTel.Text.ToString();

cus.Address = txtAddress.Text.ToString();

cus.Update();

}

catch(Exception err)

{

MessageBox.Show(err.Message.ToString());

}

}

商業邏輯層

下面是商業邏輯層的所有代碼,主要包括定義customer對象的屬性。但這僅僅是個虛構的customer對象,如果需要可以加入其他的屬性。商業邏輯層還包括添加,更新,查找,等方法。

商業邏輯層是一個中間層,處於GUI層和數據訪問層中間。他有一個指向數據訪問層的引用cusData = new DACustomer().而且還引用了System.Data名字空間。商業邏輯層使用DataSet返回數據給GUI層。

using System;

using System.Data;

namespace _3tierarchitecture

{

///

/// Summary description for BOCustomer.

///

public class BOCustomer

{

//Customer properties

private String fName;

private String lName;

private String cusId;

private String address;

private String tel;

private DACustomer cusData;

public BOCustomer()

{

//An instance of the Data access layer!

cusData = new DACustomer();

}

///

/// Property FirstName (String)

///

public String FName

{

get

{

return this.fName;

}

set

{

try

{

this.fName = value;

if (this.fName == "")

{

throw new Exception(

"Please provide first name ...");

}

}

catch(Exception e)

{

throw new Exception(e.Message.ToString());

}

}

}

///

/// Property LastName (String)

///

public String LName

{

get

{

return this.lName;

}

set

{

//could be more checkings here eg revmove ' chars

//change to proper case

//blah blah

this.lName = value;

if (this.LName == "")

{

throw new Exception("Please provide name ...");

}

}

}

///

/// Property Customer ID (String)

///

public String cusID

{

get

{

return this.cusId;

}

set

{

this.cusId = value;

if (this.cusID == "")

{

throw new Exception("Please provide ID ...");

}

}

}

///

/// Property Address (String)

///

public String Address

{

get

{

return this.address;

}

set

{

this.address = value;

if (this.Address == "")

{

throw new Exception("Please provide address ...");

}

}

}

///

/// Property Telephone (String)

///

public String Tel

{

get

{

return this.tel;

}

set

{

this.tel = value;

if (this.Tel == "")

{

throw new Exception("Please provide Tel ...");

}

}

}

///

/// Function Add new customer. Calls

/// the function in Data layer.

///

public void Add()

{

cusData.Add(this);

}

///

/// Function Update customer details.

/// Calls the function in Data layer.

///

public void Update()

{

cusData.Update(this);

}

///

/// Function Find customer. Calls the

/// function in Data layer.

/// It returns the details of the customer using

/// customer ID via a Dataset to GUI tier.

///

public DataSet Find(String str)

{

if (str == "")

throw new Exception("Please provide ID to search");

DataSet data = null;

data = cusData.Find(str);

return data;

}

}

}

 

數據訪問層

數據層包括處理MS Access數據庫的細節。所有這些細節都是透明的,不會影響到商業邏輯層。數據訪問層有個指向商業邏輯層的引用BOCustomer cus。為了應用方便並且支持其他數據庫。

using System;

using System.Data.OleDb;

using System.Data;

namespace _3tierarchitecture

{

///

/// Summary description for DACustomer.

///

public class DACustomer

{

private OleDbConnection cnn;

//change connection string as per the

//folder you unzip the files

private const string CnnStr =

"Provider=Microsoft.Jet.OLEDB.4.0;Data " +

"Source= D:\\Rahman_Backup\\Programming\\" +

"Csharp\\3tierarchitecture\\customer.mdb;";

//local variables

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM