十年一剑

君子 自强不息 厚德载物

博客园 首页 新随笔 联系 订阅 管理

转载 http://www.codeproject.com/cs/database/SqlWrapper.asp
源代码本地下载: 
使用SQLWrapper库,你可以写非常少的代码来创建你的数据访问类.

简介
你曾经在你的项目中创建数据访问层吗?你很可能创建了一个类或者一些类,他们包含几个方法,调用了存储过程或者执行一个SQL语句.如果数据库包含很多表,这是个很烦人的工作过程.最糟糕的是,这个方法要很多的同样的步骤(创建一个命令对象,填充它的属性,执行,然后返回结果).并且很少包含其他的逻辑.你有两个方法:手工写这些代码,或者(自动)生成他们.这两个方法,你会有很多源代码.

以前我在用这两个方法时,我就感觉在哪里有更容易的方法.AutoSprocTool给我一个开发SqlWrapper库的想法.

一个简单的例子
让我们写两个类,他们和Northwind数据库一起工作.一个类Orders1使用通常的方法.
差异是显而易见的.Orders2 包含了很少的代码但是他们使用起来差不多相同,仅仅是创建的方法不同.

public class Orders1
{
    
private SqlConnection m_connection = null;

    
public SqlConnection Connection
    
{
        
get{return m_connection;}
        
set{m_connection = value;}
    }


    
public DataSet CustOrdersDetail(int OrderID)
    
{
        SqlCommand cmd 
= new SqlCommand("CustOrdersDetail", m_connection);
        cmd.CommandType 
= CommandType.StoredProcedure;
        cmd.Parameters.Add(
"@OrderID", SqlDbType.Int);
        cmd.Parameters[
"@OrderID"].Value = OrderID;
        SqlDataAdapter da 
= new SqlDataAdapter(cmd);
        DataSet ds 
= new DataSet();
        da.Fill(ds);
        
return ds;
    }


    
public int CountByEmployee(int EmployeeID)
    
{
        SqlCommand cmd 
= new SqlCommand(
            
"select count(*) from Orders where EmployeeID=@EmployeeID"
            m_connection);
        cmd.CommandType 
= CommandType.Text;
        cmd.Parameters.Add(
"@EmployeeID", SqlDbType.Int);
        cmd.Parameters[
"@EmployeeID"].Value = EmployeeID;
        
int count = (int)cmd.ExecuteScalar();
        
return count;
    }

    
}


另一个使用SqlWrapper,
public abstract class Orders2 : SqlWrapperBase
{
    
public abstract DataSet CustOrdersDetail(int OrderID);

    [SWCommand(
"select count(*) from Orders where EmployeeID=@EmployeeID")]
    
public abstract int CountByEmployee(int EmployeeID);
}


现在让我们看看如何使用这些类.
SqlConnection cnn 
= new SqlConnection(
    ConfigurationSettings.AppSettings[
"ConnectionString"]);
cnn.Open();

// working with the ordinary class
Orders1 orders1 = new Orders1();
orders1.Connection 
= cnn;
DataSet ds1 
= orders1.CustOrdersDetail(10248);
int count1 = orders1.CountByEmployee(6);

// working with the wrapped class
Orders2 orders2 = (Orders2)WrapFactory.Create(typeof(Orders2));
orders2.Connection 
= cnn;
DataSet ds2 
= orders2.CustOrdersDetail(10248);
int count2 = orders2.CountByEmployee(6);

 

SqlWarpper如何工作
为了创建一个包装类,你要从SqlWrapperBase继承,并且定义抽象方法,使用自定义属性定义要执行什么,如何获得和获得什么结果.如果没有指定方法属性,那么方法的名称将被用做存储过程名称.你也可以定义任意数量的具体方法,如果你要在执行sql语句之外,还有一些更多的逻辑.然后,你可以通过调用WrapFactory.Create()方法,创建定义类的一个对象.这个方法使用System.Reflection.Emit 命名空间的类,为抽象方法添加执行.例如,下面的方法:

[SWCommand("select count(*) from Orders where EmployeeID=@EmployeeID")]
public abstract int CountByEmployee(int EmployeeID);

将被执行为:
[SWCommand(
"select count(*) from Orders where EmployeeID=@EmployeeID")]
public int CountByEmployee(int EmployeeID)
{
    MethodInfo method 
= (MethodInfo)MethodBase.GetCurrentMethod();
    
object[] values = new object[1];
    values[
0= EmployeeID;
    
object obj = SWExecutor.ExecuteMethodAndGetResult(
            m_connection, 
            m_transaction, 
            method, 
            values, 
            m_autoCloseConnection);

    
return (int)obj;
}


SWExecutor.ExecuteMethodAndGetResult() 方法执行了主要的工作.它创建了SqlCommand和返回了执行的结果. 为了这个用途,变量method提供了下面的信息:
command text (命令)
command type (命令类型)
execution method (执行的方法名称)
parameter names (参数名称)
parameter data types (参数类型)
parameter directions (参数方向)
parameter sizes, scales and precisions (参数的大小,范围,精度)
behavior in case null or DBNull is returned instead of a scalar value
如果没有返回一个精度值,而是返回了空或者DBNull时的行为.
behavior in case DBNull value must be passed to a parameter
如果必须传递一个空值作为参数时的行为.
所有的这些信息都将通过方法签名提供,其他可选的通过方法属性和参数属性.

下面的图显示了SqlWrapper类直接的联系
 


SqlWrapperBase

SqlWrapperBase类是所有的包装类的基本类,它包含了下面的属性:
Connection 属性
Transaction 属性
AutoCloseConnection 属性.如果为真,那么连接在每次的命令执行完毕就会自动关闭.
你可以在你的包装类中使用上面的属性,因为他们是protected.

SWCommandAttribute

这是一个可选的方法属性,包含了下面的属性.

CommandType可以是下面的值:SWCommandType.Text,SWCommandType.StoreProcedure和SWCommandType.InsertUpdate.这几个值类似于System.Data.CommandType枚举的值. SWCommandType.InsertUpdate 将在以后讲述.默认值是SWCommandType.Text.
CommandText 包含了一个命令文本.这依赖于CommandType属性值.
ReturnIfNull包含了一个值,如果执行了一个命令返回了空,那么将返回此值.
MissingSchemaAction是一个值,SqlDataAdapter.MissingShemaAction.默认是Add.

除了CommandText属性以外都是可选的属性.

重要:如果这个属性省略了,那么方法名称将被用做CommandText属性,CommandType属性将等于SWCommandTYpe.StoreProcedure.

SWParameterAttribute

这是一个可选的参数属性,包含下列属性:
Name:包含了参数名称,如果省略,使用方法参数名称
SqlDbType,包含了值的类型,对应了SqlParameter.SqlDbType属性.
Size 包含了命令参数的大小,对应了SqlParameter.Size属性.
Precision 包含了参数的精度.对应了SqlParameter.Presision
Scale 包含了参数的小数位数.对应了SqlParameter.Scale.
TreatAsNull 包含了被翻译成DBNull的值.在数字参数时很有用.
ParameterType 包含了下列值:SWParameterType.Default, SWParameterType.SPReturnValue, SWParameterType.Key 和 SWParameterType.Identity.默认值是SWParameterType.Default.

所有的属性都是可选的.
当ParameterType属性值是SWParameterType.SPReturnValue 时,即这个方法参数包含一个存储过程返回值,这个参数必须是传递参考的.

当SWCommandAttribute.CommandType值为SWCommandType.InsertUpdate时,ParameterType属性值可能是SWParameterType.Key 和 SWParameterType.Identity

InsertUpdate( 插入-更新 ) 命令
一个 INSERT 或者 UPDATE SQL语句,非常琐碎,但是却很常用.我在SqlWrapper库中添加下面的命令和参数,可以简单第创建插入和更新数据到表的方法.
SWCommandType.InsertUpdate标识创建一个 特定的  插入-更新 表达式.
SWCommandAttribute.CommandText 必须是一个表的名称.
SWParameterType.Identity标识 参数是一个标识表列,用来标识一个行.这个值必须被传递参考.
SWParameterType.Key 标识 这个参数是一个主键的一部分(不是标识表列),用来标识一个行.

下面是两个 插入-更新 的例子.

 

1.方法定义:
[SWCommand(SWCommandType.InsertUpdate, 
"Shippers")]
public abstract void ShippersInsertUpdate
    (
    [SWParameter(SWParameterType.Identity)]
ref int ShipperID,
    [SWParameter(
40)]string CompanyName,
    [SWParameter(
24)]string Phone
    );
SQL语句:
if(@ShipperID is NULL) 
begin  
    insert into [Shippers]([CompanyName], [Phone]) 
    values(@CompanyName, @Phone)  
    
    select @ShipperID 
= SCOPE_IDENTITY() 
end 
else 
begin  
    update [Shippers] 
set 
    [CompanyName]
=@CompanyName, 
    [Phone]
=@Phone 
    where [ShipperID]
=@ShipperID  
end
2.方法定义:
[SWCommand(SWCommandType.InsertUpdate, 
"Order Details")]
public abstract void OrderDetailsInsertUpdate
    (
    [SWParameter(SWParameterType.Key)]
int OrderID,
    [SWParameter(SWParameterType.Key)]
int ProductID,
    Decimal UnitPrice,
    Int16 Quantity,
    
float Discount
    );

SQL语句:

update [Order Details] 
set 
[OrderID]
=@OrderID, 
[ProductID]
=@ProductID, 
[UnitPrice]
=@UnitPrice, 
[Quantity]
=@Quantity, 
[Discount]
=@Discount 
where [OrderID]
=@OrderID and [ProductID]=@ProductID  

if (@@rowcount = 0)  
    insert into [Order Details]([OrderID], [ProductID], [UnitPrice], 
    [Quantity], [Discount]) 
    values(@OrderID, @ProductID, @UnitPrice, @Quantity, @Discount)

同你看到的一样,第一个例子中@ShipperID和NULL比较,默认的,这个值可能合适的是参数等于或者小于0.如果你要你能设置其他的值,你可以设置SWParameter.TreatAsNull属性,那么你设置的值将被转化为null.

在你的应用程序中创建数据访问层

SqlWrapper苦包含了基本的类:DataAccessLayerBase,它能够被利用很少的代码定制你自己的自定义数据访问层(DAL).所有要做的工作就是继承DataAccessLayerBase类,然后定义你要包装的类的属性如下:
public YourWrapperClass YourPropertyName
{
    get
    {
      return (YourWrapperClass)GetWrapped();
    }
}

这就是全部了.你可以添加任何其他你需要的成员.这个DAL的例子能创建上边的类Orders2,UserClass1.
public class MyDAL : DataAccessLayerBase
{
    public UserClass1 UserClass1{get{return (UserClass1)GetWrapped();}}
    public Orders2 Orders2{get{return (Orders2)GetWrapped();}}
}

这是一个DAL类的图:
 

这有一个如何使用你的数据访问层的例子:
MyDAL dal = new MyDAL();
dal.Init(cnn, true, true);
int c = dal.Orders2.CountByEmployee(6);
DataTable dt = dal.UserClass1.Method1();

在你使用一个DAL内的一个对象类,你要调用一个重载方法Init(),它继承于DataAccessLayerBase类.

public void Init(SqlConnection connection, bool autoCloseConnection,
          bool ownsConnection);
public void Init(string connectionString, bool autoCloseConnection);

这些方法非常重要.因为在你连接设置属性外,他们调用一个私有方法,GenerateAllWrapper(),它列举了你DAL层中类的所有方法.创建包装对象和保存它们到一个私有的哈希表,m_swTypes:
private void GenerateAllWrapped()
{
    MethodInfo[] mis = this.GetType().GetMethods();
    for(int i = 0; i < mis.Length; ++i)
    {
        Type type = mis[i].ReturnType;
        if(type.GetInterface(typeof(ISqlWrapperBase).FullName) ==
                 typeof(ISqlWrapperBase))
        {
            if(mis[i].Name.StartsWith("get_"))
            {
                if(!m_swTypes.ContainsKey(mis[i].Name))
                {
                    ISqlWrapperBase sw = WrapFactory.Create(type);
                    m_swTypes[mis[i].Name] = sw;
                }
            }
        }
    }
}

你曾经定义的属性,调用包含方法GetWrapped(),将查找一个调用方法的名字,从m_swTypes返回一个正确的对象
protected ISqlWrapperBase GetWrapped()
{
    MethodInfo mi = (MethodInfo)(new StackTrace().GetFrame(1).GetMethod());
    ISqlWrapperBase res = (ISqlWrapperBase)m_swTypes[mi.Name];
    if(res == null)
    {
        throw new SqlWrapperException("The object is not initialized.");
    }
    return res;
}

DataAccessLayerBase 使用下面三个方法,来简单第支持事务.
BeginTransaction() 和BeginTransaction(IsolationLevel iso) 打开一个新的事务.
RollbackTransaction() 回滚一个打开的事务.
CommitTransaction() 提交一个打开的事务.
包装类的Connection, Transaction 或者 AutoCloseTransaction属性将自动更新,当它们改变的时候.

另外,DataAccessLayerBase类还有几个方法(ExecuteDataSet(), ExecuteDataTable(), ExecuteScalar()和ExecuteNonQuery()),帮助你在特殊查询时使用sql语句.

在CodeProject上看了数据库的文章,感觉很好.性能也可以(感觉和访问数据库比,性能可以不计).就翻译了,水平有限,请指正. (翻译的不好的例子)
关于性能:
 1.利用Emit减少性能损失http://yok.cnblogs.com/archive/2005/11/03/267952.html
2.反射性能分析: http://www.chinaitclub.org/forums/350/ShowPost.aspx

注: 在.NET2.0中,插入和更新不能使用,发生错误.

posted on 2005-11-16 08:21  葵花宝典  阅读(1386)  评论(1编辑  收藏  举报