65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (32投票s)

2011年7月31日

CPOL

3分钟阅读

viewsIcon

178040

downloadIcon

3799

如何在数据库更改时使用 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);
    }
  }
}

几乎可以使用

这不是很棒吗? 一旦我开发了该代码,我就立即将我的(现在自动更新的)对象集绑定到 DataGridItemsSource 属性,并在 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 配合使用的查询,存在一些限制。 最后: “魔法饼干”是一个未公开的功能,因此您应该小心。

© . All rights reserved.