通用类, 用于使用反射从数据库填充 DataTable(s) 并将 DataTable(s) 保存到数据库






4.56/5 (9投票s)
通用类,使用反射填充强类型 DataSet 中部分或全部 DataTable,并通过创建 TableAdapter 将其保存到数据库。
- 下载 DataBaseConnectorMSAccess_Demo.zip - 499.7 KB
- 下载 DataBaseConnectorMSAccess_Source.zip - 532.9 KB
- 下载 DataBaseConnectorMSSqlServer_Demo.zip - 1.9 MB
- 下载 DataBaseConnectorMSSqlServer_Source.zip - 1.9 MB
引言
在 .NET 中,可以使用 MS VisualStudio 中的 AddNewDataSource
向导轻松创建强类型数据集。强类型数据集很有用,因为可以以强类型方式访问数据库字段。该向导为填充和保存 DataSet
中的 DataTable
创建了 TableAdapter
类。由于 TableAdapter
的强类型性质,必须使用相应 DataTable
的 TableAdapter
分别处理 DataSet
中每个 DataTable
的填充和保存,例如如下所示:
NorthwindDataSetTableAdapters.CustomersTableAdapter
customersAdapter = new NorthwindDataSetTableAdapters.CustomersTableAdapter();
OleDbConnection connection = new OleDbConnection(
string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}", filePath));
connection.Open();
customersAdapter.Connection = connection;
customersAdapter.Fill(destinationDataSet.Customers);
connection.Close();
尽管这看起来很容易,但当存在多个数据表时(这在实际程序中通常是这种情况),会变得笨拙且代码重复很多,因为上述代码需要为每个 DataTable
重复,包括填充 DataTable
和保存 DataTable
。如果所有数据表都在一个方法中填充或保存,那么仅填充或保存选定的 DataTable
就会变得困难,并且需要条件语句。否则,必须创建单独的方法来处理填充和保存选定列表的 DataTable
。由于 TableAdapter
和 DataTable
的名称是强类型的,因此在不同项目之间重用代码变得困难。为了克服这个困难,我编写了一个使用反射的 DataBaseConnector
类,如下面的“实现 DataBaseConnector”子标题所示。实例化 DataBaseConnector
类后,可以通过调用相应的命令方法来填充或保存所有 DataTable
或选定的 DataTable
,如下所示。该类可以在不同项目中使用,而无需更改类的代码。
Using the Code
我在文章中使用了 MS Access 数据库进行解释,但该类也可以用于 SQL Server 数据库,为此我提供了一个下载示例。
要使用 DataBaseConnector
类,请将 DataBaseConnector
类及其派生类(用于 MS Access 数据库的 OleDbDataBaseConnector
(用于 MS SQL Server 数据库的 SqlServerDataBaseConnector
))包含在数据层项目中的解决方案中(这是必需的,如“实现 DataBaseConnector”中所述),并在窗体加载事件或代码中的适当位置创建类型化 DataSet
的实例。然后,通过将类型化 DataSet
实例和连接字符串作为参数传递来创建 OleDbDataBaseConnector
的实例,如下所示。
NorthWindDataSet1 = new NorthwindDataSet();
try {
NorthWindConnector1 = new OleDbDataBaseConnector(NorthWindDataSet1,
String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}",
Application.StartupPath + "\\NorthWind.mdb"));
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
要填充 DataSet
的所有 DataTable
,请调用 OleDbDataBaseConnector
实例的 FillDataTables
方法。
private void fillAllTablesToolStripMenuItem_Click(object sender, EventArgs e) {
try {
NorthWindConnector1.FillDataTables();
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
要仅填充 DataSet
的选定 DataTable
,请调用 OleDbDataBaseConnector
实例的 FillDataTables
方法重载,并传入选定表的列表作为 String
的 Array
。
private void fillSelectedTablesToolStripMenuItem_Click(object sender, EventArgs e) {
try {
NorthWindConnector1.FillDataTables(GetSelectedTableList("Select the Tables to Fill"));
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
同样,要保存 DataSet
的所有 DataTable
,请调用 OleDbDataBaseConnector
实例的 SaveDataTables
方法。
private void saveAllTablesToolStripMenuItem_Click(object sender, EventArgs e) {
try {
NorthWindConnector1.SaveDataTables();
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
要仅保存 DataSet
的选定 DataTable
,请调用 DataBaseConnector
实例的 SaveDataTables
方法重载,并传入选定表的列表作为 String
的 Array
。
private void saveSelectedTablesToolStripMenuItem_Click(object sender, EventArgs e) {
try {
NorthWindConnector1.SaveDataTables(GetSelectedTableList("Select the Tables to Save"));
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
使用 OleDbDataBaseConnector
的优点是,一旦实例化,就可以通过一次调用 OleDbDataBaseConnector
实例的相应方法来填充或保存所有或部分 DataTable
。该类可以在不同项目中使用,而无需更改代码。
本文附带的示例项目使用了 Microsoft Visual Studio 2008、MS Access 2000 格式的 Microsoft NorthWind 示例数据库和 MS SQL Server 2008 R2 Express。
实现 DataBaseConnector 类
为了使用 DataBaseConnector
类,无需理解该类的代码,因为该类可以直接包含在不同项目中而无需修改代码。但是,下面将讨论 DataBaseConnector
类的实现。
首先定义一个抽象类 DataBaseConnector
,从中派生出特定数据库的 DataBaseConnector,例如用于 MS Access 数据库的 OleDbDataBaseConnector
和用于 MS SQL Server 数据库的 SqlServerDataBaseConnector
。首先,让我们看一下 DataBaseConnector
类。在该类中,声明了用于存储目标数据集和连接字符串的字段。在类的构造函数中,从参数设置这些字段的值。
protected string connectionString;
private DataSet destinationDataset;
protected DbConnection connection;
public DatabaseConnector(DataSet destinationDataset,
string connectionString) {
try {
this.connectionString = connectionString;
this.destinationDataset = destinationDataset;
}
catch {
throw;
}
}
定义了 FillDataTables
方法,该方法以 tableList
作为参数数组,用于从数据库填充数据集的部分或全部 DataTable
。它调用 OpenConnection
、FillDataTable
和 CloseConnection
方法。
public void FillDataTables(params string[] tableList) {
if (tableList == null) return;
destinationDataset.EnforceConstraints = false;
OpenConnection();
foreach (string table in tableList) {
try {
FillDataTable(table);
}
catch (Exception ex) {
throw new Exception(String.Format("Could not fill {0}\n{1}",
table, ex.Message));
}
}
destinationDataset.EnforceConstraints = true;
CloseConnection();
}
OpenConnection
和 CloseConnection
被声明为抽象方法,它们在特定数据库的派生类中实现。FillDataTable
方法接受 tableName
参数。它调用 GetTableAdapter
来获取与当前强类型 DataTable
对应的 TableAdapter
实例。使用反射调用 TableAdapter
的 Fill
方法来填充 DataTable
。
private void FillDataTable(String tableName) {
Type tableAdapterType;
var tableAdapter = GetTableAdapter(tableName, out tableAdapterType);
object[] parameterList = new object[] {destinationDataset.Tables[tableName]};
MethodInfo methodInfo = tableAdapterType.GetMethod("Fill");
destinationDataset.Tables[tableName].Clear();
destinationDataset.Tables[tableName].BeginLoadData();
//Invoke Fill method of tableadapter
methodInfo.Invoke(tableAdapter, parameterList);
destinationDataset.Tables[tableName].EndLoadData();
destinationDataset.Tables[tableName].AcceptChanges();
methodInfo = tableAdapterType.GetMethod("Dispose");
//Invoke dispose method of table adapter
methodInfo.Invoke(tableAdapter, null);
}
GetTableAdapter
方法构造强类型 DataTable
的 TableAdapter
名称,并使用 Assembly
类的 GetExecutingAssembly
获取 TableAdapter
类型,然后使用 Activator.CreateInstance
方法创建其实例。TableAdapter
的 Connection
属性由 AddNewDataSource
向导定义为 internal
,并且只能在同一程序集中访问。因此,DataBaseConnector
必须包含在创建了类型化 DataSet
的同一项目中。此外,在生成类型化 DataSet
时,向导会将数据库表名称中的空格替换为“_”。
private object GetTableAdapter(string tableName, out Type tableAdapterType) {
try {
//TypedDataSet designer replaces space in table name with
//underscore _.
Assembly assembly = Assembly.GetExecutingAssembly();
tableAdapterType = assembly.GetType(string.Format(
"{0}.{1}TableAdapters.{2}TableAdapter", assembly.GetName().Name,
destinationDataset.DataSetName,
tableName.Replace(" ", "_")));
var tableAdapter = Activator.CreateInstance(tableAdapterType);
PropertyInfo propertyInfo = tableAdapterType.GetProperty("Connection",
BindingFlags.NonPublic | BindingFlags.Instance);
propertyInfo.SetValue(tableAdapter, connection, null);
return tableAdapter;
}
catch (Exception ex) {
throw new Exception(
String.Format("Could not create table adapter for {0}\n {1}",
tableName, ex.Message));
}
}
同样,SaveDataTables
和 SaveDataTable
方法如下所示。
public void SaveDataTables(params string[] tableList) {
if (tableList == null) return;
destinationDataset.EnforceConstraints = false;
OpenConnection();
foreach (string table in tableList) {
try {
SaveDataTable(table);
}
catch (Exception ex) {
throw new Exception(String.Format("Could not save {0}\n{1}",
table, ex.Message));
}
}
destinationDataset.AcceptChanges();
destinationDataset.EnforceConstraints = true;
CloseConnection();
}
private void SaveDataTable(string tableName) {
Type tableAdapterType;
var tableAdapter = GetTableAdapter(tableName, out tableAdapterType);
Assembly assembly = Assembly.GetExecutingAssembly();
Type dataTableType = assembly.GetType(String.Format(
"{0}.{1}+{2}DataTable", assembly.GetName().Name,
destinationDataset.DataSetName, tableName.Replace(" ", "_")));
Type targetDatasetType = destinationDataset.GetType();
PropertyInfo propertyInfo = targetDatasetType.GetProperty(tableName.Replace(" ", "_"));
var dataTable = propertyInfo.GetValue(destinationDataset, null);
object[] parameterList = new object[] { dataTable };
MethodInfo methodInfo = tableAdapterType.GetMethod("Update", new Type[] { dataTableType });
//Invoke update method of table adapter
methodInfo.Invoke(tableAdapter, parameterList);
//Invoke dispose method of table adapter
methodInfo = tableAdapterType.GetMethod("Dispose");
methodInfo.Invoke(tableAdapter, null);
}
要为特定数据库使用 DataBaseConnector
类,需要创建一个派生自 DataBaseConnector
类的类,例如,为 MS Access 数据库派生 OleDbDataBaseConnector
类,为 MS SQL Server 数据库派生 SqlServerDataBaseConnector
类。该派生类实现了基类中声明的抽象方法 OpenConnection
和 CloseConnection
,用于打开和关闭特定于该数据库的连接,如下所示。
public class OleDbDataBaseConnector : DatabaseConnector {
public OleDbDataBaseConnector (DataSet destinationDataset,
string connectionString) :
base (destinationDataset, connectionString) {
}
protected override void OpenConnection() {
try {
if (connection == null)
connection = new OleDbConnection(connectionString);
if (connection.State != ConnectionState.Open)
connection.Open();
}
catch{
throw;
}
}
protected override void CloseConnection() {
try {
if (connection == null)
connection = new OleDbConnection(connectionString);
if (connection.State != ConnectionState.Closed)
connection.Close();
}
catch {
throw;
}
}
}
评论/建议
我将很高兴看到您对改进本文的评论和建议。