DataTable是表格数据块在内存中的表示。虽然可以手动以编程形式构建一个DataTable,但通常使用DataSet和定义在System.Data.OleDb或System.Data.SqlClient命名空间中的类型,以动态获得一个DataTable。表A-7描述了DataTable中的一些核心属性。
表A-7 DataTable的属性
DataTable属性 |
意 义 |
CaseSensitive |
表明表中的字符串比较是否区分大小写。默认的值为false |
ChildRelations |
返回DataTable的子关系(DataRelationCollection)的集合 |
Columns |
返回属于这个表的列的集合 |
Constraints |
获得表约束的集合(ConstraintCollection) |
DataSet |
获得包含这个表的DataSet |
DefaultView |
获得表的自定义视图,它可能包含已过滤的视图或游标位置 |
MinimumCapacity |
获得或设置表中行的初始数目(默认为25) |
ParentRelations |
获得这个DataTable上的父关系的集合 |
PrimaryKey |
获得或设置作为数据表主键的列数组 |
Rows |
返回属于这个表的行集合 |
TableName |
获得或设置表的名称。这个属性还可以被指定为构造函数的参数 |
图A-7可以帮助您更加清楚地了解DataTable的关键部分。要知道这并不是一个传统的类层次结构,说明类型之间is-a关系(例如,DataRow不是派生自DataRowCollection)。这个图只是显示了DataTable的核心项之间的has-a逻辑关系(例如,DataRowCollection有一些DataRow类型)。
图A-7 DataTable的集合
现在您已经了解到最基础的东西,让我们来看一个完整的创建并操作内存中的数据表的例子。假设您想构建一个显示Cars数据库中当前存货的DataTable。这个Inventory表有4个列:CarID,Make,Color和PetName。同时,CarID列作为这个表的主键(PK)并支持自动递增。PetName列允许null值(很遗憾,并不是每个人都和我们一样喜爱自己的车)。图A-8显示了该表。
图A-8 存货DataTable
整个过程将从创建一个新的DataTable类型开始。创建完这个类型后,可以把这个表的名称指定为构造函数的参数。可以用这个名称从所在DataSet引用这个表,如下所示:
// Create a new DataTable.
DataTable inventoryTable = new DataTable("Inventory");
下一步是以编程方式使用DataColumnCollection的Add()方法插入每列(使用DataTable.Columns属性访问)。下面的逻辑将CarID、Make、Color和PetName列添加到当前DataTable中(每列的基本数据类型使用DataType属性设置):
// DataColumn var.
DataColumn myDataColumn;
// Create CarID column and add to table.
myDataColumn = new DataColumn();
myDataColumn.DataType = Type.GetType("System.Int32");
myDataColumn.ColumnName = "CarID";
myDataColumn.ReadOnly = true;
myDataColumn.AllowDBNull = false;
myDataColumn.Unique = true;
// Set the autoincrement behavior.
myDataColumn.AutoIncrement = true;
myDataColumn.AutoIncrementSeed = 1000;
myDataColumn.AutoIncrementStep = 10;
inventoryTable.Columns.Add(myDataColumn);
// Create Make column and add to table.
myDataColumn = new DataColumn();
myDataColumn.DataType = Type.GetType("System.String");
myDataColumn.ColumnName = "Make";
inventoryTable.Columns.Add(myDataColumn);
// Create Color column and add to table.
myDataColumn = new DataColumn();
myDataColumn.DataType = Type.GetType("System.String");
myDataColumn.ColumnName = "Color";
inventoryTable.Columns.Add(myDataColumn);
// Create PetName column and add to table.
myDataColumn = new DataColumn();
myDataColumn.DataType = Type.GetType("System.String");
myDataColumn.ColumnName = "PetName";
myDataColumn.AllowDBNull = true;
inventoryTable.Columns.Add(myDataColumn);
在添加行之前,花点时间来设置一下表的主键。可以对需要设置的列设定DataTable.PrimaryKey属性。由于作为表主键的列可能不止一个,因此要知道PrimaryKey的属性需要一个DataColumn类型的数组。假设CarID列就是Invetory表主键的惟一组成部分,如下所示:
// Make the ID column the primary key column.
DataColumn[] PK = new DataColumn[1];
PK[0] = inventoryTable.Columns["CarID"];
inventoryTable.PrimaryKey = PK;
最后但相当重要的是,您需要往表中添加有效的数据。假设有一个合适的ArrayList保存Car类型,可以用如下的方式把它填充到表中:
// Iterate over the array list to make rows (remember, the ID is
// autoincremented).
foreach(Car c in arTheCars)
{
DataRow newRow;
newRow = inventoryTable.NewRow();
newRow["Make"] = c.make;
newRow["Color"] = c.color;
newRow["PetName"] = c.petName;
inventoryTable.Rows.Add(newRow);
}
为了显示新的本地内存表,假定有一个Windows Forms应用程序,包含一个显示DataGrid的主窗体。如第11章所示,DataSource属性用于把DataTable绑定到GUI上。输出结果如图A-9所示。
图A-9 把DataTable绑定到DataGrid上
这儿通过指定要修改的列名称的字符串来添加行。当然还可以指定列的数字索引,在需要迭代每个列时,它特别有用。这样,前面的代码可以更新为如下的代码(得到同样的最终结果):
foreach(Car c in arTheCars)
{
// Specify columns by index.
DataRow newRow;
newRow = inventoryTable.NewRow();
newRow[1] = c.make;
newRow[2] = c.color;
newRow[3] = c.petName;
inventoryTable.Rows.Add(newRow);
}
如果您想从数据表中删除一行该怎么做呢?我们可以调用DataRowCollection类型的Delete()方法。只要指定要删除行的索引(或者时DataRow)就可以。假设您已经按照图A-10更新了GUI。
图A-10 从一个DataTable中删除行
如果您查看前面的图,就会注意到由于指定了DataTable的第二行,CarID1020就被删除掉了。下面新按钮的单击事件处理逻辑就是删除内存中DataTable表中的指定行。
// Remove this row from the DataRowCollection.
protected void btnRemoveRow_Click (object sender, System.EventArgs e)
{
try
{
inventoryTable.Rows[(int.Parse(txtRemove.Text))].Delete();
inventoryTable.AcceptChanges();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
或许将这个Delete()方法命名为MarkedAsDeletable()更好一点,因为这一行只有到DataTable.AcceptChanges()方法调用后才会真正被删除。实际上,Delete()只是简单地设定一个标记表示“I am ready to die when my table tells me”。还要明白,如果有一行被标记为删除,那么DataTable可能会在AcceptChanges()调用之前拒绝这些修改,如下所示:
// Mark a row as deleted, but reject the changes.
protected void btnRemoveRow_Click (object sender, System.EventArgs e)
{
inventoryTable.Rows[txtRemove.Text.ToInt32()].Delete();
// Do more work. . .
inventoryTable.RejectChanges(); // Restore RowState.
}
或许您想查看DataTable数据的一个子集,可以用一些过滤条件来指定。例如,如果您只想从这个内存中的Inventory表中看到某个牌子的汽车该怎么做呢?DataTable类上的Select()方法恰好提供了这个功能。再次更新您的GUI,这次允许用户指定一个字符串来表示他们感兴趣查看的车的牌子(图A-11)。
图A-11 指定一个过滤器
这个Select()方法已经被重载多次,以提供不同的选择语义。传递给Select()的最基本参数可以是一个包含有某个条件操作的字符串。首先看一下新按钮的单击事件处理逻辑:
protected void btnGetMakes_Click (object sender, System.EventArgs e)
{
// Build a filter based on user input.
string filterStr = "Make='" + txtMake.Text + "'";
// Find all rows matching the filter.
DataRow[] makes = inventoryTable.Select(filterStr);
// Show what we got!
if(makes.Length = = 0)
MessageBox.Show("Sorry, no cars. . .", "Selection error!");
else
{
string strMake = null;
for(int i = 0; i < makes.Length; i++)
{
DataRow temp = makes[i];
strMake += temp["PetName"].ToString() + ""n";
}
MessageBox.Show(strMake, txtMake.Text + " type(s):");
}
}
这儿,您首先建立一个基于相关的文本框值的过滤器条件。如果您指定BMW,那么过滤器条件就是Make = ‘BMW’。把这个过滤器发送给Select()方法后,就会得到一个DataRow类型的数组,这个数组表示了匹配每个符合过滤条件的行,如图A-12所示。
图A-12 过滤后的数据
可以用很多相关的操作符组成一个过滤字符串。例如,如果想查找所有ID大于1030的车怎么做呢?您可以编写如下的代码(见图A-13的输出结果):
// Now show the pet names of all cars with ID greater than 1030.
DataRow[] properIDs;
string newFilterStr = "ID > '1030'";
properIDs = inventoryTable.Select(newFilterStr);
string strIDs = null;
for(int i = 0; i < properIDs.Length; i++)
{
DataRow temp = properIDs[i];
strIDs += temp["PetName"].ToString()
+ " is ID " + temp["ID"] + ""n";
}
MessageBox.Show(strIDs, "Pet names of cars where ID > 1030");
图A-13 指定一个数据范围
模拟标准SQL语法编写过滤逻辑。为了验证这一点,假设想根据pet名称的字母顺序来获得前面Select()命令的结果。在SQL术语中,这会被解释为基于PetName列进行排序。幸运的是,Select()方法已经被重载过,它可以传递一个排序条件,如下所示:
makes = inventoryTable.Select(filterStr, "PetName");
这样会返回图A-14所示的信息。
图A-14 已排序的数据
如果您想用降序来对结果排序,调用Select(),如下所示:
// Return results in descending order.
makes = inventoryTable.Select(filterStr, "PetName DESC");
一般来说,排序字符串是列名后跟着“ASC”(升序,默认设置)或“DESC”(降序)。如果需要的话,可以用逗号来把多个列分开排序。
您需要了解的关于DataTable的最后一个方面就是怎样用新值更新已有的行。一个方法就是先用Select()方法获得符合给定过滤条件的行。一旦获得这些DataRow,就对它们作相应的修改。例如,假定有一个新按钮在被单击后,搜索DataTable中所有Make为BMW的行。一旦标识这些项后,就可以把Make从“BMW”改为“Colt”。
// Find the rows you want to edit with a filter.
protected void btnChange_Click (object sender, System.EventArgs e)
{
// Build a filter.
string filterStr = "Make='BMW'";
string strMake = null;
// Find all rows matching the filter.
DataRow[] makes = inventoryTable.Select(filterStr);
// Change all Beemers to Colts!
for(int i = 0; i < makes.Length; i++)
{
DataRow temp = makes[i];
strMake += temp["Make"] = "Colt";
makes[i] = temp;
}
}
这个DataRow类也提供了BeginEdit()、EndEdit()和CancelEdit()方法,这些方法可以在任何相关的验证规则被临时挂起时对一个行的内容进行编辑。在前面的逻辑中,每一行都用一个指派作了验证(而且如果从DataRow中捕获事件的话,这些事件会在每次修改时触发)。在对某个DataRow调用BeginEdit()时,这一行就被设置在编辑模式下。这时您可以根据需要来作些改动,并调用EndEdit()提交修改或者CancelEdit()把所作的修改回滚到原先的版本。例如:
// Assume you have obtained a row to edit.
// Now place this row in edit mode'.
rowToUpdate.BeginEdit();
// Send the row to a helper function, which returns a Boolean.
if( ChangeValuesForThisRow( rowToUpdate) )
{
rowToUpdate.EndEdit(); // OK!
}
else
{
rowToUpdate.CancelEdit(); // Forget it.
}
虽然您可以随意地对某一DataRow手动调用这些方法,但如果把一个DataGrid绑定到DataTable,这些成员就可以被自动地调用。例如,如果您想从DataGrid中选择一行进行编辑的话,该行就会自动处于编辑模式下。当把焦点换到另一行时,就会自动调用EndEdit()。为了测试这个行为,假设您已经手动地使用DataGrid把每个车更新为某个Make(图A-15)。
如果现在您想查询所有的BMW,消息对话框会正确地返回所有行,因为关联到这个DataGrid的底层DataTable已经被自动更新了(图A-16)。
图A-15 在DataGrid中编辑行 图A-16 Inventory DataTable
DataRow[] properIDs;
string newFilterStr = "ID > '1030'";
properIDs = inventoryTable.Select(newFilterStr);
如果我想限制取前面的50行,该怎么办呢?
如果要用上面的方法,可以给DataTable中插入一个自增行(index),用它去判断.
也可以这样写.
{
if (dt.Rows.Count < top) return dt;
DataTable newTable = new DataTable();
int columns = dt.Columns.Count;
string [] col = new string [columns];
// 取得要筛选表的所有列名
for ( int c = 0 ; c < columns; c ++ )
{
col[c] = dt.Columns[c].ColumnName;
}
// 创建新表的结构
foreach ( string columnName in col)
{
newTable.Columns.Add(columnName);
}
// 选取所有行
DataRow[] rows = dt.Select( " 1=1 " );
DataRow newRow;
for ( int i = 0 ; i < top; i ++ )
{
newRow = newTable.NewRow();
foreach ( string columnName in col)
{
newRow[columnName] = rows[i][columnName].ToString();
}
newTable.Rows.Add(newRow);
}
dt.Dispose();
return newTable;
}