您好,欢迎访问一九零五行业门户网

采用SQLServer2005 Broker和SqlDependency类来提供数据更改通知(

在sqlserver2005中, sql server service broker 用于创建会话以交换消息。 消息交换在目标和发起方这两端之间进行。 使用 sqldependency 订阅查询通知是直接的:sqldependency 对象将管理数据库中设置通知涉及到的复杂性。建立通知后,对象便会监视实现通知
       在sqlserver2005中,sql server service broker 用于创建会话以交换消息。 消息交换在目标和发起方这两端之间进行。
       使用 sqldependency 订阅查询通知是直接的:sqldependency 对象将管理数据库中设置通知涉及到的复杂性。建立通知后,对象便会监视实现通知的基础数据库对象,当 sql server 创建查询通知时,将在应用程序中调用事件处理程序。
       对于应用程序接收sql server service broker通知,只能获取到对应数据库表数据做了何种更新,而无法获取更新的数据,而我们却可以利用这个通知,来做缓存依赖,来达到缓存过期的目的。
       使用 sqldependency 订阅查询通知必须向sql server service broker提供制定规则的查询语句,一般来讲,必须是简单的sql查询语句(不能用*,不能用top,不能用函数,包括聚合函数,不能用子查询,包括where后的子查询,不能用外连接,自连接,不能用临时表,不能用变量,不能用视图,不能垮库,表名之前必须加类似dbo数据库所有者这样的前缀),
例如:select * from table1,select column1 from table1,select count(*) from table1 都是错误的sql查询语句,select column1 from dbo.table1 则是正确的语句。
       以下以一个实际的例子(sqldep项目)来说明如何使用serverbroker和sqldependency类来做缓存依赖,充分利用服务器资源和提高应用程序性能,并且封装以提供给开发人员最大的便利性,我们需要按照如下步骤操作:
1.     首先要在使用的数据库执行 alter database adventureworks set enable_broker 以启用该功能,执行时必须关闭所有可能锁表的操作和作业。
2.     打开sqldep示例,运行servicebrokertest.aspx以查看结果,查看代码。
3.     在现有应用程序中增加更改通知以及缓存机制。
a)      在webconfigconfiguration>节中添加connectionstrings>节,并配置连接字符串。
b)      在webconfigsystem.web>节中添加
caching>
cache percentagephysicalmemoryusedlimit=60 privatebytespolltime=00:05:00 />
caching> (此项配置全局缓存设置,可选)
c)      建立数据访问层,如何封装编写不限,只要具有返回数据的方法即可。
d)      嵌入或者重写dabase.cs中的protected virtual datatable getdatatable方法,具体请参考sqldep示例,该方法提供自动响应程序表发生的更改,自动设定缓存机制,封装此方法后,对于开发人员,只需要按照以往开发习惯提供任意sql语句编写程序获取数据。
e)      继承dabase类或自己编写具有protected virtual datatable getdatatable方法的类,并调用该方法,参见dadimcustomer.cs。
以下我们以sqldep做测试,以验证可行性及其性能:
我们以sqlserver2005自带的adventureworksdw数据库中的dimcustomer表为例,该表有29列,各种数据类型都有,18484行,7984kb数据,平均每行0.43kb。
我们以每次查询20页,查询该表的所有列作为测试。由于缓存的是查询结果,所以内存变化可以根据每次查询的数据量为基准,20行大小大约是8.6kb,缓存默认设置是允许使用服务器内存的90%,
假设对应的数据库表不做更新操作,假设web服务器有1g的内存可使用缓存,
则可以缓存12万份不重复结果(这里没有计算.net本身每个数据实体,每个缓存相关数据所占有的空间,相对于数据而言可以忽略不计),
缓存命中率大都集中在常用查询,例如商品列表第一页,某个商品分类第一页等,一旦有某个用户使用了查询,则其他用户可以不需要访问数据库即可得到所需数据。即使缓存如果超过了程序规定的最大数据,.net运行时也会自动随即清空缓存,这并不影响程序运行。
以下附上完整代码: 
 webconfig文件:
code
    connectionstrings>
        add name=conn providername=system.data.sqlclient connectionstring=data source=localhost;initial catalog=adventureworksdw;user id=sa;password=sa/>
    connectionstrings>
数据访问类:
code
using system;
using system.configuration;
using system.data;
using system.data.sqlclient;
/**//// 
/// sqldbhelper 的摘要说明
/// 
public sealed class sqldbhelper
{
    单态模式#region 单态模式
    static sqldbhelper sqldbhelper = new sqldbhelper();
    public static sqldbhelper instance() { return sqldbhelper; }
    private sqldbhelper()
    {
        this.connectionstring = system.configuration.configurationmanager.connectionstrings[conn].connectionstring;
    }
    #endregion
private string connectionstring;
    public string connectionstring
    {
        get { return connectionstring; }
        set { connectionstring = value; }
    }
public datatable getdatatable(string strsql)
    {
        string connstr = this.connectionstring;
        using (sqlconnection conn = new sqlconnection(connstr))
        {
            sqlcommand comm = new sqlcommand(strsql, conn);
            sqldataadapter da = new sqldataadapter(comm);
            datatable dt = new datatable();
            da.fill(dt);
return dt;
        }
}
public int executenonquery(string strsql)
    {
        string connstr = this.connectionstring;
        using (sqlconnection conn = new sqlconnection(connstr))
        {
            sqlcommand comm = new sqlcommand(strsql, conn);
            if (conn.state == connectionstate.closed) conn.open();
            return comm.executenonquery();
        }
    }
public sqldependency addsqldependency(string strsql, onchangeeventhandler sqldep_onchange)
    {
        string connstr = this.connectionstring;
        using (sqlconnection conn = new sqlconnection(connstr))
        {
            sqlcommand comm = new sqlcommand(strsql, conn);
            // 添加依赖
            sqldependency sqldep = new sqldependency(comm);
            sqldep.onchange += sqldep_onchange;
            if (conn.state == connectionstate.closed) conn.open();
            comm.executenonquery();
            return sqldep;
        }
    }
/**//// 
    /// 对表增加依赖列,用于sql依赖,或者用某个int列也可
    /// 
    /// 表名,如果不是dbo所有者,请提供包括所有者的完整表名
    /// 
    public int adddependencycloumn(string tablename)
    {
        return this.executenonquery(string.format(declare @num int 
            + set @num = (select count(*) from syscolumns where id=object_id('{0}') and name = 'dep') 
            + if @num = 0 alter table {0} add dep bit not null constraint dep{0} default 0 , tablename));
    }
}
code
using system;
using system.data;
using system.data.sqlclient;
using system.web;
using system.web.caching;
/// 
/// 要使用sqlserver2005 service broker,首先要在使用的数据库执行 alter database adventureworks set enable_broker 以启用该功能,执行时必须关闭所有可能锁表的操作和作业
/// 使用依赖的sql语句,不能用*,不能用top,不能用函数,包括聚合函数,不能用子查询,包括where后的子查询,
/// 不能用外连接,自连接,不能用临时表,不能用变量,不能用视图,不能垮库,表名之前必须加类似dbo数据库所有者这样的前缀
/// 依赖只针提供一次通知,所以重新发起某次查询则需要重新提供依赖sql语句
/// 
/// 优点:此应用比较适合访问次数大于更新次数的情况,访问次数比更新次数越多,速度提升越明显
/// 缺点:对服务器内存要求较高
/// 
public abstract class dabase
{
    private sqldbhelper sqldbhelper;
    //
    private cache pagecache;
public dabase(cache cache)
    {
        sqldbhelper = sqldbhelper.instance();
        this.pagecache = cache;
    }
/// 
    /// 清除缓存
    /// 
    /// 缓存名称
    protected virtual void clearcache(string cachename)
    {
        system.collections.idictionaryenumerator cacheenum = pagecache.getenumerator();
        while (cacheenum.movenext())
        {
            // 只清除与此业务相关的缓存,根据表名
            if (cacheenum.key.tostring().tolower().indexof(cachename.tolower()) > 0)
                pagecache.remove(cacheenum.key.tostring());
        }
    }
/// 
    /// 创建service borker通知(请确认service borker已开启),自动响应程序表发生的更改,自动设定缓存机制
    /// 
    /// system.web.caching.cache对象
    /// 查询数据的sql语句
    /// 数据库表所有者
    /// 表名
    /// 列名,随意某个小列(最好是bit,tinyint,varchar(1),int)
    /// 
    protected virtual datatable getdatatable(string selectsql, string dbowner, string tablename, string column)
    {
        // 用于service broker跟踪的表范围sql
        string depsql = string.format(select {0} from {1}.{2}, column, dbowner, tablename);
datatable dt = new datatable();
        if (pagecache[selectsql] != null)
            dt = pagecache[selectsql] as datatable;
        else
        {
            // 触发行级依赖,如果该表的指定范围内的行被修改,则会收到sqlserver的通知,并且清空相应缓存
            sqldependency sqldep = sqldbhelper.addsqldependency(depsql,
                delegate(object sender, sqlnotificationeventargs e) 
                {
                    if (e.info == sqlnotificationinfo.invalid)
                    {
                        // sqldbhelper.executenonquery(alter database adventureworksdw set enable_broker);
                        // 写文件,数据库未开启service broker或者提供了无法通知的语句,例如没有写包括数据库所有者的表名。
                    }
                    this.clearcache(tablename); 
                });
            dt = sqldbhelper.getdatatable(selectsql);
            pagecache[selectsql] = dt;
        }
        return dt;
}
}
code
using system;
using system.data;
using system.web;
using system.web.caching;
public class dimcustomer : dabase
{
    public dimcustomer(cache pagecache) : base(pagecache) { }
#region 分页查询顾客信息
    public datatable selectdimcustomer(int startindex, int maxindex)
    {
        // 用于查询的sql语句
        string strsql = string.format(with t as ( 
            +     select row_number() over(order by customerkey desc) as rownum, * 
            +     from dimcustomer where '1' = '1' 
            + ) select * from t where rownum between {0} and {1} , startindex, maxindex);
return base.getdatatable(strsql, dbo, dimcustomer, customerkey);
    }
#endregion
}
测试页面:
code
@ page language=c# autoeventwireup=true codefile=servicebrokertest.aspx.cs inherits=servicebrokertest %>
doctype html public -//w3c//dtd xhtml 1.0 transitional//en http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd>
html xmlns=http://www.w3.org/1999/xhtml >
head runat=server>
    title>无标题页title>
head>
body>
    form id=form1 runat=server>
    div>
        asp:textbox id=txt1 text=1 runat=server>asp:textbox>到asp:textbox id=txt2 text=20 runat=server>asp:textbox>行asp:button id=btn1 runat=server text=获取数据 onclick=btn1_click />
        br /> 
        asp:gridview id=gv1 runat=server autogeneratecolumns=true>asp:gridview>
    div>
    form>
body>
html>
using system;
using system.data;
using system.configuration;
using system.collections;
using system.web;
using system.web.security;
using system.web.ui;
using system.web.ui.webcontrols;
using system.web.ui.webcontrols.webparts;
using system.web.ui.htmlcontrols;
public partial class servicebrokertest : system.web.ui.page
{
    protected void page_load(object sender, eventargs e)
    {
}
protected void btn1_click(object sender, eventargs e)
    {
        dimcustomer da = new dimcustomer(this.cache);
        this.gv1.datasource = da.selectdimcustomer(convert.toint32(this.txt1.text), convert.toint32(this.txt2.text));
        this.gv1.databind();
//sqldbhelper sqldbhelper = sqldbhelper.instance();
        //datatable dt = sqldbhelper.getdatatable(select top 10 * from dimcustomer);
        //this.gv1.datasource = dt;
        //this.gv1.databind();
    }
}
总结:
       特点:特别适合更新不频繁但是读取频繁的表,会大大提高应用程序性能。
优点:缓存越多,sql服务器负担就越小,大大减少了io读操作以及网络传输占用。
缺点:web服务器会稍微增加缓存调度和内存增加的负担,并且在数据库相应表发生更改后服务器会清除该表相关的所有缓存。
其它类似信息

推荐信息