在项目中遇到一些要把DataSet的更改提交到数据库的需求,因此,实现了一个工具类来完成这个工作。
设计思想:DataSet中的每一个DataTable中的每一行都有状态(RowState), 并且DataTable提供了一个GetChanges(DataRowState state)的方法来获取变化了的数据集合,返回一个新的DataTable对象。因此,我们根据数据提交的顺序,将分别处理 Insert, Update, Delete三种情况,并且根据与数据库表的映射,自动构造SQL语句,利用SqlDataAdapter完成数据提交工作。
由于.net基础数据类型与SQL Server数据类型存在多对一的关系,且使用数据适配器(DataAdapter)时,构造SQL参数对象必须要求指定每个参数对象的长度以及更多信息(映射数据有版本的情况下),因此,不好根据DataColumn的类型来推导对应的SqlDbType,故根据需要实现了一个新的类,来保存每个列的映射及参数。
下面是实现的原代码,有许多需要改进的地方,希望大家提出更好的意见和建议。在此谢过,先!
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Text;
namespace kim.Data
{
/// <summary>
/// Summary description for SqlDbAccess.
/// </summary>
public class SqlDbAccess
{
static SqlDbAccess()
{
}
/// <summary>
/// 处理传入的数据库表名,加上[],避免因表名是数据库中的关键字而出错。
/// 并且在不存在所有者名的时候,加上所有者名。
/// </summary>
/// <param name="dbTableName">待处理的数据库表名。</param>
/// <returns>处理后的数据库表名。</returns>
private static string PrepareDbTableName(string dbTableName)
{
string[] tableName = dbTableName.Split('.');
if(tableName.Length == 1) //只有表名
{
if(tableName[0][0] == '[')
return tableName[0]; // [tableName] 格式
else
return string.Concat("dbo.[", dbTableName, "]"); //tableName 格式
}
else
{
StringBuilder text = new StringBuilder(100);
for(int i=0; i<tableName.Length; i++)
{
if(tableName
[0] == '[') //[xx] 格式
text.Append(tableName
[0] + '.');
else // xx 格式
text.Append("[" + tableName
+ "].");
}
text.Remove(text.Length-1, 1);
return text.ToString();
}
}
/// <summary>
/// 更新DataTable的更改到数据库中。
/// 并发控制采用“最后的更新生效”
/// </summary>
/// <param name="table">有效的要提交的DataTable对象</param>
/// <param name="sqlConn">有效的数据为连接对象</param>
/// <param name="dbTableName">有效的数据库表名</param>
/// <param name="dataColumnMappings">有效的要提交的列及参数映射对象数组</param>
/// <param name="keyColumnMappings">有效的条件列及参数映射对象数组</param>
/// <param name="needTransaction">是否需要事务</param>
public static void UpdateData(DataTable table, SqlConnection sqlConn, string dbTableName,
ParameterColumnMapping[] dataColumnMappings, ParameterColumnMapping[] keyColumnMappings, bool needTransaction)
{
if(table == null) throw new ArgumentNullException("table");
if(sqlConn == null) throw new ArgumentNullException("sqlConn");
if(dbTableName == null || dbTableName.Length == 0) throw new ArgumentNullException("dbTableName");
if(dataColumnMappings == null || dataColumnMappings.Length == 0) throw new ArgumentNullException("dataColumnMappings");
if(keyColumnMappings == null || keyColumnMappings.Length == 0) throw new ArgumentNullException("keyColumnMappings");
if(table.Rows.Count == 0) return;
dbTableName = PrepareDbTableName(dbTableName);
// 设置连接是否需要在完成后关闭的标记。
// 如果连接对象传进来时是关闭的,则用完后也应该关闭。即保持连接传入前的状态。
bool connNeedClose = (sqlConn.State == ConnectionState.Closed);
SqlDataAdapter sqlAdapter = new SqlDataAdapter();
// 添加表映射及列映射
DataTableMapping tableMapping = sqlAdapter.TableMappings.Add(dbTableName, table.TableName);
for(int i=0; i<dataColumnMappings.Length; i++)
{
tableMapping.ColumnMappings.Add(dataColumnMappings
.SourceColumn, dataColumnMappings
.DataTableColumn);
}
// 构造WHERE条件部分
StringBuilder sqlWhere = new StringBuilder(256);
sqlWhere = new StringBuilder(256);
sqlWhere.Append(" WHERE ");
for(int i=0; i<keyColumnMappings.Length; i++)
{
if(i == 0)
{
sqlWhere.Append(string.Format("[{0}] = @{0}", keyColumnMappings
.SourceColumn));
}
else
{
sqlWhere.Append(string.Format(" AND [{0}] = @{0}", keyColumnMappings
.SourceColumn));
}
}
StringBuilder sqlText = new StringBuilder(1024);
SqlTransaction sqlTran = null;
DataTable tempTable = null;
DataRow[] rows = new DataRow[1];
// 开始提交数据
try
{
if(connNeedClose) sqlConn.Open();
if(needTransaction) sqlTran = sqlConn.BeginTransaction();
SqlCommand sqlCmd = new SqlCommand("", sqlConn, sqlTran);
// 处理Insert
tempTable = table.GetChanges(DataRowState.Added);
if(tempTable != null)
{
sqlAdapter.InsertCommand = sqlCmd;
StringBuilder valueText = new StringBuilder(256);
// 循环每一行,只处理不为Null的列
foreach(DataRow dr in tempTable.Rows)
{
sqlCmd.Parameters.Clear();
sqlText.Length = 0;
sqlText.Append("INSERT INTO " + dbTableName + " (");
for(int i=0; i<dataColumnMappings.Length; i++)
{
if(dr[dataColumnMappings
.DataTableColumn] != DBNull.Value)
{
sqlText.Append(string.Format(" [{0}],", dataColumnMappings
.SourceColumn));
valueText.Append(string.Format(" @{0},", dataColumnMappings
.SourceColumn));
sqlCmd.Parameters.Add(dataColumnMappings
.DbParameter);
}
}
// 移除末尾的','
sqlText.Remove(sqlText.Length-1, 1);
valueText.Remove(valueText.Length-1, 1);
// 完成Insert语句的构造并提交
sqlText.Append(string.Format(" ) VALUES ({0})", valueText.ToString()));
sqlCmd.CommandText = sqlText.ToString();
rows[0] = dr;
sqlAdapter.Update(rows);
}
}
// 处理Update
// 仅对已经发生了改变的列进行Update
tempTable = table.GetChanges(DataRowState.Modified);
if(tempTable != null)
{
int changedColumns = 0; //用于记录已经改变的列数
sqlAdapter.UpdateCommand = sqlCmd;
foreach(DataRow dr in tempTable.Rows)
{
sqlCmd.Parameters.Clear();
sqlText.Length = 0;
sqlText.Append(string.Format("UPDATE {0} SET", dbTableName));
changedColumns = 0;
for(int i=0; i<dataColumnMappings.Length; i++)
{
// 当前版本与原始版本不等
if(!dr[dataColumnMappings
.DataTableColumn, DataRowVersion.Current].Equals(
dr[dataColumnMappings
.DataTableColumn, DataRowVersion.Original]))
{
sqlText.Append(string.Format(" [{0}]=@{0},", dataColumnMappings
.SourceColumn));
sqlCmd.Parameters.Add(dataColumnMappings
.DbParameter);
changedColumns ++;
}
}
// 仅当有列改变了才进行处理,以防止行状态改变了,但值未变的情况。
if(changedColumns > 0)
{
sqlText.Remove(sqlText.Length-1, 1); //移除末尾的‘,'
sqlText.Append(" " + sqlWhere.ToString());
// 添加条件参数
for(int j=0; j<keyColumnMappings.Length; j++)
{
sqlCmd.Parameters.Add(keyColumnMappings[j].DbParameter);
}
// 提交数据
sqlCmd.CommandText = sqlText.ToString();
rows[0] = dr;
sqlAdapter.Update(rows);
}
}
}
// 处理Delete
tempTable = table.GetChanges(DataRowState.Deleted);
if(tempTable != null && tempTable.Rows.Count > 0)
{
sqlText.Length = 0;
sqlText.Append("DELETE FROM " + dbTableName + sqlWhere.ToString());
sqlCmd.CommandText = sqlText.ToString();
sqlCmd.Parameters.Clear();
// 添加条件参数
for(int j=0; j<keyColumnMappings.Length; j++)
{
sqlCmd.Parameters.Add(keyColumnMappings[j].DbParameter);
}
sqlAdapter.DeleteCommand = sqlCmd;
sqlAdapter.Update(tempTable);
}
if(sqlTran != null) sqlTran.Commit();
}
catch(Exception ex)
{
if(sqlTran != null)
{
sqlTran.Rollback();
}
throw ex;
}
finally
{
if(connNeedClose) sqlConn.Close();
}
}
/// <summary>
/// 定义参数与列的映射关系的类。
/// </summary>
public class ParameterColumnMapping
{
private string m_SourceColumn;
private string m_DataTableColumn;
private SqlParameter m_DbParameter;
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="sourceColumn">数据库源列名</param>
/// <param name="dataTableColumn">DataTable中的列名</param>
/// <param name="dbParameter">对应的参数对象</param>
public ParameterColumnMapping(string sourceColumn, string dataTableColumn, SqlParameter dbParameter)
{
m_SourceColumn = sourceColumn;
m_DataTableColumn = dataTableColumn;
m_DbParameter = dbParameter;
SetParameterSourceColumn();
}
public ParameterColumnMapping() : this(null, null, null)
{
}
/// <summary>
/// 获取或设置数据库源列名。
/// </summary>
public string SourceColumn {
get {
return m_SourceColumn;
}
set {
m_SourceColumn = value;
}
}
/// <summary>
/// 获取或设置DataTable对象中的列名。
/// </summary>
public string DataTableColumn {
get {
return m_DataTableColumn;
}
set {
m_DataTableColumn = value;
}
}
/// <summary>
/// 获取或设置列对应的Sql参数对象。
/// </summary>
public SqlParameter DbParameter {
get {
return m_DbParameter;
}
set {
m_DbParameter = value;
SetParameterSourceColumn();
}
}
private void SetParameterSourceColumn()
{
if((m_DbParameter.SourceColumn == null || m_DbParameter.SourceColumn == string.Empty)
&& (m_DataTableColumn != null && m_DataTableColumn != string.Empty))
{
m_DbParameter.SourceColumn = m_DataTableColumn;
}
}
}
}
}
**************************************************
SqlDbAccess.ParameterColumnMapping[] dataColumnMappings = new kim.Data.SqlDbAccess.ParameterColumnMapping[2];
dataColumnMappings[0] = new kim.Data.SqlDbAccess.ParameterColumnMapping("APP_ID", myData.AppSystems.AppIDColumn.ColumnName,
new SqlParameter("@APP_ID", SqlDbType.NVarChar, 20, myData.AppSystems.AppIDColumn.ColumnName));
dataColumnMappings[1] = new kim.Data.SqlDbAccess.ParameterColumnMapping("DESCRIPTION", myData.AppSystems.DescriptionColumn.ColumnName,
new SqlParameter("@DESCRIPTION", SqlDbType.NVarChar, 500, myData.AppSystems.DescriptionColumn.ColumnName));
SqlDbAccess.ParameterColumnMapping[] keyColumnMappings = new kim.Data.SqlDbAccess.ParameterColumnMapping[1];
keyColumnMappings[0] = new kim.Data.SqlDbAccess.ParameterColumnMapping("APP_ID", myData.AppSystems.AppIDColumn.ColumnName,
new SqlParameter("@APP_ID", SqlDbType.NVarChar, 20, ParameterDirection.Input, true, 0, 0, myData.AppSystems.AppIDColumn.ColumnName, DataRowVersion.Original, null));
SqlConnection sqlConn = new SqlConnection(Configuration.D2WHP01_ConnString);
SqlDbAccess.UpdateData(myData.AppSystems, sqlConn, "AzProvider.dbo.APP", dataColumnMappings, keyColumnMappings, true);
ps: 缺点:使用sqlAdapter.Update,因为他是每条数据更新一次(效率比较低)