使用LINQ-to-SQL跟踪对数据库的外部更改

时间:2009-12-15 21:31:01

标签: c# sql database linq linq-to-sql

有没有办法让SQL Server 2005回调到连接的应用程序,以便连接的应用程序知道表中的记录何时使用同一数据库的另一个应用程序修改了字段?

一个简单的例子是同一个应用程序的两个实例连接到同一个数据库中的同一个表。当应用程序的一个实例对表进行更改时,另一个实例将收到有关已更改内容的通知,并且能够在数据库中查询更改。

更新

非常感谢你们的帮助。我甚至都不知道要查找SqlDependency类。我已经按照本页http://msdn.microsoft.com/en-us/a52dhwx7.aspx中的说明创建了SqlDependency测试演示。但是,我无法让它发挥作用。我从未看到OnChange事件被调用。

我还尝试使用说明作为指导修改我自己的应用程序,但没有运气。我在下面列出了我自己的应用程序中的代码。基本上,Position表具有PositionID字段以及LocationX和LocationY字段。我写了另一个应用程序,允许我更新给定行的LocationX字段。

我错过了什么?为什么数据库更改不会触发我的偶数处理程序?

更新#2

另请注意,我正在为我的命令使用硬编码的SQL字符串。我宁愿不使用注释掉的LINQ语句。以这种方式使用LINQ来生成将用于构建命令的SQL字符串是否可以?

更新#3

所以我设法弄清楚下面的代码有什么问题。显然你必须执行一次命令才会有数据缓存,否则服务器不知道何时通知你?我添加了一行来使用我的SqlCommand执行DataAdapter.Fill(),现在这个事件似乎在预期时触发。

这让我想到了下一个问题。 SqlDependency.OnChange事件只让您知道某些内容已更改。我怎样才能从旧的DataSet和新的DataSet中找出逐行更改的内容?

我当然可以再次阅读整个查询并更新我的所有数据结构,但这似乎过分了。

我可以调用DataContext.Refresh()并让它对我的数据结构进行所有更新,但这似乎不会触发任何DataContext生成的OnChanging()事件。似乎Refresh()实际上撕下了我的所有结构并创建了新的结构。所以我永远无法弄清楚发生了什么变化。

有人有任何建议吗?

public partial class MainForm : Form
  {
    private ArpPhase2DbContextDataContext db = null;
    private SqlConnection connection = null;
    private SqlCommand command = null;

    public MainForm()
    {
      InitializeComponent();
    }

    private void MainForm_Load(object sender, EventArgs e)
    {
      this.canRequestNotifications();
      this.db = ArpPhase2DbContextDataContext.Instance;
      this.setupSqlDependency();
    }

    private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
    {
      SqlDependency.Stop(this.db.Connection.ConnectionString);

      if (this.connection != null)
      {
        this.connection.Close();
      }

      this.db.SubmitChanges();
    }

    private bool canRequestNotifications()
    {
      try
      {
        SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
        perm.Demand();

        return true;
      }
      catch
      {
        return false;
      }
    }

    private void setupSqlDependency()
    {
      // Remove any existing dependency connection, then create a new one.
      SqlDependency.Stop(this.db.Connection.ConnectionString);
      SqlDependency.Start(this.db.Connection.ConnectionString);

      if (this.connection == null)
      {
        this.connection = new SqlConnection(this.db.Connection.ConnectionString);
      }

      if (this.command == null)
      {
        var sql = (from position in this.db.Positions
                   select position);

        //string commandString = sql.ToString();
        string commandString = "SELECT * FROM Positions;";
        this.command = new SqlCommand(commandString, connection);
      }

      this.getData();
    }

    private void getData()
    {
      // Make sure the command object does not already have
      // a notification object associated with it.
      this.command.Notification = null;

      // Create and bind the SqlDependency object
      // to the command object.
      SqlDependency dependency = new SqlDependency(this.command);
      dependency.OnChange += new OnChangeEventHandler(this.dependency_OnChange);
    }

    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
      // This event will occur on a thread pool thread.
      // Updating the UI from a worker thread is not permitted.
      // The following code checks to see if it is safe to
      // update the UI.
      ISynchronizeInvoke i = (ISynchronizeInvoke)this;

      // If InvokeRequired returns True, the code
      // is executing on a worker thread.
      if (i.InvokeRequired)
      {
        // Create a delegate to perform the thread switch.
        OnChangeEventHandler del = new OnChangeEventHandler(this.dependency_OnChange);

        object[] args = { sender, e };

        // Marshal the data from the worker thread
        // to the UI thread.
        i.BeginInvoke(del, args);

        return;
      }

      // Remove the handler, since it is only good
      // for a single notification.
      SqlDependency dependency = (SqlDependency)sender;

      dependency.OnChange -= this.dependency_OnChange;

      // Add information from the event arguments to the list box
      // for debugging purposes only.
      Console.WriteLine("Info: {0}, Source: {1}, Type: {2}", e.Info.ToString(),
        e.Source.ToString(), e.Type.ToString());

      // Rebind the dependency.
      this.setupSqlDependency();
    }
  }

3 个答案:

答案 0 :(得分:2)

SQL Server可以使用Query Notifications执行此操作。 L2S没有内置任何东西来支持这一点,但也没有什么能阻止你在同一个应用程序中使用L2S之外的东西。

答案 1 :(得分:0)

查询通知使用索引视图技术检测数据更改,并在结果集可能更改时通知已订阅的查询。这是为ASP SqlCacheDependency提供高速缓存失效的技术。您可以在The Mysterious Notification了解更多信息。

在.Net Framework中,利用查询通知的最常用组件是SqlDependency。有关如何将linq2sql与SqlDependency集成的各种示例,如linqtosqlcache

您不应该使用此技术来监视经常更改的数据,而只能查看值得缓存的目录参考数据。设置和发送通知的成本非常高。

答案 2 :(得分:-4)

你为什么要这样做?

Linq-to-SQL在你开始使用它之前已经死了。

现在他们推EF,WCF-DS等(谁知道什么时候他们也会杀了他们)。

即使是查询通知也不再是一个安全的赌注(因为如果你的应用程序将持续超过几年,它们会非常脆弱。)