视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
采用SQLServer2005Broker和SqlDependency类来提供数据更改通知(
2020-11-09 07:53:37 责编:小采
文档


在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) 在webconfig节中添加节,并配置连接字符串。

b) 在webconfig节中添加

(此项配置全局缓存设置,可选)

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



数据访问类:

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" %>





无标题页




行 









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服务器会稍微增加缓存调度和内存增加的负担,并且在数据库相应表发生更改后服务器会清除该表相关的所有缓存。

下载本文
显示全文
专题