Thứ Hai, 5 tháng 8, 2013

Managing Data và Relationships: The DataSet - Lập trình CSDL với C#

Lớp DataSet được thiết kế như là một thùng chứa các dữ liệu không kết nối. Nó không có khái niệm về các kết nối dữ liệu. Thật vậy, dữ liệu được giữ trong một DataSet không quan tâm đến nguồn cơ sở dữ liệu – nó có thể chỉ là những mẫu tin chứa trong một file CSV, hoặc là những đầu đọc từ một thiết bị đo lường.

Một DataSet bao gồm một tập các bảng dữ liệu, mỗi bảng là một tập các cột dữ liệu và dòng dữ liệu. Thêm vào đó là các định nghĩa dữ liệu, bạn có thể định nghĩa các link giữa các DataSet. Mối quan hệ phổ biến giữa các DataSet là parent-child relationship. Một mẫu tin trong một bảng (gọi là Order) có thể liên kết với nhiều mẫu tin trong bảng khác (Bảng Order_Details). Quan hệ này có thể được định nghĩa và đánh dấu trong DataSet.
Click To expand
Phần dưới đây giải thích các lớp được dùng trong một DataSet.

Data Tables

Một data table rất giống một bảng cơ sở dữ liệu vật lí – nó bao gồm một bộ các cột với các thuộc tính riêng, và có thể không chứa hoặc chứa nhiều dòng dữ liệu. Một data table có thể định nghĩa một khóa chínhm, bao gồm một hoặc nhiều cột, và cũng có thể chứa các ràng buộc của các cột. Tất cả các thông tin đó được thể hiện trong schema.
Có nhiều các để định nghĩa một schema cho một bảng dữ liệu riêng. Chúng sẽ được thảo luận ngay sau phần giới thiệu về cột dữ liệu và dòng dữ liệu.
Sơ đồ dưới đây chỉ ra một vài đối tượng có thể truy cập thông qua một bảng dữ liệu:
Click To expand
Một đối tượng DataTable (cũng như một DataColumn) có thể có một số các mở rộng riêng liên quan đến thuộc tính của nó. Tập hợp này có thể nằm trong thông tin user-defined gắng liền với đối tượng. Ví dụ, một cột có thể đưa ra một mặt nạ nhập liệu dùng để giới hạn các giá trị hợp lệ cho cột đó – một ví dụ về số phúc lợi xã hội Mĩ. Các thuộc tính mở rộng đặc biệt quan trọng khi dữ liệu được cấu trúc ở một tầng giữa và trả về cho client trong một số tiến trình. Bạn có thể lưu một chuẩn hợp lệ (như minmax) cho các số của các cột.
Khi một bảng dữ liệu được tạo ra, có thể do việc chọn dữ liệu từ một cơ sở dữ liệu, đọc dữ liệu từ một file, hoặc truy xuất thủ công trong mã, tập hợp Rows được dùng để chứa giá trị trả về.
Tập hợp Columns chứa các thể hiện DataColumn có thể được thêm vào bảng này. Những định nghĩa  schema của dữ liệu, ví dụ như kiểu dữ liệu, tính khả rỗng, giá trị mặc định, vân vân... Tập Constraints có thể được tạo ra bởi các ràng buộc khóa chính hoặc tính độc nhất.
Thông tin về sơ đồ của một bảng dữ liệu có thể được sử dụng trong việc biểu diễn của một bảng dữ liệu bằng DataGrid (chúng ta sẽ bàn về vấn đề này trong chương sau). Điều khiển DataGrid sử dụng các thuộc tính như kiểu dữ liệu của cột để quyết định điều khiển gì dùng cho cột đó. Một trường bit trong cơ sở dữ liệu có thể được biểu diễn như một checkbox trong DataGrid. Nếu một cột được định nghĩa trong cơ sở sơ đồ dữ liệu như là một NOT NULL, lựa chọn này được lưu trữ trong DataColumn vì vậy nó sẽ được kiểm tra khi người dùng cố gằng di chuyển khỏi một dòng.

Data Columns

Một đối tượng DataColumn định nghĩa các thuộc tính của một cột trong DataTable, chẳng hạn như kiểu dữ liệu của cột đó, chẳng hạn cột là chỉ đọc, và các sự kiện khác. Một cột có thể được tạo bằng mã, hoặc có thể được tạo tự động trong thời gian chạy.
Khi tạo một cột, tốt hơn hết là nên đặt cho nó một cái tên; nếu không thời gian chạy sẽ tự động sinh cho bạn một cái tên theo định dạng Columnn, n là mố sô tự động tăng.
Kiểu dữ liệu của một cột có thể cài đặt bằng cách cung cấp trong cấu trúc của nó, hoặc bằng cách cài đặt thuộc tính DataType. Một khi bạn đã load dữ liệu vào một bảng dữ liệu bạn không thể sửa lại kiểu dữ liệu của một cột – nếu không bạn sẽ nhận một ngoại lệ.
Các cột dữ liệu có thể được tạo để giữ các kiểu dữ liệu của .NET Framework sau:
Boolean
Decimal
Int64
TimeSpan
Byte
Double
Sbyte
UInt16
Char
Int16
Single
UInt32
DateTime
Int32
String
UInt64
Một khi đã được tạo, bước tiếp theo là cài các thuộc tính khác cho đối tượng DataColumn, chẳng hạn như tính khả rỗng nullability, giá trị mặc định. Đoạn mã sau chỉ ra một số các tùy chọn được cài đặt trong một DataColumn:
DataColumn customerID = new DataColumn("CustomerID" , typeof(int));
customerID.AllowDBNull = false;
customerID.ReadOnly = false;
customerID.AutoIncrement = true;
customerID.AutoIncrementSeed = 1000;
DataColumn name = new DataColumn("Name" , typeof(string));
name.AllowDBNull = false;
name.Unique = true;
Các thuộc tính sau có thể được cài đặt trong một DataColumn:
Property
Description
AllowDBNull
Nếu là true, cho phép cột có thể chấp nhận DBNull.
AutoIncrement
Cho biết rằng dữ liệu của cột này là một số tự động tăng.
AutoIncrementSeed
Giá trị khởi đầu cho một cột AutoIncrement.
AutoIncrementStep
Cho biết bước tăng giữa các giá trị tự động, mặc định là 1.
Caption
Có thể dùng cho việc biểu diễn tên của cột trên màn hình.
ColumnMapping
Cho biết cách một cột ánh xạ sang XML khi một DataSet được lưu bằng cách gọi phương thức DataSet.WriteXml.
ColumnName
Tên của cột. Nó tự động tạo ra trong thời gian chạy nếu không được cài đặt trong cấu trúc.
DataType
Kiểu giá trị của cột.
DefaultValue
Dùng để định nghĩa giá trị mặc định cho một cột
Expression
Thuộc tính này định nghĩa một biểu thức dùng cho việct tính toán trên cột này

Data Rows

Lớp này cấu thành các phần khác của lớp DataTable. Các cột trong một data table được định nghĩa trong các thuộc tính của lớp DataColumn. Dữ liệu của bảng thật sự có thể truy xuất được nhờ vào đối tượng DataRow. Ví dụ sau trình bày cách truy cập các dòng trong một bảng dữ liệu. Mã của ví dụ này có sẵn trong thư mục 07_SimpleDatasetSql. Trước tiên là các thông tin về kết nối:
string source = "server=(local)\\NetSDK;" +
                "uid=QSUser;pwd=QSPassword;" + 
                "database=northwind";
string select = "SELECT ContactName,CompanyName FROM Customers";
SqlConnection  conn = new SqlConnection(source);
Mã sau đây giới thiệu lớp SqlDataAdapter, được dùng để điền dữ liệu cho một DataSet. SqlDataAdapter sẽ phát ra các SQL, và điền vào một bảng Customers trong DataSet. Chúng ta sẽ bàn về lớp data adapter trong phần Populating a DataSet dưới đây.
SqlDataAdapter da = new SqlDataAdapter(select, conn);
DataSet ds = new DataSet();
da.Fill(ds , "Customers");
Trong mã dưới đây, bạn chú ý cách dùng chỉ mục của DataRow để truy xuất giá trị trong dòng đó. Giá trị của một cột có thể trả về bằng cách dụng một trong những chỉ mục được cài đè. Chúng cho phép bạn trả về một giá trị cho biết số, tên, hoặc DataColumn:
foreach(DataRow row in ds.Tables["Customers"].Rows)
   Console.WriteLine("'{0}' from {1}" , row[0] ,row[1]);
Một trong những điều quan trọng nhất của một DataRow là phiên bản của nó. Điều đó cho phép bạn nhận được những giá trị khác nhau cho một dòng cụ thể. Các phiên bản được mô tả trong bảng sau:
DataRowVersion Value
Description
Current
Giá trị sẵn có của cột. Nếu không xảy một hiệu chỉnh nào, nó sẽ mang giá trị gốc. Nếu có một hiệu chỉnh xảy ra, giá trị sẽ là giá trị hợp lệ cuối cùng được cập nhật.
Default
Giá trị mặc định (nói một cách khác, giá trị mặc định được cài đặt cho cột).
Original
Giá trị của cột trong cơ sở dữ liệu vào lúc chọn. Nếu phương thức AcceptChanges DataRow được gọi, thì giá trị này sẽ được cập nhật thành giá trị hiện tại.
Proposed
Khi các thay đổi diễn ra trên một dòng nó có thể truy lục giá trị thay đổi này. Nếu bạn gọi BeginEdit() trên mộg dòng và tạo các thay đổi, mỗi một cột giữ một giá trị cho đến khi phương thức EndEdit() hoặc CancelEdit() được gọi.
Phiên bản của một cột có thể dùng theo nhiều cách. Một ví dụ cho việc cập nhật các dòng trong cơ sở dữ liệu, đó là một câu lệnh SQL phổ biến như sau:
UPDATE Products
SET    Name = Column.Current
WHERE  ProductID = xxx
AND    Name = Column.Original;
Rõ ràng mã này không bao giờ được biên dịch, nhưng nó chỉ ra một cách dùng cho các giá trị hiện tại và gốc của một cột trong một dòng.
Để trả về một giá trị từ DataRow, dùng các phương thức chỉ mục thừa nhận một giá trị DataRowVersion như là một tham số. Đoạn mã sau đây chỉ ra cách đạt được tất cả các giá trị cho mỗi cột của một DataTable:
foreach (DataRow row in ds.Tables["Customers"].Rows )
{
  foreach ( DataColumn dc in ds.Tables["Customers"].Columns )
  {
    Console.WriteLine ("{0} Current  = {1}" , dc.ColumnName , 
                                              row[dc,DataRowVersion.Current]);
    Console.WriteLine ("    Default  = {0}" , row[dc,DataRowVersion.Default]);
    Console.WriteLine ("    Original = {0}" , row[dc,DataRowVersion.Original]);
  }
}
Mỗi dòng có một cờ trạng thái gọi là RowState, nó có thể dùng để xác định thực thi nào là cần thiết cho dòng đó khi nó cập nhật cơ sở dữ liệu. Thuộc tính RowState có thể được cài đặ để theo dõi tất cả các trạng thái thay đổi trên DataTable, như thêm vào các dòng mới, xóa các dòng hiện tại, và thay đổi các cột bên trong bảng. Khi dữ liệu được cập nhật vào cơ sở dữ liệu, cờ trạng thái được dùng để nhận biết thực thi SQL nào sẽ xảy ra. Những cờ này được định nghĩa bởi bảng liệt kê DataRowState:
DataRowState Value
Description
Added
Dòng được vừa mới được thêm vào tập hợp DataTable's Rows. Tất cả các dòng đựoc tạo trên máy khách đều được cài đặt giá trị này, và cuối cùng là phát ra câu lệnh SQL INSERT khi cập nhật cho cơ sở dữ liệu.
Deleted
Giá trị này cho biết dòng đó có thể được đánh dấu xoá trong DataTable bởi phương thức DataRow.Delete(). Dòng này vẫn tồn tại trong DataTable, nhưng không thể trông thấy từ màn hình (trừ khi một DataView được cài đặt rõ ràng). Các DataView sẽ được trình bày trong chương tiếp theo. Các dòng được đánh dấu trong DataTable sẽ bị xoá khỏi cơ sở dữ liệu khi nó được cập nhật.
Detached
Một dòng sẽ có trạng thái này ngay sau khi nó đươc tạo ra , và có thể cũng trả về trạng thái này bởi việc gọi phương thức DataRow.Remove(). Một dòng detached không được coi là một thành phần của bảng dữ liệu.
Modified
Một dòng sẽ được Modified nếu giá trị trong cột bất kì bị thay đổi.
Unchanged
Một dòng sẽ không thay đổi kể từ lần cuối cùng gọi AcceptChanges().
Trạng thái của một dòng phụ thuộc vào phương thức mà dòng đó đã gọi. Phương thức AcceptChanges() thường được gọi sau một cập nhật dữ liệu thành công (có nghĩa là sau khi thực hiện cập nhật cơ sở dữ liệu).
Cách phổ biến nhất để thay đổi dữ liệu trong một DataRow là sử dụng chỉ số, tuy vậy nếu bạn có một số thay đổi bạn ccũgn cần gọi các phương thức BeginEdit() EndEdit() methods.
Khi một cập nhật được tạo ra trên một cột trong một DataRow, sự kiện ColumnChanging sẽ được phát ra trên các dòng của DataTable. Nó cho phép bạn ghi đè lên thuộc tính ProposedValue của các lớp DataColumnChangeEventArgs, và thay đổi nó nếu muốn. Cách này cho phép các giá tri trên cột có hiệu lực . Nếu bạn gọi BeginEdit() trước khi tạo thay đổi, sự kiện ColumnChanging vẫn xảy ra. Chúng cho phép bạn tạo một sự thay đổi kép khi cố gọi EndEdit(). Nếu bạn muốn phục hồi lại giá trị gốc, hãy gọi CancelEdit().
Một DataRow có thể liên kết với một vài dòng khác của dữ liệu. Điều này cho phép tạo các liên kết có thể điều khiển được giữa các dòng, đó là kiểu master/detail. DataRow chứa một phương thức GetChildRows() dùng để thay đổi một mảng các dòng liên quan đến các cột từ một bản khác trong cùng DataSet như là dòng hiện tại. Chúng sẽ được trình bày trong phần Data Relationships nằm ở phần sau của chương này.

Schema Generation

Có ba cách để tạo một schema cho một DataTable. Đó là:
  • Hãy để thời gian chạy làm điều đó giúp bạn
  • Viết mã tạo các bảng
  • Dùng trình tạo sơ đồ XML

Runtime Schema Generation

Ví dụ về DataRow ở trên đã chỉ ra mã để chọn dữ liệu từ một cơ sở dữ liệu và tạo ra môt DataSet:
SqlDataAdapter da = new SqlDataAdapter(select , conn);
DataSet ds = new DataSet();
da.Fill(ds , "Customers");
Nó rõ ràng dễ sử dụng, nhưng nó cũng có môt vài trở ngại. Một ví dụ là bạnc phải làm việc với tên cột được chọn từ cơ sở dữ liệu, điều đó cũng tốt thôi, nhưng chăc rằng muốn đổi tên vật lí thành tên thân thiện hơn.
Bạn có thể thực hiện việc đổi tên một cách thủ công trong mệnh đề SQL, chẳng hạn như trong SELECT PID AS PersonID FROM PersonTable; bạn luôn được cảnh báo không nên đổi tên các cột trong SQL, chỉ thay thế một cột khi thật sự cần để tên xuất hiện trên màn hình được thân thiện hơn.
Một vấn đề tiềm ẩn khác không các trình phát DataTable/DataColumn tự động là bạn không thể điều khiển vượt quá kiểu của cột, các kiểu này được thời gian chạy lựa chọn cho bạn. Nó rất có ích trong việc chọn kiểu dữ liệu đúng cho bạn, nhưng trong nhiều trường hợp bạn muốn có nhiều khả năng hơn . Ví dụ bạn cần định nghĩa một tập các kiểu giá trị dùng cho một cột, vì vậy mã cần phải được viết lại. Nếu bạn chấp nhận kiểu giá trị măc định cho các cột đươc tạo ra trong thời gian chạy, có thể là một số nguyên 32-bit.
Cuối cùng một điều rất quan trọng, đó là sử dụng các trình tạo bảng tự động, bạn không thể truy xuất dữ liệu access to the data within the DataTable – you are at the mercy of indexers, which return instances of object rather than derived data types. If you like sprinkling your code with typecast expressions then skip the following sections.

Hand-Coded Schema

Việc phát ra mã để tạo một DataTable, với đầy đủ các cột là một việc tương đối đơn giản. Các ví dụ trong phần này sẽ truy cập bảng Products từ cơ sỏ dữ liệu Northwind. Mã của phần này sẵn có trong ví dụ 08_ManufacturedDataSet.
Click To expand
Dưới đây là mã để tạo thủ công một DataTable, có sơ đồ như trên.
public static void ManufactureProductDataTable(DataSet ds)
{
   DataTable   products = new DataTable("Products");
   products.Columns.Add(new DataColumn("ProductID", typeof(int)));
   products.Columns.Add(new DataColumn("ProductName", typeof(string)));
   products.Columns.Add(new DataColumn("SupplierID", typeof(int)));
   products.Columns.Add(new DataColumn("CategoryID", typeof(int)));
   products.Columns.Add(new DataColumn("QuantityPerUnit", typeof(string)));
   products.Columns.Add(new DataColumn("UnitPrice", typeof(decimal)));
   products.Columns.Add(new DataColumn("UnitsInStock", typeof(short)));
   products.Columns.Add(new DataColumn("UnitsOnOrder", typeof(short)));
   products.Columns.Add(new DataColumn("ReorderLevel", typeof(short)));
   products.Columns.Add(new DataColumn("Discontinued", typeof(bool)));
   ds.Tables.Add(products);
}
Bạn có thể sửa đổi mã trong ví dụ DataRow và sử dụng các định nghĩa sau:
string source = "server=localhost;" +
                "integrated security=sspi;" + 
                "database=Northwind";
string select = "SELECT * FROM Products";
SqlConnection conn = new SqlConnection(source);
SqlDataAdapter cmd = new SqlDataAdapter(select, conn);
DataSet ds = new DataSet();
ManufactureProductDataTable(ds);
cmd.Fill(ds, "Products");
foreach(DataRow row in ds.Tables["Products"].Rows)
   Console.WriteLine("'{0}' from {1}", row[0], row[1]);
Phương thức ManufactureProductDataTable() tạo một DataTable mới, thay đổi cho từng cột, và sau đó thêm nó vào danh sách các bảng trong DataSet. DataSet có một bộ chỉ mục nắm giữ tên của bảng và trả về DataTable được gọi.
Ví dụ trên không thật sự là bảo toàn kiểu, Tôi đã dùng bộ chỉ mục cột để lấy dữ liệu. Tốt hơn hết là dùng một lớp (hoặc một bộ các lớp) để điều khiển các DataSet, DataTable,DataRow, dùng để định nghĩa các bộ truy xuất bảo vệ kiểu cho các bảng, các dòng, các cột. Bạn có thể viết mã của mình – đó quả là một công việc chán nản, bạn có thể sử dụng các lớp bảo vệ kiểu sẵn có.
.NET Framework  có các hỗ trợ cho việc dùng các sơ đồ XML để định nghĩa một DataSet, DataTable, và các lớp khác mà chúng ta có thể làm trong phần này. Phần XML Schemas nằm trong chương này sẽ bàn về các phương thức này, nhưng trước tiên, chúng ta sẽ xem xét về các quan hệ và ràng buộc trong một DataSet.

Các quan hệ dữ liệu

Khi viết một ứng dụng, thường cần phải có sẵn nhiều bảng để lưu trữ thông tin. Lớp DataSet là một nơi để chứa các thông tin này.
Lớp DataSet là một thiết kế để tạo nên các mối quan hệ giữa các các bảng. Mã trong phần này được tôi thiết kế để tạo bằng tay mối quan hệ cho hai bảng dữ liệu. Vì vậy, nếu bạn không có SQL Server hoặc cơ sở dữ liệu NorthWind, bạn cũng có thể chạy ví dụ này. Mã có sẵn trong thư mục 09_DataRelationships:
DataSet ds = new DataSet("Relationships");
ds.Tables.Add(CreateBuildingTable());
ds.Tables.Add(CreateRoomTable());
ds.Relations.Add("Rooms",
                  ds.Tables["Building"].Columns["BuildingID"],
                  ds.Tables["Room"].Columns["BuildingID"]);
Các bảng đơn giản chứa một khóa chính và một trường tên, trong đó bảng Room có một khóa ngoại BuildingID.
Click To expand

Sau đó thêm một số dữ liệu cho mỗi bảng.
foreach(DataRow theBuilding in ds.Tables["Building"].Rows)
{
   DataRow[] children = theBuilding.GetChildRows("Rooms");
   int roomCount = children.Length;
   Console.WriteLine("Building {0} contains {1} room{2}",
                     theBuilding["Name"],
                     roomCount,
                     roomCount > 1 ? "s" : "");
   // Loop through the rooms
   foreach(DataRow theRoom in children)
      Console.WriteLine("Room: {0}", theRoom["Name"]);
}
Sự khác biệt lớn nhất giữa DataSet và kiểu đối tượng Recordset cổ điển là sự biểu hiện của quan hệ. Trong một Recordset cổ điển, một quan hệ được biểu diễn là một cột giả trong dòng. Cột này bản thân nó là một Recordset có thể lập lại. Trong ADO.NET, một quan hệ đơn giản là một lời gọi phương thức GetChildRows():
DataRow[] children = theBuilding.GetChildRows("Rooms");
Phương thức này có một số kiểu, ví dụ trên chỉ ra cách dùng tên của quan hệ. Nó trả về một mảng các dòng có thể cập nhật bằng bộ chỉ mục như đã đề cập ở các ví dụ trước đây.
Thích thú hơn là quan hệ dữ liệu có thể xem xét theo hai cách. Không chỉ có thể đi từ cha đến con, mà có thể tìm được các dòng cha của một mẫu tin con bằng cách sử dụng thuộc tính ParentRelations trên lớp DataTable. Thuộc tính này trả về một DataRelationCollection, có thể truy cập bằng kí hiệu mảng [] (ví dụ, ParentRelations["Rooms"]), hoặc dùng luân phiên phương thức GetParentRows() như mã dưới đây:
foreach(DataRow theRoom in ds.Tables["Room"].Rows)
{
   DataRow[] parents = theRoom.GetParentRows("Rooms");
   foreach(DataRow theBuilding in parents)
      Console.WriteLine("Room {0} is contained in building {1}",
                        theRoom["Name"],
                        theBuilding["Name"]);
}
Có hai phương thức với rất nhiều các cài đặt đè khác nhau để trả về các dòng cha – GetParentRows() (trả về một mảng các dòng), hoặc GetParentRow() (trả về một dòng cha duy nhất của một quan hệ).

Ràng buộc dữ liệu

Thay đổi kiểu dữ liệu của một cột đã được tạo trên một máy đơn không chỉ là một khả năng tuyệt vời của một DataTable. ADO.NET cho phép bạn tạo một tập các ràng buộc trên một cột (hoặc nhiều cột), dùng cho các nguyên tắc chuẩn hóa dữ liệu.
Thời gian chạy hỗ trợ các kiểu ràng buộc sau, như là các lớp trong không gian System.Data.
Constraint
Description
ForeignKeyConstraint
Thực một liên kết giữa hai DataTables trong một DataSet
UniqueConstraint
Bảo đảm tính độc nhất của cột

Cài đặt khóa chính

Một điều phổ biến của một bảng trong một cơ sở dữ liệu quan hệ, bạn có thể cung cấp một khóa chính, dựa vào một hoặc nhiều cột trong một DataTable.
Mã sau tạo một khóa chính cho bảng Products, mà sơ đồ của nó đã được tạo bằng thủ công trong các ví dụ trên, chúng ta có thể tìm thấy trong thư mục 08_ManufactureDataSet.
Chú ý rằng một khóa chính cảu một bảng chỉ là một kiểu của ràng buộc. Khi một khóa chính được thêm vào một DataTable, thời gian chạy cũng phát ra một ràng buộc độc nhất trên khóa chính. Bởi vì thực tế không tồn tại kiều ràng buộc PrimaryKey – một khóa chính đơn giản là một ràng buộc duy nhất trên một hoặc nhiều cột.
public static void ManufacturePrimaryKey(DataTable dt)
{
   DataColumn[] pk = new DataColumn[1];
   pk[0] = dt.Columns["ProductID"];
   dt.PrimaryKey = pk;
}
Một khóa chính có thể bao gồm một vài cột, nó được xem như là một mảng các DataColumns. Một khóa chính của một bảng được cài đặt trên những cột này đơn giản được còi là một mảng của các cột làm nên thuộc tính.
Để kiểm tra các ràng buộc của một bảng, bạn có thể lập lại ConstraintCollection. Đối với ràng buộc tự sinh như ví dụ trên, tên của ràng buộc sẽ là Constraint1. Nó không phải là một tên tốt, vì vậy tốt nhất là nên tạo ràng buộc trước sau đó định nghĩa các cột tạo nên khóa chính, như chúng ta sẽ làm dưới đây.
Là một lập trình viên cơ sở dữ liệu lâu năm, tôi nhận thấy ràng tên của một ràng buộc cần phải thật rõ nghĩa. Mã dưới đây định danh ràng buộc trước khi tạo khóa chính:
DataColumn[] pk = new DataColumn[1];
pk[0] = dt.Columns["ProductID"];
dt.Constraints.Add(new UniqueConstraint("PK_Products", pk[0]));
dt.PrimaryKey = pk;
Ràng buộc duy nhất có thể áp dụng cho bao nhiêu cột tùy thích.

Tạo một khóa ngoại

Ngoài các ràng buộc duy nhất, một DataTable có thể chứa các ràng buộc khóa ngoại. Nó thường được áp dụng cho các mối quan hệ chủ tớ, nhưng cũng có thể dùng để tạo bảng sao các cột giữa các bảng nếu bạng tạo một ràng buộc chính xác. Một quan hệ chủ tớ là một mẫu tin cha có thể có nhiều mẫu tin con, liên kết với khóa chính của mẫu tin cha.
Một ràng buộc khóa ngoại có thể chỉ thực thi trên các bảng bên trong một DataSet, ví dụ dưới đây sử dụng bảng Categories trong cơ sở dữ liệu Northwind, và tạo một ràng buộc giữa nó với bảng Products table.
Click To expand
Bước đầu tiên là tạo một bảng dữ liệu mới cho bảng Categories. Ví dụ 08_ManufactureDataSet:
DataTable categories = new DataTable("Categories");
categories.Columns.Add(new DataColumn("CategoryID", typeof(int)));
categories.Columns.Add(new DataColumn("CategoryName", typeof(string)));
categories.Columns.Add(new DataColumn("Description", typeof(string)));
categories.Constraints.Add(new UniqueConstraint("PK_Categories",
                               categories.Columns["CategoryID"]));
categories.PrimaryKey = new DataColumn[1]
                              {categories.Columns["CategoryID"]};
Dòng cuối cùng của mã trên tạo một khóa chính cho bảng Categories. Khóa chính là một cột đơn, tất nhiên nó cũng thể tạo một khóa chính trên nhiều cột bằng các dùng kí tự mảng.
Sau đó tôi tạo một ràng buộc giữa hai bảng:
DataColumn parent = ds.Tables["Categories"].Columns["CategoryID"];
DataColumn child = ds.Tables["Products"].Columns["CategoryID"];
ForeignKeyConstraint fk = 
   new ForeignKeyConstraint("FK_Product_CategoryID", parent, child);
fk.UpdateRule = Rule.Cascade;
fk.DeleteRule = Rule.SetNull;
ds.Tables["Products"].Constraints.Add(fk);
Ràng buộc này dùng để liên kết giữa Categories.CategoryIDProducts.CategoryID. Có bốn cấu trúc khác nhau cho ForeignKeyConstraint, nhưng tôi khuyên bạn nên dùng tên của ràng buộc.

Tạo các ràng buộc Update và Delete

Bổ sung cho phần định nghĩa tất nhiên là một vài kiểu của ràng buộc giữa các bảng cha và con, bạn có thể định nghĩa phải làm gì trong một ràng buộc cập nhật.
Ví dụ trên tạo một qui tắc cập nhật và một qui tắc xóa. Những qui tắc này được dùng khi môt sự kiện được phát ra trên cột (hoặc dòng) trong bảng cha, và qui tắc được dùng để quyết định chuyện gì sẽ xảy ra trong bảng con. Có bốn qui tắc khác nhau có thể áp dụng được liệt kê trong Rule enumeration: 
  • Cascade – Nếu khóa cha được cập nhật sau đó copy giá trị mới này cho tất cả các mã của khóa con. Nếu mẫu cha bị xoá, thì xóa luôn các mẫu con. Nó là tùy chọn mặc định.
  • None – Không làm gì hết. Tùy chọn này sẽ bỏ các dòng mô côi khỏi bảng dữ liệu con.
  • SetDefault – Mỗi thay đổi trên dòng con được mang giá trị mặc định của nó, nếu nó được định nghĩa trước.
  • SetNull – Tất cả các dòng được chọn là DBNull
    Chú ý:
    Các ràng buộc chỉ có hiệu lực trong một DataSet nếu thuộc tính EnforceConstraints của DataSettrue.
Tôi đã đổi các lớp chính dùng để tạo nên DataSet, chỉ ra cách tạo thủ công các lớp bằng mã. Có một cách khác để định nghĩa một DataTable, DataRow, DataColumn, DataRelation,Constraint – bằng cáchc dùng các file sơ đồ XML và công cụ XSD sẵn có trong .NET. Đoạn dưới đây trình bày cách để tạo một sơ đồ đơn giản và tạo các lớp bảo vệ kiểu để truy cập dữ liệu của bạn.