ADO.Net的封裝已經有很多的實作了,但我總感覺那些實作還是沒有透明化使用者對ADO.Net的了解。比如說很多人推崇的Enterprise Library的DataAccess,我認為就是封裝不夠徹底。我理想中封裝徹底的ADO.Net對象是,使用者不需要(或盡可能的少)了解任何,而DataAccess還是需要使用者直接的處理很多ADO.Net的對象。而我需要的ADO.Net的封裝希望使用者,僅給予SQL指令,指派參數,并擷取結果即可。
定義SqlDataProvider類
/// <summary>
/// SQL資料提供者的實作
/// </summary>
public class SqlDataProvider : DataProviders.IDataProvider
{
}
DataProviders.IDataProvider是我定義的一個接口,我希望将來所有的資料提供者都能實作該接口,以便利用依賴倒置實作抽象工廠。
定義DataProviders.IDataProvider接口
public interface IDataProvider
void AddParameters(string parname, Guid value);
void AddParameters(string parname, long value);
void AddParameters(string parname, string value);
void AddParameters(string parname, string value, DataProviders.StringFamily dataType);
void AddParameters(string parname, string value, DataProviders.StringFamily dataType, int size);
void AddParameters(string parname, float value);
void AddParameters(string parname, decimal value);
void AddParameters(string parname, DateTime value);
void AddParameters(string parname, DateTime value, DataProviders.DateFamily dataType);
void AddParameters(string parname, int value);
void AddParameters(string parname, object value);
void AddParameters(string parname, System.Drawing.Bitmap value);
void AddParameters(string parname, byte[] value);
void AddParameters(string parname, byte[] value, DataProviders.ByteArrayFamily dataType);
void AddParameters(string parname, bool value);
void AddParameters(string parname, short value);
void AddParameters(string parname, byte value);
System.Data.CommandType CommandType { get; set; }
string ConnectionString { get; }
System.Data.DataSet ExecuteDataSet();
System.Data.DataTable ExecuteDataTable();
void ExecuteReader(ReadData readData);
int ExecuteNonQuery();
object ExecuteScalar();
string SQL { get; set; }
}
public delegate void ReadData(System.Data.IDataReader dataReadre);
從該接口可以看到,實作的DataProvider封裝了關于具體的連接配接對象,指令對象和參數類型的資訊。
SqlDataProvider類實作(基礎部分)
private static System.Data.SqlClient.SqlConnection conn;
private System.Data.SqlClient.SqlCommand cmd;
/// 預設構造函數
public SqlDataProvider()
{
/// 接受連接配接字元串
/// <param name="connstr"></param>
public SqlDataProvider(string connstr)
: this(connstr, "")
/// 接受連接配接字元串和sql字元串
/// <param name="sql"></param>
public SqlDataProvider(string connstr, string sql)
conn = new System.Data.SqlClient.SqlConnection(connstr);
cmd = new System.Data.SqlClient.SqlCommand();
cmd.Connection = conn;
cmd.CommandText = sql;
/// 需要執行的SQL指令
public string SQL
set
cmd.CommandText = value;
get
return cmd.CommandText;
/// 目前的連接配接字元串
public string ConnectionString
return conn.ConnectionString;
/// 設定指令的類型
public System.Data.CommandType CommandType
cmd.CommandType = value;
return cmd.CommandType;
從上述代碼可以觀察到,我們的DataProvider隻向使用者暴露了兩個字元串資料:連接配接字元串和SQL指令字元串。而将ADO.Net特有的Connection和Command封裝起來了。由于使用者不需要了解到具體執行個體化的Connection和Command,則為以後對其他資料源提供者的擴充帶來了機會。
SqlDataProvider類實作(資料執行部分)
/// 将SqlDataReader送出給具體的委托器處理
/// <param name="readData"></param>
public void ExecuteReader(ReadData readData)
using (conn)
conn.Open();
System.Data.SqlClient.SqlDataReader dr = cmd.ExecuteReader();
readData(dr);
conn.Close();
/// 對連接配接執行 Transact-SQL 語句并傳回受影響的行數
/// <returns></returns>
public int ExecuteNonQuery()
int result = -1;
result = cmd.ExecuteNonQuery();
return result;
/// 執行查詢,并傳回查詢所傳回的結果集中第一行的第一列。忽略其他列或行
public object ExecuteScalar()
object result = null;
result = cmd.ExecuteScalar();
/// 執行查詢,并傳回查詢的DataSet
public System.Data.DataSet ExecuteDataSet()
System.Data.DataSet datadet = new System.Data.DataSet();
System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();
adapter.SelectCommand = cmd;
adapter.Fill(datadet);
return datadet;
/// 執行查詢,并傳回查詢的Table
/// <param name="tableIndex"></param>
public System.Data.DataTable ExecuteDataSet(int tableIndex)
System.Data.DataSet datadet = ExecuteDataSet();
if (datadet.Tables.Count > 0 && tableIndex < datadet.Tables.Count)
return datadet.Tables[tableIndex];
else
return null;
public System.Data.DataTable ExecuteDataTable()
System.Data.DataTable table = new System.Data.DataTable();
adapter.Fill(table);
return table;
DataProvider提供ExecuteReader、ExecuteNonQuery、ExecuteScalar、ExecuteDataSet和ExecuteDataTable方法,向使用者封裝了兩種不同的資訊:
對執行資料通路的過程(Open後要Close等)已經在執行過程中的輔助對象(DataAdapter)資訊。使用者僅需要簡單的調用上述的方法,既可以得到他所關注的資料。
SqlDataProvider類實作(參數部分)
/// 添加一個Variant類型資料
/// <param name="parname"></param>
/// <param name="value"></param>
public void AddParameters(string parname, object value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.Variant).Value = value;
/// 添加一個Bit類型資料
public void AddParameters(string parname, bool value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.Bit).Value = value;
/// 添加一個TinyInt類型資料
public void AddParameters(string parname, byte value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.TinyInt).Value = value;
/// 添加一個SmallInt類型資料
public void AddParameters(string parname, short value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.SmallInt).Value = value;
/// 添加一個Int類型資料
public void AddParameters(string parname, int value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.Int).Value = value;
/// 添加一個BigInt類型資料
public void AddParameters(string parname, long value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.BigInt).Value = value;
/// 添加一張圖檔
public void AddParameters(string parname, System.Drawing.Bitmap value)
System.IO.MemoryStream ms = new System.IO.MemoryStream();
value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
AddParameters(parname, ms.ToArray(), ByteArrayFamily.Image);
/// 添加一個Timestamp類型
public void AddParameters(string parname, byte[] value)
AddParameters(parname, value, ByteArrayFamily.Timestamp);
/// 添加一個位元組數組族類型資料
/// <param name="dateType"></param>
public void AddParameters(string parname, byte[] value, ByteArrayFamily dataType)
cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType)).Value = value;
/// 添加一個字元類型資料,預設是NVarChar,長度是value.Length
public void AddParameters(string parname, string value)
AddParameters(parname, value, StringFamily.NVarChar, value.Length);
/// 添加一個字元族類型資料
/// <param name="length"></param>
public void AddParameters(string parname, string value, int size)
AddParameters(parname, value, StringFamily.NVarChar, size);
public void AddParameters(string parname, string value, StringFamily dataType)
AddParameters(parname, value,dataType, value.Length);
/// <param name="size"></param>
public void AddParameters(string parname, string value, StringFamily dataType, int size)
cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType), size).Value = value;
/// 添加一個SmallDateTime類型資料
public void AddParameters(string parname, DateTime value)
AddParameters(parname, value, DateFamily.SmallDateTime);
/// 添加一個日期族類型資料
public void AddParameters(string parname, DateTime value, DateFamily dataType)
/// 添加一個Decimal類型資料
public void AddParameters(string parname, decimal value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.Decimal).Value = value;
/// 添加Float類型資料
public void AddParameters(string parname, float value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.Float).Value = value;
/// 添加一個UniqueIdentifier類型資料
public void AddParameters(string parname, System.Guid value)
cmd.Parameters.Add(parname, System.Data.SqlDbType.UniqueIdentifier).Value = value;
ADO.Net對參數的處理冗長的很,需要很多代碼,我們的DataProvider通過重載簡單的實作了對參數的處理,使用者在大多數情況下隻需要提供兩個參數:參數的名稱和值。DataProvider和根據值的類型推算應該使用具體的哪個System.Data.SqlDbType。
不過,另人煩惱的是C#的資料類型和SQL的資料類型不是簡單的一對一的關系,而是一對多的複雜關系,我們需要有一個方法來适配資料類型的不同。
資料類型的關系定義
/// C#對于的SQL類型
public enum StringFamily
Char,
NChar,
NText,
NVarChar,
Text,
VarChar
public enum DateFamily
DateTime,
SmallDateTime,
Date,
Time,
DateTime2,
DateTimeOffset
public enum ByteArrayFamily
Binary,
Image,
Timestamp,
VarBinary
DataTypeAdapter的定義
/// SqlDbType資料類型和.NET Framework資料類型的擴充卡
public static class DataTypeAdapter
/// 将.NET Framework資料類型适配為SqlDbType資料類型
/// <param name="data"></param>
public static System.Data.SqlDbType ConvertSqlDbType(StringFamily data)
switch (data)
case StringFamily.Char:
return System.Data.SqlDbType.Char;
case StringFamily.NChar:
return System.Data.SqlDbType.NChar;
case StringFamily.NText:
return System.Data.SqlDbType.NText;
case StringFamily.NVarChar:
return System.Data.SqlDbType.NVarChar;
case StringFamily.Text:
return System.Data.SqlDbType.Text;
default:
return System.Data.SqlDbType.VarChar;
public static System.Data.SqlDbType ConvertSqlDbType(DateFamily data)
case DateFamily.Date:
return System.Data.SqlDbType.Date;
case DateFamily.DateTime:
return System.Data.SqlDbType.DateTime;
case DateFamily.DateTime2:
return System.Data.SqlDbType.DateTime2;
case DateFamily.DateTimeOffset:
return System.Data.SqlDbType.DateTimeOffset;
case DateFamily.SmallDateTime:
return System.Data.SqlDbType.SmallDateTime;
return System.Data.SqlDbType.Time;
public static System.Data.SqlDbType ConvertSqlDbType(ByteArrayFamily data)
case ByteArrayFamily.Binary:
return System.Data.SqlDbType.Binary;
case ByteArrayFamily.Image:
return System.Data.SqlDbType.Image;
case ByteArrayFamily.Timestamp:
return System.Data.SqlDbType.Timestamp;
return System.Data.SqlDbType.VarBinary;
通過上述的資料類型适配,我們将使用者和ADO.Net直接的具體關系弱耦合了。
本文轉自shyleoking 51CTO部落格,原文連結:http://blog.51cto.com/shyleoking/805857