Thứ Hai, 5 tháng 8, 2013

Các Sơ đồ XML (Lập trình C# với CSDL)

XML là một đường hào vững chắc bao bọc ADO.NET - thật vậy, các định dạng điều khiển cho việc truyền dữ liệu hiện tại là XML. Với thời gian chạy .NET, nó có thể mô tả một DataTable trong một file sơ đồ XML. Hơn thế nữa, bạn có thể định nghĩa một DataSet, với một số DataTables, một bộ các quan hệ giữa các bảng, bao gồm các chi tiết và mô tả đây đủ của dữ liệu.

Khi bạn có một file XSD, có một công cụ trong thời gian chạy để chuyển sơ đồ này thành các lớp dữ liệu tương ứng, chẳng hạn như một lớp DataTable ở trên. Trong phần này chúng ta sẽ bắt đầu với một file XSD đơn giản dùng để mô tả các thông tin tương tự ví dụ Products ở trên, và sau đó tạo ra một vài tính năng mở rộng. File này là Products.xsd, trong thư mục 10_XSD_DataSet:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema 
   id="Products" 
   targetNamespace="http://tempuri.org/XMLSchema1.xsd" 
   elementFormDefault="qualified" 
   xmlns="http://tempuri.org/XMLSchema1.xsd" 
   xmlns:mstns="http://tempuri.org/XMLSchema1.xsd" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
   xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="Product">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ProductID" type="xs:int" />
        <xs:element name="ProductName" type="xs:string" />
        <xs:element name="SupplierID" type="xs:int" minOccurs="0" />
        <xs:element name="CategoryID" type="xs:int" minOccurs="0" />
        <xs:element name="QuantityPerUnit" type="xs:string" minOccurs="0" />
        <xs:element name="UnitPrice" type="xs:decimal" minOccurs="0" />
        <xs:element name="UnitsInStock" type="xs:short" minOccurs="0" />
        <xs:element name="UnitsOnOrder" type="xs:short" minOccurs="0" />
        <xs:element name="ReorderLevel" type="xs:short" minOccurs="0" />
        <xs:element name="Discontinued" type="xs:boolean" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
Chúng ta sẽ xem xét kĩ trong chương 11; còn bây giờ, file này cơ bản định nghĩa mọt sơ đồ với các thuộc tính id tạo thành Products. Một kiểu phức tạp Product đã được định nghĩa, để chứa một số các yếu tố, cho mỗi trường trong bảng Products.
Cảm ơn .NET Framework đã công cụ XSD.EXE để tạo ra tất cả các mã cho các lớp này chỉ cần một file nhập XSD. 

Tạo mã với XSD

Bạnc có thể lưu file trên với tên Product.xsd, và chuyển nó thành mã với lệnh sau:
xsd Product.xsd /d
Nó sẽ tạo ra file Product.cs.
Có một vài cách có thể dùng XSD để thay đổi output generated. Một vài cách phổ biến được đưa ra trong bảng sau.
Switch
Description
/dataset (/d)
Các lớp được thừa kế từ DataSet, DataTable,DataRow.
/language:<language>
Cho phép bạn chon ngôn ngữ để chuyển. C# là giá trị mặc định, nhưng có thể chọn VB cho một file Visual Basic .NET.
/namespace:<namespace>
Định nghĩa không gian tên của code được phát ra. Giá trị mặc định là no namespace.
Một phiên bản ngắn gọn của XSD cho sơ đồ Products được trình bày dưới đây. Tôi đã bỏ đi môt vài mã không cần thiết chỉ giữ lại những gì quan trọng nhất, và làm một vài thao tác định dạng lại để vừa với khổ giấy. Để xem kết quả cuối cùng, chạy XSD.EXE trên sơ đồ Products và xem xet file .cs được tạo ra. Mã ví dụ trong thư mục 10_XSD_DataSet:
//------------------------------------------------------------------------------
// <autogenerated>
//     This code was generated by a tool.
//     Runtime Version: 1.0.3512.0
//
//     Changes to this file may cause incorrect behavior and will be lost if 
//     the code is regenerated.
// </autogenerated>
//------------------------------------------------------------------------------

// 
// This source code was auto-generated by xsd, Version=1.0.3512.0.
// 
using System;
using System.Data;
using System.Xml;
using System.Runtime.Serialization;

[Serializable()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Diagnostics.DebuggerStepThrough()]
[System.ComponentModel.ToolboxItem(true)]
public class Products : DataSet 
{
   private ProductDataTable tableProduct;
   public Products()
   public ProductDataTable Product 
   public override DataSet Clone()
   public delegate void ProductRowChangeEventHandler ( object sender,
                                                       ProductRowChangeEvent e);

   [System.Diagnostics.DebuggerStepThrough()]
   public class ProductDataTable : DataTable, System.Collections.IEnumerable

   [System.Diagnostics.DebuggerStepThrough()]
   public class ProductRow : DataRow
}
Tôi đã hơi tùy tiện trong mã này, tôi đã tách nó thành 3 phần và bỏ đi các thành phần protected và private vì vậy chúng ta chỉ có thể tập trung vào các giao diện chính. Chúng ta sẽ xem xét mã sau trong phần nó về DataSet.
Cấu trúc của Products() gọi một phương thức tĩnh, InitClass(), nó xây dựng một thể hiện của lớp ProductDataTable xuất phát từ lớp DataTable, và thêm nó vào tập hợp các Tables của DataSet. Bảng dữ liệu Products có thể được truy cập như mã dưới đây:
DataSet ds = new Products();
DataTable products = ds.Tables["Products"];
Hoặc, đơn giản hơn bằng cách sử dụng thuộc tính Product, sẵn có trong các đối tượng xuất phát từ DataSet:
DataTable products = ds.Product;
Lớp ProductDataTable bao gồm các mã khác như sau:
[System.Diagnostics.DebuggerStepThrough()]
public class ProductDataTable : DataTable, System.Collections.IEnumerable 
{
   private DataColumn columnProductID;
   private DataColumn columnProductName;
   private DataColumn columnSupplierID;
   private DataColumn columnCategoryID;
   private DataColumn columnQuantityPerUnit;
   private DataColumn columnUnitPrice;
   private DataColumn columnUnitsInStock;
   private DataColumn columnUnitsOnOrder;
   private DataColumn columnReorderLevel;
   private DataColumn columnDiscontinued;
        
   internal ProductDataTable() : base("Product")
   {
      this.InitClass();
  }
Lớp ProductDataTable, được xuất phát từ DataTable và thực thi giao diện IEnumerable, định nghĩa một thể hiện DataColumn tĩnh cho mỗi cột trong bảng. Chúng được khởi động một lần nữa từ cấu tử bởi việc gọi phương thức tĩnh InitClass(). Mỗi cột cung cấp một con trỏ nội, lớp DataRow được mô tả sau.
[System.ComponentModel.Browsable(false)]
public int Count
{
   get { return this.Rows.Count; }
}
internal DataColumn ProductIDColumn
{
   get { return this.columnProductID; }
}
// Other row accessors removed for clarity - there is one for each of the columns
Việc thêm các dòng vào bảng được cung cấp bởi hai quá tải trong phương thức AddProductRow(). Quá tải thứ nhất tạo DataRow và không trả về giá trị nào cả. Quá tải thứ hai là một tập các giá trị, mỗi giá trị dành cho các cột trong DataTable, tạo các giá trị cho một dòng mới, thêm dòng vào DataTable và trả dòng về cho trình gọi. 
public void AddProductRow(ProductRow row)
{
   this.Rows.Add(row);
}

public ProductRow AddProductRow ( string ProductName , int SupplierID ,
                                  int CategoryID , string QuantityPerUnit ,
                                  System.Decimal UnitPrice , short UnitsInStock ,
                                  short UnitsOnOrder , short ReorderLevel ,
                                  bool Discontinued )
{
   ProductRow rowProductRow = ((ProductRow)(this.NewRow()));
   rowProductRow.ItemArray = new object[] 
   {
      null,
      ProductName,
      SupplierID,
      CategoryID,
      QuantityPerUnit,
      UnitPrice,
      UnitsInStock,
      UnitsOnOrder,
      ReorderLevel,
      Discontinued
   };
   this.Rows.Add(rowProductRow);
   return rowProductRow;
}
Giống như thành phần InitClass() trong lớp xuất phát từ DataSet, dùng để thêm bảng vào trong DataSet, thành phần InitClass() trong ProductDataTable thêm các cột vào DataTable. Thuộc tính của mỗi cột được điền thích hợp, và cột sau đó được thêm vào tập hợp columns.
private void InitClass()
{
   this.columnProductID = new DataColumn ( "ProductID", 
                                            typeof(int), 
                                            null,
                                            System.Data.MappingType.Element);
   this.Columns.Add(this.columnProductID);
   // Other columns removed for clarity

   this.columnProductID.AutoIncrement = true;
   this.columnProductID.AllowDBNull = false;
   this.columnProductID.ReadOnly = true;
   this.columnProductName.AllowDBNull = false;
   this.columnDiscontinued.AllowDBNull = false;
}

public ProductRow NewProductRow()
{
    return ((ProductRow)(this.NewRow()));
}
Phương thức cuối cùng mà tôi muốn nói tới, NewRowFromBuilder(), được gọi trực tiếp từ phương thức NewRow() của DataTable. Ở đây nó tạo một dòng định kiểu mạnh. Thể hiện của DataRowBuilder được tạo bởi DataTable, các thành phần của nó chỉ có thể truy sử dụng trong nhị phân System.Data.
protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
{
   return new ProductRow(builder);
}
Và lớp cuối cùng được nói tới là ProductRow xuất phát từ DataRow. Lớp này được dùng để cung cấp cách truy cập bảo vệ kiểu cho tất cả các trường của dữ liệu trong bảng dữ liệu. Nó là một bao một dòng riêng và cung các các thành phần cót thể đọc và viết  cho các cột trong bảng.
Các vùng khả rỗng sẽ được kiểm tra kĩ lưỡng. Ví dụ dưới đây chỉ ra các khả năng của cột SupplierID:
[System.Diagnostics.DebuggerStepThrough()]
public class ProductRow : DataRow
{
   private ProductDataTable tableProduct;
    
   internal ProductRow(DataRowBuilder rb) : base(rb)
   {
      this.tableProduct = ((ProductDataTable)(this.Table));
   }
    
   public int ProductID
   {
      get { return ((int)(this[this.tableProduct.ProductIDColumn])); }
      set { this[this.tableProduct.ProductIDColumn] = value; }
   }
  // Other column accessors/mutators removed for clarity
  
   public bool IsSupplierIDNull()
   {
      return this.IsNull(this.tableProduct.SupplierIDColumn);
   }
    
   public void SetSupplierIDNull()
   {
      this[this.tableProduct.SupplierIDColumn] = System.Convert.DBNull;
   }
}
Giờ đây chúng ta có thể kết hợp các lớp được sinh ra bởi XSD.EXE vào mã nguồn. Mã dưới đây sử dụng các lớp để nhận dữ liệu từ bảng Products và thể hiện ra màn hình console:
using System;
using System.Data;
using System.Data.SqlClient;

public class XSD_DataSet
{
   public static void Main()
   {
      string source = "server=(local)\\NetSDK;" +
                      "uid=QSUser;pwd=QSPassword;" + 
                      "database=northwind";
      string select = "SELECT * FROM Products";
      SqlConnection conn = new SqlConnection(source);
      SqlDataAdapter da = new SqlDataAdapter(select , conn);
      Products ds = new Products();
      da.Fill(ds , "Product");
      foreach(Products.ProductRow row in ds.Product )
      Console.WriteLine("'{0}' from {1}" , 
                         row.ProductID ,
                         row.ProductName);
   }
}
Các phần chính được bôi đậm. Mã trên chứa một lớp Products xuất phát từ DataSet, được tạo và điền dữ liệu bởi trình cung cấp dữ liệu.
Để biên dịch ví dụ này bạn dùng lệnh sau:
xsd product.xsd /d
csc /recurse:*.cs
Dòng đầu tiên tạo ra file Products.cs từ sơ đồ Products.XSD, và dòng thứ hại sử dụng tham số /recurse:*.cs để duyệt tất cả các file trong extension .cs và thêm vào nhị phân cuối.