众所周知,DataSet和DataTable可以被认为是“内存中”的数据库。其中DataSet包含若干个可能具备一定关联的DataTable,从而实现类似缓冲区一般的数据存储,由于DataSet或者是DataTable是被SqlDataAdapter(或者更抽象的是某个数据库的DataAdapter)的Fill方法填充而成,自然只需调用其Update方法即可完全更新整个对应的物理数据库,同时也自动设置相关的各个DataTable中的每一行DataRow的RowState到其应有的状态。
其实DataSet和DataTable不仅如此,使用恰当在某些方面甚至可以完全代替SQL语句;下面给出一些例子:
I)DataTable的筛选:
多数情况下我们常常使用where对某个数据表中所有数据进行有选择筛选,然后使用adapter的Fill方法填充入DataTable中。其实直接使用DataTable的Select方法和DataView也可以做到这一点
通过MSDN的示例大致你可以了解到Select方法是对内部已经填充好的所有DataRow中指定的某个DataColumn进行过滤性筛选,返回结果是DataRow数组。这里需要提醒你注意的是你不能直接把DataRow数组直接绑定到一个控件上(诸如DataGridView或者是GridView(ASP.NET控件));这是因为任何实现IEnumerable<T>或者IEnumerable接口的Collection类直接作用于展示控件上,展示控件只会通过内部反射等复杂方式反射“T"的公共属性(仅有RowError,RowState,HasErrors和Table会被显示在这些控件中),而不会去进一步反射DataTable每一个DataRow和DataColumn等值。所以解决方案是:使用原来的DataTable.Clone方法,拷贝一个不带任何数据的空Table,然后foreach方法循环遍历DataRow[],并且使用空DataTable的ImportRow或者是通过Add方法加入即可。
- DataView的RowFilter属性:http://msdn.microsoft.com/zh-cn/library/system.data.dataview.rowfilter(v=vs.80).aspx
作用基本同Select,不过有一个好处是DataView有一个ToTable,直接返回过滤后的DataTable。这就意味着你不比向上面的Select方法一样手动去创建一个DataTable了。
同时ToTable还提供了一个重载方法(以下是函数体定义):
[C#]
public DataTable ToTable (
bool distinct,
params string[] columnNames
)
[VB.NET]
Public Function ToTable ( _
distinct As Boolean, _
ParamArray columnNames As String() _
) As DataTable
这个方法允许你指定是否对指定的列中的数据进行去除重复值的操作,并只返回被过滤的那些字段的数据(布尔值设置为True的时候,类似distinct的作用;不同的时在SQL中distinct只能对一个字段进行操作,但是DataTable中则可以针对多个字段列进行筛选)。例如我们对以下冗余DataTable进行过滤(MSDN示例代码有所变更,运行结果:只返回Category和Product,且“4”,“5”两条数据被“合并”成一条数据了,冗余项被删除):
[C#]
namespace A
{
class Program
{
private static void DemonstrateDataView()
{
DataTable table = new DataTable("NewTable");
DataColumn column = new DataColumn("ID", typeof(System.Int32));
table.Columns.Add(column);
column = new DataColumn("Category", typeof(System.String));
table.Columns.Add(column);
column = new DataColumn("Product", typeof(System.String));
table.Columns.Add(column);
column = new DataColumn("QuantityInStock", typeof(System.Int32));
table.Columns.Add(column);
// Add some items.
DataRow row = table.NewRow();
row.ItemArray = new object[] { 1, "Fruit", "Apple", 14 };
table.Rows.Add(row);
row = table.NewRow();
row.ItemArray = new object[] { 2, "Fruit", "Orange", 27 };
table.Rows.Add(row);
row = table.NewRow();
row.ItemArray = new object[] { 3, "Bread", "Muffin", 23 };
table.Rows.Add(row);
row = table.NewRow();
row.ItemArray = new object[] { 4, "Fish", "Salmon", 12 };
table.Rows.Add(row);
row = table.NewRow();
row.ItemArray = new object[] { 5, "Fish", "Salmon", 15 };
table.Rows.Add(row);
row = table.NewRow();
row.ItemArray = new object[] { 6, "Bread", "Croissant", 23 };
table.Rows.Add(row);
table = new DataView(table).ToTable(true, "Category","Product");
//Print out
foreach (DataRow r in table.Rows)
{
foreach (DataColumn col in table.Columns)
{
Console.Write(r[col].ToString()+"");
}
Console.WriteLine();
}
}
static void Main(string[] args)
{
DemonstrateDataView();
}
}
}
[VB.NET]
Namespace A
Class Program
Private Shared Sub DemonstrateDataView()
Dim table As New DataTable("NewTable")
Dim column As New DataColumn("ID", GetType(System.Int32))
table.Columns.Add(column)
column = New DataColumn("Category", GetType(System.String))
table.Columns.Add(column)
column = New DataColumn("Product", GetType(System.String))
table.Columns.Add(column)
column = New DataColumn("QuantityInStock", GetType(System.Int32))
table.Columns.Add(column)
' Add some items.
Dim row As DataRow = table.NewRow()
row.ItemArray = New Object() {1, "Fruit", "Apple", 14}
table.Rows.Add(row)
row = table.NewRow()
row.ItemArray = New Object() {2, "Fruit", "Orange", 27}
table.Rows.Add(row)
row = table.NewRow()
row.ItemArray = New Object() {3, "Bread", "Muffin", 23}
table.Rows.Add(row)
row = table.NewRow()
row.ItemArray = New Object() {4, "Fish", "Salmon", 12}
table.Rows.Add(row)
row = table.NewRow()
row.ItemArray = New Object() {5, "Fish", "Salmon", 15}
table.Rows.Add(row)
row = table.NewRow()
row.ItemArray = New Object() {6, "Bread", "Croissant", 23}
table.Rows.Add(row)
table = New DataView(table).ToTable(True, "Category", "Product")
'Print out
For Each r As DataRow In table.Rows
For Each col As DataColumn In table.Columns
Console.Write(r(col).ToString() & "")
Next
Console.WriteLine()
Next
End Sub
Private Shared Sub Main(args As String())
DemonstrateDataView()
End Sub
End Class
End Namespace
II)多个在DataSet中的DataTable实现“一对多”的浏览:
当一个DataSet容器中存放着互相关联的DataTable的时候,我们可以通过添加Relationship为它们构建表关系(通过DataSet.Relations.Add方法)。比如以下的例子中"master"表和"details"就是属于“一对多”关系:
[C#]
namespace WinFormCSharp
{
public partial class Form1 : Form
{
public Form1()
{
this.Size = new System.Drawing.Size(900, 400);
// dataset
var dt1 = new DataTable("master");
dt1.Columns.Add("Id", typeof(int));
dt1.Columns.Add("Name", typeof(string));
var dt2 = new DataTable("details");
dt2.Columns.Add("Id", typeof(int));
dt2.Columns.Add("Name", typeof(string));
var ds = new DataSet();
ds.Tables.AddRange(new[] { dt1, dt2 });
ds.Relations.Add("master_details", dt1.Columns["Id"], dt2.Columns["Id"]);
// test data
for (int i = 0; i < 5; i++)
dt1.Rows.Add(i, "n" + i);
foreach (DataRow r in dt1.Rows)
{
var id = r["Id"];
for (int i = 0; i < 3; i++)
dt2.Rows.Add(id, "n" + i);
}
new DataGridView { Dock = DockStyle.Left, Width = this.Width/2, Parent = this, DataSource = ds, DataMember = "master.master_details" };
new DataGridView { Dock = DockStyle.Left, Width = this.Width/2, Parent = this, DataSource = ds, DataMember = "master" };
}
}
}
[VB.NET]
Namespace WinFormCSharp
Public Partial Class Form1
Inherits Form
Public Sub New()
Me.Size = New System.Drawing.Size(900, 400)
' dataset
Dim dt1 = New DataTable("master")
dt1.Columns.Add("Id", GetType(Integer))
dt1.Columns.Add("Name", GetType(String))
Dim dt2 = New DataTable("details")
dt2.Columns.Add("Id", GetType(Integer))
dt2.Columns.Add("Name", GetType(String))
Dim ds = New DataSet()
ds.Tables.AddRange(New () {dt1, dt2})
ds.Relations.Add("master_details", dt1.Columns("Id"), dt2.Columns("Id"))
' test data
For i As Integer = 0 To 4
dt1.Rows.Add(i, "n" & i)
Next
For Each r As DataRow In dt1.Rows
Dim id = r("Id")
For i As Integer = 0 To 2
dt2.Rows.Add(id, "n" & i)
Next
Next
New DataGridView() With { _
Key .Dock = DockStyle.Left, _
Key .Width = Me.Width / 2, _
Key .Parent = Me, _
Key .DataSource = ds, _
Key .DataMember = "master.master_details" _
}
New DataGridView() With { _
Key .Dock = DockStyle.Left, _
Key .Width = Me.Width / 2, _
Key .Parent = Me, _
Key .DataSource = ds, _
Key .DataMember = "master" _
}
End Sub
End Class
End Namespace
运行以上代码,我们可以发现点击最左边的一个dataGridView,右边会自动显示对应的子项。这是因为当你点选不同的Row的时候,另外一个DataGridView实际是从master.master_details去寻找对应的外键项。因而产生此效果。
您不能单就设置主表的DataSource为ds.Tables[0](VB.NET中是ds.Tables(0)),因为这种自动寻找是通过relation外键加以寻找的,外键又是通过DataMember进行指定的。此时如果你只是指定了DataSource而没有指定DataMember,那么只是一种单纯的绑定而已,运行点击主表的行,右边的DataGridView不再会发生任何改变!
III)自动计算:http://msdn.microsoft.com/zh-cn/library/system.data.datacolumn.expression(v=vs.80).aspx
所谓“自动计算”,就是说某一列的字段值不是手动填入,而是通过前面几个字段进行一定运算后自动填入的。比如有一个表:
Id(自增长列),产品名称,单价,数量,总价。
那么总价=单价*数量,当输入了单价和数量,总价应该自动计入,且不准修改。
“不准修改”可以通过设置DataTable.Columns[0].ReadOnly=true;(VB.NET:DataTable.Columns(0).ReadOnly=True));至于这个“自动计算”的总价:
1)如果是外连数据库的(也就是由SqlDataAdapter填充数据表的),可以直接在SQL表对应字段中设置默认值表达式:单价*数量。
2)另外就是手动设置DataColumn的Expression属性为“单价*数量”。
值得注意的是:Expression不能包含对自身字段的引用,因为这将引发类似递归一样的死循环,运行将发生错误。