使用 SQL Server Service Broker 自动刷新 Entity Framework 数据






4.77/5 (32投票s)
如何在数据库更改时使用 SqlDependency 自动刷新 Entity Framework 对象集
在数据库更改时自动更新您的应用程序
从 SQL Server 2005 开始,微软引入了一个名为 SQL Server Service Broker (SSB) 的新特性,它允许 SQL Server 与其他地方进行通信(和通知!)。.NET Framework 中有一个名为 SqlDependency 的类,它允许通知客户端数据库的更改。要使其工作,SqlDependency
构造函数需要一个 SqlCommand
- 并且当您使用 Entity Framework 时,这是一个问题。SqlCommand
对象隐藏在 EF 的对象深处。
很久以前,Fritz Onion (之前发布在 http://www.pluralsight-training.net/community/blogs/fritz/archive/2006/06/15/27694.aspx)的一篇很棒的帖子揭示了一个“魔法饼干”,它允许将 SqlDependency
对象连接到隐藏的 SqlCommand
。此技术已在开源项目 LinqToCache 中使用,该项目使用 LINQ to SQL 维护缓存。
在本文中,我将介绍一种使用 SQL Server Service Broker 通知以自动更新 Entity Framework 对象集的方法。
AutoRefresh 扩展方法
您可能熟悉 Refresh
方法,该方法允许您使用数据库中的新数据更新对象集(表的表示形式)在您的对象上下文中
this.MyObjectContext.Refresh(RefreshMode.StoreWins, this.MyObjectContext.MyObjectSet);
使用下面概述的 ServiceBrokerUtility
,您可以轻松地不仅一次刷新对象集,而且在数据库中发生的每次更新时自动刷新。 您所需要做的就是用 ServiceBrokerUtility
中定义的新的 AutoRefresh
扩展方法替换 Refresh
方法。
this.MyObjectContext.AutoRefresh
(RefreshMode.StoreWins, this.MyObjectContext.MyObjectSet);
ServiceBrokerUtility
这是所有魔法的代码(来自饼干)
public static class ServiceBrokerUtility
{
private static List<string> connectionStrings = new List<string>();
private const string sqlDependencyCookie = "MS.SqlDependencyCookie";
private static ObjectContext ctx;
private static RefreshMode refreshMode;
private static readonly Dictionary<string, IEnumerable> collections = new Dictionary<string, IEnumerable>();
static public void AutoRefresh(this ObjectContext ctx,
RefreshMode refreshMode, IEnumerable collection)
{
var csInEF = ctx.Connection.ConnectionString;
var csName = csInEF.Replace("name=", "").Trim();
var csForEF =
System.Configuration.ConfigurationManager.ConnectionStrings
[csName].ConnectionString;
var newConnectionString = new
System.Data.EntityClient.EntityConnectionStringBuilder
(csForEF).ProviderConnectionString;
if (!connectionStrings.Contains(newConnectionString))
{
connectionStrings.Add(newConnectionString);
SqlDependency.Start(newConnectionString);
}
ServiceBrokerUtility.ctx = ctx;
ServiceBrokerUtility.refreshMode = refreshMode;
AutoRefresh(collection);
}
static public void AutoRefresh(IEnumerable collection)
{
var oldCookie = CallContext.GetData(sqlDependencyCookie);
try
{
var dependency = new SqlDependency();
collections.Add(dependency.Id, collection);
CallContext.SetData(sqlDependencyCookie, dependency.Id);
dependency.OnChange += dependency_OnChange;
ctx.Refresh(refreshMode, collection);
}
finally
{
CallContext.SetData(sqlDependencyCookie, oldCookie);
}
}
static void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Info == SqlNotificationInfo.Invalid)
{
Debug.Print("SqlNotification: A statement was provided that cannot be notified.");
return;
}
try{
var id =((SqlDependency)sender).Id;
IEnumerable collection;
if (collections.TryGetValue(id, out collection))
{
collections.Remove(id);
AutoRefresh(collection);
var notifyRefresh = collection as INotifyRefresh;
if (notifyRefresh != null)
System.Windows.Application.Current.Dispatcher.BeginInvoke(
(Action)(notifyRefresh.OnRefresh));
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print("Error in OnChange: {0}", ex.Message);
}
}
}
几乎可以使用
这不是很棒吗? 一旦我开发了该代码,我就立即将我的(现在自动更新的)对象集绑定到 DataGrid
的 ItemsSource
属性,并在 Visual Studio 中按下了 F5 以编译并运行我的程序。 我显示了 Visual Studio 的服务器资源管理器,右键单击我的表,选择了“显示表数据”,从而更改了数据库中字段的值。
哒哒 - 该字段的值在我的程序的网格中相应地发生了变化! 在我享受了那场游戏一段时间后,我尝试向数据库中添加一个新记录。 令人失望的是,我的网格没有以任何方式响应新记录。
调试器显示,只要在我的对象集中创建新记录,新记录就会到达,但 DataGrid
并不在意。 经过一番研究,我发现(出于某种原因)对象集缺少对 INotifyCollectionChanged
接口的任何支持。
新问题 - 好的,我们需要更多代码。
AutoRefreshWrapper
此类采用一个对象集,并输出一个具有适当通知支持的类型化的可枚举,用于控件,当然还有 ServiceBrokerUtility
的自动刷新功能。
使用此包装类,我们可以轻松地在视图模型中实现一个属性,我们可以使用它来绑定到
public class MyViewModel
{
public MyViewModel()
{
..
MyThings = new AutoRefreshWrapper<thing>(
MyObjectContext.MyObjectSet, RefreshMode.StoreWins);
..
}
public IEnumerable<thing> MyThings { get; private set; }
..
}
这是包装类
public class AutoRefreshWrapper<T> : IEnumerable<T>, INotifyRefresh
where T : EntityObject
{
private IEnumerable<T> objectQuery;
public AutoRefreshWrapper(ObjectQuery<T> objectQuery, RefreshMode refreshMode)
{
this.objectQuery = objectQuery;
objectQuery.Context.AutoRefresh(refreshMode, this);
}
public IEnumerator<T> GetEnumerator()
{
return objectQuery.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public void OnRefresh()
{
try
{
if (this.CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print("Error in OnRefresh: {0}", ex.Message);
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
此类实现了一个新的接口 INotifyRefresh
public interface INotifyRefresh : INotifyCollectionChanged
{
void OnRefresh();
}
最后,整个解决方案对我很有效。 但是要小心 - 关于与 Service Broker 配合使用的查询,存在一些限制。 最后: “魔法饼干”是一个未公开的功能,因此您应该小心。