2012年9月15日 星期六

使用 ADO.NET Disconnected 類別

ADO.NET

使用 DataTable 物件

DataTable 物件

DataTable 是一種表格式的資料表現概念,它由資料列(rows)、資料欄(columns)、條件約束(constraints)組成。

常用方法:

  • AcceptChanges :認可自前一次呼叫 AcceptChanges 以來對這個資料表所做的所有變更。
  • GetChanges :取得自前一次呼叫 AcceptChanges 以來所做的所有變更。
  • Load :載入資料。( 視 LoadOption 參數,執行不同的載入操作)
  • LoadDataRow :尋找並更新特定資料列。 如果找不到符合的資料列,則使用指定值來建立新的資料列。
  • Merge :將指定的 DataTable 與目前的 DataTable 合併。
  • NewRow :使用與資料表相同的結構描述來建立新的 DataRow。
  • OnRowChanged :引發 RowChanged 事件。
  • Select :取得符合篩選條件的所有 DataRow 物件的陣列。
  • Copy :複製這個 DataTable 的結構和資料。

建立 DataTable

private DataTable CreateDataTable()
{
    //=====================================================
    //Create the DataTable named "Employee"
    //=====================================================

    DataTable Employee = new DataTable("Employee");

    //=====================================================
    //Add the DataColumn
    //=====================================================

    DataColumn col1 = new DataColumn("EmpNo");
    col1.DataType = typeof(string);
    col1.MaxLength = 10;
    col1.Unique = true;                 // Unique Key
    col1.AllowDBNull = false;
    col1.DefaultValue = "";             // DefaultValue
    col1.Caption = "EmpNo";
    col1.AutoIncrement = false;

    DataColumn col2 = new DataColumn("FirstName");
    col2.MaxLength = 20;
    col2.AllowDBNull = false;

    DataColumn col3 = new DataColumn("LastName");
    col3.MaxLength = 20;
    col3.AllowDBNull = false;

    DataColumn col4 = new DataColumn("Age");
    col3.DataType = typeof(Int16);
    col1.DefaultValue = 0;
 
    // Add [ Expression columns ]

    DataColumn col5 = new DataColumn("LastName and FirstName");
    col5.DataType = typeof(string);
    col5.MaxLength = 40;
    col5.Expression = "LastName + ',' + FirstName";
    col1.AllowDBNull = false;

    Employee.Columns.Add(col1);
    Employee.Columns.Add(col2);
    Employee.Columns.Add(col3);
    Employee.Columns.Add(col4);
    Employee.Columns.Add(col5);

    //=====================================================
    // Set Primary Key Columns
    //=====================================================

    Employee.PrimaryKey = new DataColumn[] { col1 };

    return Employee;
}

Copy 、 Clone 、 Import

DataTable employee = CreateDataTable();

// Copy DataTable
DataTable copy = employee.Copy();

// Clone DataTable
DataTable clone = employee.Clone();

// Importing datarows to datatable
clone.ImportRow(employee.Rows[0]);

使用 ImportRow 時須注意:

  • 呼叫 NewRow 會使用現有資料表結構描述,將資料列加入至資料表,但具有資料列的預設值,並將 DataRowState 設定為 Detached。
  • 呼叫 ImportRow 會保留現有的 DataRowState,以及資料列中的值。
  • 新資料行會加入至資料表的結尾。
  • 如果匯入的資料行違反條件約束 (如:PK),會引發 Exception,不會被新增到資料表中。

DataColumn 物件

DataColumn 是用來表示 DataTable 中資料行的結構描述。

底下是 DataColumn 預設的屬性值:

  • DataType :預設值為 String
  • MaxLength :預設值為 -1 (表示不執行最大長度的檢查)
  • Unique :預設值為 False
  • AllowDBNull :預設值為 True
  • Caption :預設值為 ColumnName 的值。
  • AutoIncrement :資料行是否自動遞增資料行的值。預設值為 false。
  • Expression :設定運算式的欄位。

建立 DataTable 的 PK 欄位

Employee.PrimaryKey = new DataColumn[] { col1 };

篩選 DataTable

使用 DataTable.Select()

DataTable.Select 可以用來取得符合篩選條件的所有 DataRow 物件的陣列。

protected void btnDataTableSelect_Click(object sender, EventArgs e)
{
    // load test data
    DataTable dataTable = TestData.CreateEmployeeDataTable();
    TestData.SetEmployeeData(dataTable);

    // set filter expressiong
    string expression = "FirstName = 'AAA'";
    DataRow[] foundRows = dataTable.Select(expression);

    // import filter result to new datatable
    DataTable tableFiltered = dataTable.Clone();  // 複製datatable結構描述。
    foreach (DataRow row in foundRows)
    {
        tableFiltered.ImportRow(row);
    }

    gv1.DataSource = tableFiltered;
    gv1.DataBind();
}

使用 DataView.RowFilter()

RowFilter 是 DataView 物件的屬性,用來設定該 DataView 的篩選條件。 你可以透過 RowFilter 屬性的設定,取得符合篩選條件的 DataView 物件。

protected void btnDataViewRowFilter_Click(object sender, EventArgs e)
{
    // load test data
    DataTable dtEmpData = TestData.CreateEmployeeDataTable();
    TestData.SetEmployeeData(dtEmpData);

    // Use the RowFilter method of DataView object to find all rows matching the filter.
    dtEmpData.DefaultView.RowFilter = "FirstName = 'AAA'";
    DataTable dtFiltered = dtEmpData.DefaultView.ToTable();

    // 使用篩選後的 datatable 當做資料來源
    gv1.DataSource = dtFiltered;
    gv1.DataBind();

    // DataView 物件也可以直接當做資料來源
    dtEmpData.DefaultView.RowFilter = "FirstName = 'AAA'";
    gv1.DataSource = dtEmpData.DefaultView;
    gv1.DataBind();
}

使用 DataRow 物件

DataRow 是用來表示 DataTable 的欄位定義,也就因此它必須由 DataTable 產生。

DataRow 物件的常用方法

當

將資料加入到 DataTable 中

要將資料加入到 DataTable ,可以使用 Add 、 Load 或 LoadDataRow 方法。

Add :將資料逐筆加入到 DataTable 中

public void Add(DataRow row);
public DataRow Add(params object[] values);  //若是自動編號欄位,該欄位必須傳入 null 以取得該資料行的預設值。 
// 1) 先建立 DataRow , 再加入 DataTable

DataRow newemployee = employee.NewRow();
newemployee["EmpNo"] = "123456789A"; 
newemployee["FirstName"] = "Nancy"; 
newemployee["LastName"] = "Davolio"; 
newemployee["Age"] = 30; 
employee.Rows.Add(newemployee);

// 2) 直接在 DataTable 加入一筆資料

employee.Rows.Add("987654321X", "Andrew", "Fuller", 40);

Load :資料來源的值填滿 DataTable。

如果 DataTable 已經包含資料列,則資料來源中的資料將會與現有的資料列合併。 合併的原則,則視 LoadOption 參數決定。

使用 Load 或 LoadDataRow 方法時,可以指定是否更新現有 DataTable 中的資料,所以必須指定 PK 欄位。 當載入的資料與現有的資料具有相同的 PK 時,它就會根據 LoadOption 參數值做不同的處理。

public void Load(IDataReader reader);
public void Load(IDataReader reader, LoadOption loadOption);
public virtual void Load(IDataReader reader, LoadOption loadOption, FillErrorEventHandler errorHandler);
DataTable NewEmployees = CreateDataTable();
NewEmployees.Columns.Remove("LastName and FirstName");
NewEmployees.Rows.Add("1234567890", "AAA", "BBB", 10);
NewEmployees.Rows.Add("1234567891", "CCC", "EEE", 20);
DataTableReader reader = new DataTableReader(NewEmployees);

Employee.Load(reader);

LoadDataRow :尋找並更新特定資料列

如果找不到符合的資料列,則會將該資料列加入到 DataTable 。

public DataRow LoadDataRow(object[] values, bool fAcceptChanges);
public DataRow LoadDataRow(object[] values, LoadOption loadOption);
employee.LoadDataRow(
    new object[] { "1234567891", "CCC", "DDD", 30 },
    LoadOption.OverwriteChanges);       //更新 Current , 且更新 Original

LoadOption

這個列舉值是指明若載入的資料與現有資料 PK 重複時,應如何套用到現有的資料列上。

public enum LoadOption
{
    OverwriteChanges = 1,   // OverwriteRow, Update Original and Current Values 
    PreserveChanges = 2,    // PreserveCurrentValues (default), Update Original Values 
    Upsert = 3,             // UpdateCurrentValues 
}
LoadOption Version Value Changed RowState
OverwriteChanges 以收到的資料列值,更新 Original and Current 。 無論原狀態為何,只要有被更新的列,
其 RowState 屬性會設定為 Unchanged 。
新增的列, RowState 屬性會設定為 Unchanged 。
PreserveChanges 以收到的資料列值,更新 Original 。 被更新的列,
若其 RowState 原本為 Added ,則會設定為 Modified 。
若其 RowState 原本為 Modified ,則會設定為 Modified 。
若其 RowState 原本為 Deleted ,則會保留為 Deleted 。
新增的列, RowState 屬性會設定為 Unchanged 。
Upsert 以收到的資料列值,更新 Current 。 被更新的列,
若其 RowState 原本為 Added ,則會保留為 Added 。
若其 RowState 原本為 Modified ,則會保留為 Modified 。
若其 RowState 原本為 Deleted ,則會設定為 Modified 。
新增的列, RowState 屬性會設定為 Added 。

範例:

//載入測試資料:Modify , Unchanged 和 Added 各二筆
DataTable dTable = CreateDataTable();
dTable.Rows.Add("001", "AAA", "aaa", 10);   //Modified
dTable.Rows.Add("002", "BBB", "bbb", 20);   //Unchanged
dTable.AcceptChanges();
dTable.Rows.Add("003", "CCC", "ccc", 30);   //Added
dTable.Rows.Add("004", "DDD", "ddd", 40);   //Added
dTable.Rows[0][3] = 15;
dTable.Rows[2][3] = 35;

dTable.LoadDataRow(new object[] { "001", "AAA", "aaa", 11 }, LoadOption.PreserveChanges);
dTable.LoadDataRow(new object[] { "003", "CCC", "ccc", 31 }, LoadOption.PreserveChanges);
dTable.LoadDataRow(new object[] { "005", "EEE", "eee", 51 }, LoadOption.PreserveChanges);

dTable.LoadDataRow(new object[] { "001", "AAA", "aaa", 11 }, LoadOption.OverwriteChanges);
dTable.LoadDataRow(new object[] { "003", "CCC", "ccc", 31 }, LoadOption.OverwriteChanges);
dTable.LoadDataRow(new object[] { "005", "EEE", "eee", 51 }, LoadOption.OverwriteChanges);

dTable.LoadDataRow(new object[] { "001", "AAA", "aaa", 11 }, LoadOption.Upsert);
dTable.LoadDataRow(new object[] { "003", "CCC", "ccc", 31 }, LoadOption.Upsert);
dTable.LoadDataRow(new object[] { "005", "EEE", "eee", 51 }, LoadOption.Upsert);




DataRowState

DataRowState 這個列舉值是用來取得 DataRow 物件的狀態。

public enum DataRowState
{
    Detached = 1,   // DataRow is created but not added to a DataTable.
    Unchanged = 2,  // DataRow has not changed since the last call to the AcceptChanges method.
    Added = 4,      // DataRow is added to a DataTable.
    Deleted = 8,    // DataRow is deleted using the Delete method of the DataRow.
    Modified = 16,  // DataRow has been modified since the last time the AcceptChanges method was called.
}

DataRowVersion

DataRowVersion 這個列舉值是用來描述 DataRow 的版本。

public enum DataRowVersion
{
    Original = 256,     // 資料列含有原始值(original value)。
    Current = 512,      // 資料列含有目前的值(current value)。
    Proposed = 1024,    // 資料列含有建議值(proposed value)。
    Default = 1536,     // 當 RowState 為 Added、Modified 時,其預設的版本 Current ; 當 DataRowState 為 Detached 時,版本則為 Proposed。
}

DataRow 的版本會隨下列狀況下變更:

取得 DataRow 不同的版本。

if (dRow.HasVersion(DataRowVersion.Original))
    myDebug.Write( dRow["Age", DataRowVersion.Original].ToString() ); 

if (dRow.HasVersion(DataRowVersion.Proposed))
    myDebug.Write( dRow["Age", DataRowVersion.Proposed].ToString() ); 

DataRow 的預設版本:

範例1

DataTable dTable = CreateDataTable();

DataRow dRow = dTable.NewRow();
dRow["EmpNo"] = "12345";
dRow["FirstName"] = "vito";
dRow["LastName"] = "shao";
dRow["Age"] = 10;
ShowState("1)After DataTable.NewRow", dTable, dRow);

dTable.Rows.Add(dRow);
ShowState("2)After DataTable.Rows.Add", dTable, dRow);

dRow.AcceptChanges();
ShowState("3)After Add + AcceptChanges", dTable, dRow);

dRow.BeginEdit();
dRow["Age"] = 22;
ShowState("4)After BeginEdit", dTable, dRow);

dRow.CancelEdit();
ShowState("5)After CancelEdit", dTable, dRow);

dRow.BeginEdit();
dRow["Age"] = 33;
dRow.EndEdit();
dRow.CancelEdit();
ShowState("6)After BeginEdit+EndEdit+CancelEdit", dTable, dRow);

dRow["Age"] = 44;
dTable.RejectChanges();
ShowState("9)After modify + RejectChanges", dTable, dRow);

dRow["Age"] = 55;
dRow.AcceptChanges();
ShowState("7)After modify + AcceptChanges", dTable, dRow);

dRow.Delete();
ShowState("8)After Delete", dTable, dRow);

dTable.AcceptChanges();
ShowState("9)After Delete + AcceptChanges", dTable, dRow);

//                                        RowState   RowCounts   Age    Default Current Original Proposed
//                                        =========  ==========  ===    ======= ======= ======== ========
//1)After DataTable.NewRow                Detached    0                   10                       10       
//2)After DataTable.Rows.Add              Added       1           10      10      10                        
//3)After Add + AcceptChanges             Unchanged   1           10      10      10      10                
//4)After BeginEdit                       Unchanged   1           22      22      10      10       22 (Default=Proposed)
//5)After CancelEdit                      Unchanged   1           10      10      10      10                
//6)After BeginEdit+EndEdit+CancelEdit    Modified    1           33      33      33      10                
//9)After modify + RejectChanges          Unchanged   1           10      10      10      10                
//7)After modify + AcceptChanges          Unchanged   1           55      55      55      55                
//8)After Delete                          Deleted     1                                   55                
//9)After Delete + AcceptChanges          Detached    0              

範例2

DataTable Employees = CreateDataTable();
Employees.Rows.Add("001", "AAA", "BBB", 10);
Employees.Rows.Add("002", "CCC", "DDD", 20);
Employees.AcceptChanges();

DataRow emp = Employees.Rows[1];

DataRowState rowState;
String LastName = "";

rowState = emp.RowState;    //Unchanged
            
emp["LastName"] = "EEE";
rowState = emp.RowState;    //Modified

emp.AcceptChanges();
rowState = emp.RowState;    //Unchanged

emp.BeginEdit();
emp["LastName"] = "FFF";
rowState = emp.RowState;    //Unchanged

LastName = emp["LastName"].ToString();

if (emp.HasVersion(DataRowVersion.Current))
    LastName = emp["LastName", DataRowVersion.Current].ToString();

if (emp.HasVersion(DataRowVersion.Original))
    LastName = emp["LastName", DataRowVersion.Original].ToString(); 

if (emp.HasVersion(DataRowVersion.Proposed))
    LastName = emp["LastName", DataRowVersion.Proposed].ToString(); 

emp.EndEdit();
rowState = emp.RowState;    //Modified

if (emp.HasVersion(DataRowVersion.Current))
    LastName = emp["LastName", DataRowVersion.Current].ToString();

if (emp.HasVersion(DataRowVersion.Original))
    LastName = emp["LastName", DataRowVersion.Original].ToString();

if (emp.HasVersion(DataRowVersion.Proposed))
    LastName = emp["LastName", DataRowVersion.Proposed].ToString();

//                   RowState   LastName   Current Original Proposed
//                   =========  ========== ======= ======== ========
//1)After BeginEdit  Unchanged  EEE        EEE     EEE      FFF
//2)After EndEdit    Modified   FFF        FFF     EEE      無

轉換 DataTable 與 XML 資料

DataTable 轉換 XML 相關的方法

如何將 DataTable 轉成 XML 文件

DataTable employee = CreateDataTable();
employee.Rows.Add("987654321X", "vito", "shao", 40);
employee.Rows.Add("A123456789", "allen", "wu", 30);

employee.WriteXml(Server.MapPath("employee1.xml"));
Response.Redirect("employee.xml");

下面為輸出結果,其中,由於項目名稱不允許空白,所以自動使用 _x0020_ 取代。

<?xml version="1.0" standalone="yes"?>
<DocumentElement>
  <Employee>
    <EmpNo>987654321X</EmpNo>
    <FirstName>vito</FirstName>
    <LastName>shao</LastName>
    <Age>40</Age>
    <LastName_x0020_and_x0020_FirstName>shao,vito</LastName_x0020_and_x0020_FirstName>
  </Employee>
  <Employee>
    <EmpNo>A123456789</EmpNo>
    <FirstName>allen</FirstName>
    <LastName>wu</LastName>
    <Age>30</Age>
    <LastName_x0020_and_x0020_FirstName>wu,allen</LastName_x0020_and_x0020_FirstName>
  </Employee>
</DocumentElement>

如何控制 XML 文件的樣式

變更 DataColumn 物件的 ColumnMapping 屬性,可以控制 XML 文件輸出的樣式

MappingType 列舉值:

  • Attribute :將欄位對應至 XML 的屬性 (Attribute)。
  • Element :將欄位對應至 XML 的項目。(預設)
  • Hidden :不對應欄位。
  • SimpleContent :將欄位對應至 XmlText 的節點。
DataTable employee = CreateDataTable();
    employee.Rows.Add("987654321X", "vito", "shao", 40);
    employee.Rows.Add("A123456789", "allen", "wu", 30);

    DataColumnCollection dColumns = employee.Columns;
    dColumns["EmpNo"].ColumnMapping = MappingType.Attribute;                //將 EmpNo 以屬性型式輸出
    dColumns["FirstName"].ColumnMapping = MappingType.Hidden;               //忽略輸出 FirstName 欄位
    dColumns["LastName"].ColumnMapping = MappingType.Hidden;                //忽略輸出 LastName 欄位
    dColumns["Age"].ColumnMapping = MappingType.Hidden;                     //忽略輸出 Age 欄位
    dColumns["LastName and FirstName"].ColumnName = "FullName";

    employee.WriteXml(Server.MapPath("employee2.xml"));
    Response.Redirect("employee2.xml");

下面為輸出結果:

<?xml version="1.0" standalone="yes"?>
<DocumentElement>
  <Employee EmpNo="987654321X">
    <FullName>shao,vito</FullName>
  </Employee>
  <Employee EmpNo="A123456789">
    <FullName>wu,allen</FullName>
  </Employee>
</DocumentElement>

如何將 DataTable 轉成含 Schema 的 XML 文件

上面的例子,我們輸出的 XML 文件並沒有包含資料的型別,所以所有的欄位資料都會被認定為字串。 若要在輸出的 XML 文件包含 Schema 的話,只要在輸出時,多加指定 XmlWriteMode 屬性即可。

DataTable employee = CreateDataTable();
employee.Rows.Add("987654321X", "vito", "shao", 40);
employee.Rows.Add("A123456789", "allen", "wu", 30);

DataColumnCollection dColumns = employee.Columns;
dColumns["EmpNo"].ColumnMapping = MappingType.Attribute;                //將 EmpNo 對應成屬性
dColumns["LastName and FirstName"].ColumnMapping = MappingType.Hidden;  //忽略 FullName 欄位

employee.WriteXml(Server.MapPath("employee3.xml"), XmlWriteMode.WriteSchema);
Response.Redirect("employee3.xml");

下面為輸出結果:

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="Employee" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Employee">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="FirstName" msdata:Ordinal="1">
                  <xs:simpleType>
                    <xs:restriction base="xs:string">
                      <xs:maxLength value="20" />
                    </xs:restriction>
                  </xs:simpleType>
                </xs:element>
                <xs:element name="LastName" msdata:Ordinal="2">
                  <xs:simpleType>
                    <xs:restriction base="xs:string">
                      <xs:maxLength value="20" />
                    </xs:restriction>
                  </xs:simpleType>
                </xs:element>
                <xs:element name="Age" type="xs:short" default="0" minOccurs="0" msdata:Ordinal="3" />
              </xs:sequence>
              <xs:attribute name="EmpNo" msdata:DefaultValue="" use="required">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="10" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
              <xs:attribute name="LastName_x0020_and_x0020_FirstName" msdata:ReadOnly="true" msdata:Expression="LastName + ',' + FirstName" use="prohibited">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="40" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:attribute>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
      <xs:unique name="Constraint1" msdata:PrimaryKey="true">
        <xs:selector xpath=".//Employee" />
        <xs:field xpath="@EmpNo" />
      </xs:unique>
    </xs:element>
  </xs:schema>
  <Employee EmpNo="987654321X">
    <FirstName>vito</FirstName>
    <LastName>shao</LastName>
    <Age>40</Age>
  </Employee>
  <Employee EmpNo="A123456789">
    <FirstName>allen</FirstName>
    <LastName>wu</LastName>
    <Age>30</Age>
  </Employee>
</NewDataSet>

將 XML 文件轉 DataTable

透過 DataTable.ReadXml 方法,可以輕易的將 XML 文件轉入到 DataTable 。 若這個 XML 文件不含 shcema ,則必須先建立 DataTable 的樣式,而且要注意,讀取資料時,若資料型別無法對應,也會引發例外狀況。 若使用含 shcema 的 XML 文件,則可以不用建立 DataTable 的樣式,就可以直接取得和原本一模一樣的 DataTable 物件。

public XmlReadMode ReadXml(Stream stream);
public XmlReadMode ReadXml(string fileName);
public XmlReadMode ReadXml(TextReader reader);
public XmlReadMode ReadXml(XmlReader reader);
protected void btnReaXML1_Click(object sender, EventArgs e)
{
    DataTable Employee = CreateDataTable();                //先建立 DataTable 樣式
    Employee.ReadXml(Server.MapPath("employee1.xml"));  //讀取原始的 XML 文件
    gv1.DataSource = Employee;
    gv1.DataBind();
}
protected void btnReaXML2_Click(object sender, EventArgs e)
{
    DataTable xmlTable = new DataTable();               //不須先建立 DataTable 樣式
    xmlTable.ReadXml(Server.MapPath("employee3.xml"));  //可讀取樣式的 XML 文件
    gv1.DataSource = xmlTable;
    gv1.DataBind();
}

使用 DataView 物件

DataView 物件

DataView 就像是 DataTable 的檢視表。主要功能可以用它來排序、篩選、搜尋資料。

DataView 建構子

public DataView();
public DataView(DataTable table);
public DataView(DataTable table, string RowFilter, string Sort, DataViewRowState RowState);

DataView 屬性

DataView 方法

  • AddNew :加入新的資料列至 DataView。
  • Delete :刪除指定索引處的資料列。
  • Find :根據指定的排序鍵值,在 DataView 中尋找資料列。
  • FindRows :傳回其資料行符合指定排序鍵值的 DataRowView 物件陣列。
  • ToTable :根據現有 DataView 中的資料列,建立新的 DataTable。
  • CopyTo :將項目複製到陣列。 只針對 Web Form 介面。

排序 DataView

  • 若設定 ApplyDefaultSort = true ,會使用 PK 欄位做遞增排序。
  • 若有指定 Sort 屬性,則會依據該條件排序。
DataTable employee = CreateDataTable();
employee.Rows.Add("222", "vito", "shao", 40);
employee.Rows.Add("333", "allen", "wu", 30);
employee.Rows.Add("111", "shao", "vito", 35);

DataView myDataView = new DataView(employee);

myDataView.ApplyDefaultSort = true;

GridView1.DataSource = myDataView;
GridView1.DataBind();
DataView myDataView = new DataView(employee);

myDataView.Sort = "FirstName DESC, Age ASC";

GridView1.DataSource = myDataView;
GridView1.DataBind();

篩選 DataView

DataView.RowFilter 屬性是用來過濾資料用,其使用語法類似 SQL 中的 Where 子句,但是不用 Where 這個關鍵字。

DataView myDataView = new DataView(employee);

myDataView.RowFilter = " Age > 30 and FirstName like '%A%' ";
myDataView.Sort = "FirstName DESC";

GridView1.DataSource = myDataView;
GridView1.DataBind();

搜尋 DataView

使用 Find 方法前,必須先指定排序的欄位,因為它是依照排序的欄位進行搜尋,並回傳第一筆符合條件資料的索引。

protected void btnFind_Click(object sender, EventArgs e)
{
    DataTable employee = CreateDataTable();
    employee.Rows.Add("222", "vito", "shao", 40);
    employee.Rows.Add("333", "allen", "wu", 30);
    employee.Rows.Add("111", "shao", "vito", 35);

    DataView myDataView = new DataView(employee);

    myDataView.Sort = "Age ASC";

    int iRowIndex = myDataView.Find("30");

    if (iRowIndex == -1)
    {
        myDebug.WriteLine("找不到符合條件的資料");
    }
    else
    {
        DataTable dTable = CreateDataTable();
        DataRow dRow = dTable.NewRow();
            
        foreach (DataColumn col in employee.Columns)
        {
            if (!col.ReadOnly)
                dRow[col.ToString()] = myDataView[iRowIndex][col.ToString()];
        }
        dTable.Rows.Add(dRow);

        GridView1.DataSource = dTable;
        GridView1.DataBind();
    }
}

使用多欄位條件搜尋

myDataView.Sort = "FirstName, LastName";

// Find FirstName + LastName
object[] vals = new object[2];
vals[0] = "vito";
vals[1] = "shao";

int iRowIndex = myDataView.Find(vals);

使用 DataSet 物件

DataSet 物件

DataSet 是一種使用記憶體來存放關聯性資料的物件。其中包含了 DataTable 和 DataRelation 物件。 透過 DataRelation 物件可以用來建立兩個 DataTable 之間的父子關聯性。

如何建立 DataSet 與 DataTable 與 DataRelation

private DataSet CreateDataSet()
{
    //==========================================================
    // 在 DataSet 中,加入 DataTable 
    //==========================================================

    DataTable dtCompany = new DataTable("Company");
    dtCompany.Columns.Add("CompanyId", typeof(Guid));
    dtCompany.Columns.Add("CompanyName", typeof(string));
    dtCompany.PrimaryKey = new DataColumn[] { dtCompany.Columns["CompanyId"] };

    DataTable dtEmployee = new DataTable("Employee");
    dtEmployee.Columns.Add("EmployeeId", typeof(Guid));
    dtEmployee.Columns.Add("CompanyId", typeof(Guid));
    dtEmployee.Columns.Add("LastName", typeof(string));
    dtEmployee.Columns.Add("FirstName", typeof(string));
    dtEmployee.Columns.Add("Salary", typeof(decimal));
    dtEmployee.PrimaryKey = new DataColumn[] { dtEmployee.Columns["EmployeeId"] };
    
    DataSet dsCompanyData = new DataSet("CompanyList");
    dsCompanyData.Tables.Add(dtCompany);
    dsCompanyData.Tables.Add(dtEmployee);

    //==========================================================
    // 在 DataSet 中,加入 DataRelation 
    //==========================================================

    DataRelation rlCompanyEmployee = 
        new DataRelation("Company_Employee", dtCompany.Columns["CompanyId"], dtEmployee.Columns["CompanyId"]);
    dsCompanyData.Relations.Add(rlCompanyEmployee);

    return dsCompanyData;
}

如何將 DataSet 繫結到控制項

  1. 設定控制項的 DataSource 屬性。
  2. 叫用 DataBind 方法。
  3. 可以使用控制項的 DataMember 屬性,指定繫結到特定的 DataTable ,若沒有指定,會繫結到第一個 DataTable 。
gv1.DataSource = ds;
gv1.DataMember = "Company";
gv1.DataBind();

gv2.DataSource = ds;
gv2.DataMember = "Employee";
gv2.DataBind();

DataRelation 物件

DataRelation 是用來表示 DataTable 之間的關聯性。 透過 DataRelation 可以由 Parent DataTable 關聯到 Child DataTable ,反之亦可。

//建立測試資料
DataSet companyList = CreateDataSet();
PopulateDataSet(companyList);

//取得資料關聯性
DataRelation dRelation = companyList.Relations["Company_Employee"];

//取出父資料表中的一筆公司資料
DataRow companyParent = companyList.Tables["company"].Rows[1];

//透過關連取出與該公司相關聯的員工資料
string tmp = "";
foreach (DataRow employeeChild in companyParent.GetChildRows(dRelation))
{
    tmp = employeeChild["LastName"] + " " +
            employeeChild["FirstName"] + " " +
            string.Format("{0:C}", employeeChild["Salary"]);
    myDebug.WriteLine(tmp);
}

建立條件約束 (Key Constraint)

若父資料表有一個 Primary Key ,通常我們會在子資料表建立 Foreing Key ,以便約束索引欄位的正確性。 或者當父資料表有資料被刪除時,我們希望子資料表中相關聯的資料也自動被刪除,類似這樣的需求都可以透過索引鍵條件約束 (Key Constraint) 來達成。

UniqueConstraint

UniqueConstraint 這個物件可以用來表示欄位值必須唯一的限制。

ForeignKeyConstraint

ForeignKeyConstraint 這個物件就是用來建立 Fk 的約束行為。以下是它的幾個方法:

  • DeleteRule :設定資料列刪除時會跨越這個條件約束發生的動作。
  • UpdateRule :設定資料列更新時會跨越這個條件約束發生的動作。
  • AcceptRejectRule :指示在叫用 AcceptChanges 時應該跨越這個條件約束來發生的動作。
protected void btnCascadingUpdateDelete_Click(object sender, EventArgs e)
{
    //父層資料表:建立 Unique Key
    //子層資料表:建立 Foreign Key
    DataSet companyList = CreateConstraintDataSet();
    PopulateDataSet(companyList);

    //刪除父層資料表中的資料,則子層資料表中的相關資料也會被刪除
    companyList.Tables["Company"].Rows[0].Delete();
    BindingDataSet(companyList);
}

private DataSet CreateConstraintDataSet()
{
    DataTable dtCompany = new DataTable("Company");
    dtCompany.Columns.Add("CompanyId", typeof(Guid));
    dtCompany.Columns.Add("CompanyName", typeof(string));
    dtCompany.PrimaryKey = new DataColumn[] { dtCompany.Columns["CompanyId"] };

    DataTable dtEmployee = new DataTable("Employee");
    dtEmployee.Columns.Add("EmployeeId", typeof(Guid));
    dtEmployee.Columns.Add("CompanyId", typeof(Guid));
    dtEmployee.Columns.Add("LastName", typeof(string));
    dtEmployee.Columns.Add("FirstName", typeof(string));
    dtEmployee.Columns.Add("Salary", typeof(decimal));
    dtEmployee.PrimaryKey = new DataColumn[] { dtEmployee.Columns["EmployeeId"] };

    //======================================================
    // 建立 DataSet 
    //======================================================

    DataSet dsCompanyData = new DataSet("CompanyList");
    dsCompanyData.Tables.Add(dtCompany);
    dsCompanyData.Tables.Add(dtEmployee);

    //======================================================
    // 建立 UniqueConstraint 
    //======================================================

    //UniqueConstraint UKey = new UniqueConstraint("UK_CompanyId", dtCompany.Columns["CompanyId"]);
    //dtCompany.Constraints.Add(UKey);      // (注意:該欄位若為 PK , 則無法再建立 unique )

    //======================================================
    // 建立 ForeignKeyConstraint 
    //======================================================

    ForeignKeyConstraint FKey = new ForeignKeyConstraint("Fk_CompanyId",
        dtCompany.Columns["CompanyId"], dtEmployee.Columns["CompanyId"]);

    FKey.DeleteRule = Rule.Cascade;         //設定若父層刪除,子層也跟著刪除 
    FKey.UpdateRule = Rule.Cascade;         //設定若父層變更,子層也跟著變更
    FKey.AcceptRejectRule = AcceptRejectRule.Cascade;

    dtEmployee.Constraints.Add(FKey);

    return dsCompanyData;
}

序列化 DataSet 物件

  • WriteXml :將目前 DataSet 中的資料寫入指定的 XML 檔案。若參數 XmlWriteMode 設成 WriteSchema ,可連同 DataSet 的結構描述一起輸出。
  • WriteXmlSchema :將目前 DataSet 的結構描述寫入指定的 XML 檔案。
  • ReadXml :讀取指定檔案中的 XML 結構描述和資料。
  • ReadXmlSchema :讀取指定檔案中的 XML 結構描述。

將 DataSet 輸出成 XML 文件

//建立測試資料
DataSet companyList = CreateDataSet();
PopulateDataSet(companyList);

//輸出 DataSet 中的資料到 XML 文件
companyList.WriteXml(MapPath("CompanyList.xml"));

//輸出 DataSet 中的資料和結構描述到 XML 文件
companyList.WriteXml(MapPath("CompanyListSchema.xml"), XmlWriteMode.WriteSchema);

//輸出 DataSet 中的結構描述到 XML 文件
companyList.WriteXmlSchema(MapPath("CompanyListSchema.xsd"));

將 DataSet 輸出成巢狀的 XML 文件

上面的例子,其輸出的 XML 文件會把 Company 資料會全部集中在一起, Employee 資料也會集中在一起。 如果我們希望在某一筆公司資料底下顯示該公司的員工資料,就可以用設定成巢狀 (Nested) 的 XML 文件。 當然啦,這個功能是由 DataSet 中的 DataRelation 所控制的。

//建立測試資料
DataSet companyList = CreateDataSet();
PopulateDataSet(companyList);

//設定巢狀的 DataRelation
companyList.Relations["Company_Employee"].Nested = true;

//輸出
companyList.WriteXml(MapPath("CompanyList.xml"));

將異動過的 DataSe 輸出成 XML 文件

由前面的說明,我們知道, DataSet 中包含一個稱為 DataRowVersion 的資訊,可用來表示更改前後的不同資訊。 若希望輸出的 XML 文件包含所有的資料,則可以將參數 XmlWriteMode 設成 DiffGram 。

XmlWriteMode 列舉:

  • WriteSchema :輸出內容包含資料和結構描述。
  • IgnoreSchema :輸出內容僅含資料。
  • DiffGram :輸出內容包括原始和目前的值。

範例:

//建立測試資料
DataSet companyList = CreateDataSet2();
PopulateDataSet2(companyList);

companyList.Tables["Employee"].AcceptChanges();
companyList.Tables["Employee"].Rows[1]["Salary"] = 0;

// XML 文件
companyList.WriteXml(MapPath("Employee.xml"));

// DiffGram XML 文件
companyList.WriteXml(MapPath("EmployeeDiffGram.xml"), XmlWriteMode.DiffGram);

下面為輸出結果:
第10行顯示該筆資料的狀態為 Modified 。
而文件的下半部,則顯示未更改的原始資料。

<?xml version="1.0" standalone="yes"?>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
  <CompanyList>
    <Employee diffgr:id="Employee1" msdata:rowOrder="0">
      <EmployeeId>001</EmployeeId>
      <LastName>AAA</LastName>
      <FirstName>aaa</FirstName>
      <Salary>40</Salary>
    </Employee>
    <Employee diffgr:id="Employee2" msdata:rowOrder="1" diffgr:hasChanges="modified">
      <EmployeeId>002</EmployeeId>
      <LastName>BBB</LastName>
      <FirstName>bbb</FirstName>
      <Salary>0</Salary>
    </Employee>
    <Employee diffgr:id="Employee3" msdata:rowOrder="2">
      <EmployeeId>003</EmployeeId>
      <LastName>CCC</LastName>
      <FirstName>ccc</FirstName>
      <Salary>20</Salary>
    </Employee>
  </CompanyList>
  <diffgr:before>
    <Employee diffgr:id="Employee2" msdata:rowOrder="1">
      <EmployeeId>002</EmployeeId>
      <LastName>BBB</LastName>
      <FirstName>bbb</FirstName>
      <Salary>70</Salary>
    </Employee>
  </diffgr:before>
</diffgr:diffgram>

由上面例子可以知道 DiffGram 會輸出整個 DataSet 中的資訊,包含變更過和沒變更過的資料。 若只希望輸出的 XML 文件僅含變更過的資訊,可以使用 GetChanges 方法,取得變更資料集的複本,再輸出成 XML 文件。

還原序列化 DataSet 物件

//讀取結構描述
DataSet companyList1 = new DataSet();
companyList1.ReadXmlSchema(MapPath("CompanyListSchema.xml"));

//讀取資料檔
companyList1.ReadXml(MapPath("CompanyListData.xml"));

//同時讀取結構描述和資料檔 
DataSet companyList2 = new DataSet();
companyList2.ReadXml(MapPath("CompanyListDataSchema.xml"));

二進位序列化與還原序列化 DataSet 物件

  • 二進位序列化約有 20K 的初始化額外資訊,所以序列化小型的 DataSet 時,檔案不一定會比較小。
  • 二進位序列化會自動包含結構描述,所以還原序列化時不需要先載入結構描述。
// 使用 BinaryFormatter 序列化 DataSet
protected void btnBinarySerializing_Click(object sender, EventArgs e)
{
    DataSet companyList = CreateDataSet();
    PopulateDataSet(companyList);

    // 設定輸出成二進位格式
    companyList.RemotingFormat = SerializationFormat.Binary;

    using (FileStream fs = new FileStream(MapPath("CompanyList.bin"), FileMode.Create))
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(fs, companyList);
    }
}

// 使用 BinaryFormatter 還原序列化 DataSet
protected void btnBinaryDeserializing_Click(object sender, EventArgs e)
{
    DataSet companyList;
            
    using (FileStream fs = new FileStream(MapPath("CompanyList.bin"), FileMode.Open))
    {
        BinaryFormatter formatter = new BinaryFormatter();
        companyList = (DataSet)formatter.Deserialize(fs);
    }
    BindingDataSet(companyList);
}

合併 DataSet

public void Merge(DataRow[] rows);
public void Merge(DataSet dataSet);
public void Merge(DataTable table);
public void Merge(DataSet dataSet, bool preserveChanges);
public void Merge(DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction);
  • dataSet:合併對象。
    當使用 A.Merge(B) 時,表示 A 資料集合併 B 資料集,B 資料集的資料將被併入到 A 資料集中。
  • preserveChanges:若要保留目前 DataSet 中的變更,則為 true,否則為 false。
    當 key 重複時,預設會將 B 資料集中的資料覆寫到 A 資料集中。
    若希望保留 A 資料集的資料,不希望被覆寫,設定 preserveChanges 參數為 true 即可。
  • missingSchemaAction:合併時,若發生欄位不相符時的處理原則。
DataSet companyList1 = CreateDataSet2();
PopulateDataSet2(companyList1);

DataSet companyList2 = CreateDataSet2();
PopulateDataSet3(companyList2);

companyList1.AcceptChanges();
companyList2.AcceptChanges();

companyList1.Tables["Employee"].Rows[0]["Salary"] = 0;
companyList1.Merge(companyList2);   

//雖然 companyList1 資料中的 Salary 已改成 0, 但合併後,會被 companyList2 覆寫

companyList1.Merge(companyList2, true);         //若 preserveChanges 設成 true , 則不會覆寫

如何由現有的 Datatable 產生 Group By 的 Datatable

  • 1. 先建立 Group By 欄位的 Datatable
  • 2. 再透過 Relaction 欄位求加總或平均。

下面例子,假說有個員工資料表,你希望依據現有 DataTable 中的資料,以 FirstName 為群組欄位,求得平均年齡。

//原始資料
DataTable employees = TestData.GetEmployeeDataTable();

//利用原始資料, 建立一個 Distinct FirstName 欄位的 DataTable
DataTable grouped = employees.DefaultView.ToTable("GEmployee", true, new[] { "FirstName" });

DataSet ds = new DataSet("myDataSet");
ds.Tables.Add(employees);
ds.Tables.Add(grouped);

//建立這二個 datatable 的關連
ds.Relations.Add("Relation1", grouped.Columns["FirstName"], employees.Columns["FirstName"]);

//藉由關連欄位,取得子表單的總計 Child 是關鍵字
grouped.Columns.Add("AvgAge", typeof(int), "Avg(Child.Age)");

如何具名 DataSet 中的 DataTable

當透過查詢取得一個含有多個 DataTable 的 DataSet ,通常我們會使用索引值取得每一個 DataTable , 如果你想要讓每一個 DataTable 都有一個名字,可以使用以下的方法:

取得 DataSet 後為每個 DataTable 命名

ds.Tables[0].TableName = "Orders";
    ds.Tables[1].TableName = "OrderDetails";

建立 DataTable 對應表

myDataAdapter.TableMappings.Add("Table1", "Orders");
    myDataAdapter.TableMappings.Add("Table2", "OrderDetails");
    myDataAdapter.Fill(myDataSet);

沒有留言:

張貼留言