1. 程式人生 > >DataSet與實體類(轉)

DataSet與實體類(轉)

DataSet與SqlDataAdapter物件是微軟在ADO.NET中推出的新一代的資料訪問方式,有些情況下非常適合使用 DataSet,例如在設計原型、開發小型系統和支援實用程式時。但是,在企業系統中使用 DataSet 可能並不是最佳的解決方案,因為對企業系統來說,易於維護要比投入市場的時間更重要。因此在這裡我們探討 DataSet 的一種替代解決方案,即:自定義實體與集合
一、DataSet 存在的問題
問題一:硬編碼問題

DataSet 和資料庫不僅共享資料,不幸的是,它們還共享架構。
如:(
   資料訪問的C#程式碼:
   public DataSet GetAllUsers() 
   {
    SqlConnection connection = new SqlConnection(CONNECTION_STRING);
    SqlCommand command = new SqlCommand("Select * from Users", connection);
    command.CommandType = CommandType.StoredProcedure;
    SqlDataAdapter da = new SqlDataAdapter(command);
    DataSet ds = new DataSet();
    da.Fill(ds);
    return ds;
   }


   頁面程式碼:
   <form id="Form1" method="post" runat="server">
   <asp:Repeater ID="users" Runat="server">
   <ItemTemplate>
   <%# DataBinder.Eval(Container.DataItem, "FirstName") %>
   <br />
   </ItemTemplate>
   </asp:Repeater>
   </form>
   在這個例子中我們可以看到,在ASPX頁面中直接綁定了列名"FistName",如果資料庫的結構發生變化(闢如更改列名),那這種變化會直接從資料庫影響到我們的用介面層(雖然這種程式碼修改不麻煩)。
這樣還像“N層”結構嗎?從某種程式上看,我認為這是一種硬編碼。在大型軟體中使用這種型式開發,會給將來的維護帶來很大的麻煩的。
   另外,這種編碼方式還必須使使用者層知道資料庫的結構,當然也有人提出了使用強型別DataSet和常量解決這個問題。但這裡的編碼語法會使程式設計師很煩。如:ds.Tables[0].Rows[i]["userId"].ToString()不僅難於閱讀,而且需要非常熟悉列名稱及其型別。如果像這樣使用 DataSet,業務層可能會很薄弱或很複雜。
問題二:弱型別的問題
  DataSet的資料表中的值都以 System.Object 的形式返回,需要對這種值進行轉換
,但轉換可能會失敗的風險(如:空值轉換,型別不匹配轉換,識別符號不正確等),不幸的是,失敗不是在編譯時發生,而是在執行時發生。
另外,在轉換的時候,我們可能會遇到拆箱和裝箱的操作這種操作會比較耗費資源,降低軟體效能。
問題三:面向物件的桎梏

   DataSet雖然是個物件,但是這個物件還是從資料庫中的二維關係表演化過來的,我們的一個實體物件在DataSet中卻變成資料表中的一條記錄,而物件與物件的關係卻變成資料表與資料表之間的資料關係(DataRelation)。而我們面向物件的思想是在程式程式碼中模擬現實中的物件來構建程式中的物件的屬性和方法,然後建立程式物件之間的引用與派生關係。
   正因如此,我們的程式程式碼很難如實反應我們現實生活中的物件及物件與物件之間的聯絡。
  所以說DataSet這個東西能很好地理解資料庫,但不能很好地理解我們的生活。

二、自定義實體類:(
自定義實體類是我們根據現實生活中的物件抽象出來的類,它的物件能很好地反應我們的生活。它很好地利用繼承和封裝等 OO 技術。
如:
public class User 
{
   private string userName;
   private string password;
   public string UserName 
   {
    get { return userName; }
    set { userName = value; }
   }
   public string Password 
   {
    get { return password; }
    set { password = value; }
   }
   public User() {}
   public User(string name, string password) 
   {
    this.UserName = name;
    this.Password = password;
   }
}

簡單的實體類的物件可對應表中的一條記錄,其屬性可對應於該記錄的欄位值。而實體類中對其它實體類的引用關聯可對應於表與表之間的關聯關係。
下面我們看一下如何把資料庫中的結構與資料轉換到我們的實體物件中去。
public User GetUser(string username) 
{
   SqlConnection conn = new SqlConnection("server=.;database=mydb;uid=sa;pwd=sa");
   SqlCommand command = new SqlCommand("select * from username where username = @username", connection);
   command.Parameters.AddWithValue("@username", username);
   SqlDataReader dr = null;
   conn.Open();
   dr = command.ExecuteReader();
   if (dr.Read())
   {
   User user = new User();
    user.UserName = Convert.ToString(dr["UserName"]);
    user.Password = Convert.ToString(dr["Password"]);
    conn.Close();
    return user;
   }
   conn.Close();
   return null;
}
細心的朋友可能注意到我前面提到 DataSet 的問題:
   弱型別,效率降低,並且存在資料庫架構變化而轉換出錯的問題,還需要開發人員深入瞭解基礎資料結構。
看一看上文的程式碼,您可能會注意到這些問題依然存在。但請注意,我們已經將這些問題封裝到一個非常孤立的程式碼區域內;而並不像DataSet那樣分佈在整個程式程式碼當中。
好了,到此為至,我們對夠把資料庫中的一條記錄轉換為我們實體類物件了,可以當我們從資料庫中檢索出一批記錄來,我們如何儲存到記憶體中呢?答案當然是實體類的集合。
說到集合,我想大部分人會想到ArrayList,但現在問題來了,ArrayList本身就是一個弱型別的集合,即任何東西都可以放在ArrayList集合中去,而取資料的時候又要進行型別轉換或拆箱裝箱。這豈不是又重DataSet的覆轍了嗎?在DotNet1.1中,我們只好採用自定義集合型別(派生自CollectionBase類),來實現集合的強型別化。但由於實體類的多樣性,也必然帶來了實體類集合的多樣性,使我們花費大量時間編寫各種實體類的強型別集合。
好在DotNet2.0中為我們提供了一個新的概念--泛型。它使人們從編寫大量強型別的實體類集合的工作中解脫出來。在這裡我們不多談泛型這個概念,只來關注一下我們最常用的一個泛型集合型別--List<T>

三、泛型集合List<T>
List<T>這個泛型集合所在的名稱空間是:System.Collections.Generic,它是一個強型別的集合,它還提供了一系列的集合操作方法,如新增、刪除、檢索、排序等。
其中的"T"中個型別“代位符”,當我們例項化List<T>的時候,會根據"T"的值,控制我們List<T>只儲存相應型別的資料。
如:
List<User> list = new List<User>();
這句話就生成一個集合list,該集合中的每一個元素必須是User資料型別,如果試圖把其它資料型別新增到我們的list集合中去會發生編譯異常。另外在取得list集合中的某個元素的時候,也不必去進行強制轉換,因為它儲存進去的就是User型別。
如:
  public List<User> GetUser(string username) 
   {
   List<User> list = new List<User>();
    SqlConnection conn = new SqlConnection("server=.;database=mydb;uid=sa;pwd=sa");
    SqlCommand command = new SqlCommand("select * from username where username = @username", connection);
    command.Parameters.AddWithValue("@username", username);
    SqlDataReader dr = null;
    conn.Open();
    dr = command.ExecuteReader();
    while(dr.Read())
    {
    User user = new User();
     user.UserName = Convert.ToString(dr["UserName"]);
     user.Password = Convert.ToString(dr["Password"]);
     list.Add(user);
    }
    conn.Close();
   return list;
   }
在VS2005中我們的GridView和DataList都可以繫結到我們的實體類泛型集合上,就像使用DataSet繫結一樣方便。)
在泛型集合中有的方法Sort()可以對裡面的實體類進行排序,但是如果僅簡單使用Sort()進行排序的話會產生異常。因為List<User>中所儲存的是一個實體物件,究竟按照哪個實體屬性進行排序List<User>物件現在並不知道。除非我們實體類實現IComparable<T>介面
IComparable<T>介面中有個方法"int CompareTo(T)"需要我們實現,在該方法中編寫程式碼,把當前物件與T物件相關屬性值的進行對比,並根據對比的結果返回-1,1,0三個值,以做為List<T>物件對其內部實體類物件進行排序的依據。
如:
public class User : IComparable<User>
{
   private string _UserName;
   private string _Password;
   private 
   public string UserName 
   {
    get { return _UserName; }
    set { _UserName = value; }
   }
   public string Password 
   {
    get { return _Password; }
    set { _Password = value; }
   }
   public User() {}
   public User(string name, string password) 
   {
    this.UserName = name;
    this.Password = password;
   }
  public int CompareTo(User obj)
   {
    if(this.UserName > obj.UserName)
    {
     return 1;
    }
    else if(this.UserName < obj.UserName)
    {
     return -1;
    }
    else
    {
     return 0;
    }
   }
}

四、實體類之間的關係
對於關係資料庫,可以通過外來鍵維護關係。實體之間也存在關係,而在實體類中,關係則體現為一個實體類對另一個物件的引用。(
如:

public class Role : IComparable<Role>
{
   private string _RoleId;
   private string _RoleName;
   public string RoleId
   {
    get{return _RoleId;}
    set{_RoleId = value;}
   }
   public string RoleName
   {
    get{return _RoleName;}
    set{_RoleName = value;}
   }
   public Role(){}
   public Role(string roleid,string rolename)
   {
    this._RoleId = roleid;
    this._RoleName = rolename;
   }
  public int CompareTo(Role obj)
   {
    if(this.RoleId > obj.RoleId)
     return 1;
    else if(this.RoleId < obj.RoleId)
     return -1;
    else 
     return 0;
   }
}
public class User : IComparable<User>
{
   private string _UserName;
   private string _Password;
   private List<Role> _Roles;
   public string UserName 
   {
    get { return _UserName; }
    set { _UserName = value; }
   }
   public string Password 
   {
    get { return _Password; }
    set { _Password = value; }
   }
  public List<Role> Roles
   {
    get{ return _Role;}
    set{_Role = value;}
   }
   public User() {}
   public User(string name, string password) 
   {
    this.UserName = name;
    this.Password = password;
   }
public int CompareTo(User obj)
   {
    if(this.UserName > obj.UserName)
    {
     return 1;
    }
    else if(this.UserName < obj.UserName)
    {
     return -1;
    }
    else
    {
     return 0;
    }
   }
}

資料訪問類:
//角色資料訪問類
   public class RoleDA
   {
    private SqlConnection conn = new Conn().Connection;
    public RoleDA()
    {
    }
    //根據角色代號返回角色實體物件
    public 
RoleData select(string roleId)
    {
       
RoleData rd = null;
       SqlCommand cmd = conn.CreateCommand();
       cmd.CommandText = "select * from [role] where roleid = @roleid";
       cmd.Parameters.AddWithValue("@roleid",roleId);
       conn.Open();
       SqlDataReader dr = cmd.ExecuteReader();
       while (dr.Read())
       {
           
rd = new RoleData();
           rd.RoleId = dr["roleid"].ToString();
           rd.RoleName = dr["rolename"].ToString();
       }
       conn.Close();
       
return rd;
    }
   }

   //使用者角色關係資料訪問類
  public class UserInRoleDA
   {
    private SqlConnection conn = new Conn().Connection;
    public UserInRoleDA()
    {
    }
    //根據使用者名稱查詢該使用者的角色列表
     public List<RoleData> select(string username)
     {
         List<RoleData> list = new List<RoleData>();
         RoleData rd = null;
         SqlCommand cmd = conn.CreateCommand();
         cmd.CommandText = "select * from userinrole where username = @username";
         cmd.Parameters.AddWithValue("@username",username);
         conn.Open();
         SqlDataReader dr = cmd.ExecuteReader();
         while (dr.Read())
         {
            //呼叫角色資料訪問類生成角色實體物件
             rd = new RoleDA().select(dr["roleid"].ToString());
             list.Add(rd);
         }
         conn.Close();
         return list;
     }
   }
   //使用者資料訪問類
  public class UserDA
   {
    private SqlConnection conn = new Conn().Connection;
    public UserDA()
    {
    }
    //根據使用者名稱返回使用者實體對)
     public UserData select(string username)
     {
         UserData user = null;
         SqlCommand cmd = conn.CreateCommand();
         cmd.CommandText = "select * from [user] where username = @username";
         cmd.Parameters.AddWithValue("@UserName",username);
         conn.Open();
         SqlDataReader dr = cmd.ExecuteReader();
         if (dr.Read())
         {
             user = new UserData();
             user.UserName = dr["username"].ToString();
             user.Password = dr["password"].ToString();
             //根據使用者名稱呼叫使用者角色關係資料訪問類的方法生成使用者擁有的角色列表
             user.Roles = new UserInRoleDA().select(user.UserName);
         }
         conn.Close();
        return user;
     }
   }
客戶端程式碼:
  UserData user = new UserDA().select("Fred L. Mannering");
    Response.Write("UserName:"+user.UserName+"<br>");
    Response.Write("Password:"+user.Password+"<br>");
    foreach (RoleData role in user.Roles)
    {
        Response.Write(">>Role:"+role.RoleId.ToString()+"-"+role.RoleName+"<br>");
    }
執行結果:
   UserName:Fred L. Mannering
   Password:aaa
   >>Role:R002-Manager
   >>Role:R003-Employee
  
自定義實體類使您獲得了面向物件的程式設計的豐富功能,並幫助您構建了可靠、可維護的 N 層體系結構的框架,它也是目前業界所普遍採用的實現方式。