4

IsolationLevel.ReadUncommitedSqlDependency を機能させることができましたが、SqlDependency とは無関係の SQL トランザクションであると考えていたものを使用しない場合に限ります。

トランザクションで使用するIsolationLevel.ReadUncommittedと (以下に大きくコメントされています)、SqlDependency サブスクリプションが失敗し、次のOnChange通知がすぐに表示されます。

sqlNotificationEventArgs.Info = "Isolation";
sqlNotificationEventArgs.Source = "Statement";
sqlNotificationEventArgs.Type = "Subscribe";

IsolationLevel を削除すると、すべてが期待どおりに機能します (もちろん、分離は正しくありません)。

これが私の関連コードです:

private static string connString = "the connection string";
[MTAThread]
private static void Main(string[] args)
    while(true)
    {
        using (var context = new LinqDataContext(connString))
        {
            var conn = context.Connection;
            conn.Open();
            /***********************************************************************/
            /* Remove `IsolationLevel.ReadUncommitted` and the SqlDependency works */
            /***********************************************************************/
            using (var trans = conn.BeginTransaction(IsolationLevel.ReadUncommitted))
            {
                // simplified query, the real query uses UPDATE OUTPUT INSERTED
                const string sqlCommand = "SELECT [Columns] FROM dbo.[TABLE] WHERE [Status] = 'ready'";
                results = conn.Query({transaction: trans, sql: sqlCommand});
                trans.Commit();
            }
            DoAwesomeStuffWithTheResults(results, context);
        }
        WaitForWork();
    }
}

SqlDependency 関連のコード:

private static ManualResetEvent _quitEvent = new ManualResetEvent(false);

/// <summary>
/// Sets up a SqlDependency a doesn't return until it receives a Change notification
/// </summary>
private static void WaitForWork(){
    // in case we have dependency running we need to go a head and stop it first. 
    SqlDependency.Stop(connString);
    SqlDependency.Start(connString);

    using (var conn = new SqlConnection(connString))
    {
        using (var cmd = new SqlCommand("SELECT [Status] From dbo.[TABLE]", conn))
        {
            cmd.Notification = null;

            var dependency = new SqlDependency(cmd);
            dependency.OnChange += dependency_OnDataChangedDelegate;

            conn.Open();

            cmd.ExecuteReader();
        }
    }
    _quitEvent.WaitOne();
    SqlDependency.Stop(connString);
}
private static void dependency_OnDataChangedDelegate(object sender, SqlNotificationEventArgs e)
{
    ((SqlDependency)sender).OnChange -= dependency_OnDataChangedDelegate;
    _quitEvent.Set();
}

SqlDependency を設定する前に、コンテキスト、その接続、およびトランザクションを適切に破棄したように感じますが、そうではないようです。

ここで何が間違っていますか?

4

2 に答える 2

8

作業開始おめでとうございますSqlDependency(私はまったく皮肉を言っているわけではありません。多くの人がこれで失敗していました)。

ここで、MSDNの「通知用クエリの作成」トピックを読んでください。この要件を含む、クエリが通知に対して有効な条件が表示されます。

ステートメントは、READ_UNCOMMITTED または SNAPSHOT 分離レベルで実行してはなりません。

仕組みの基本SqlDependencyについて書きましたが、誤解が解けるかもしれません。また、サイド ノードとして、Linq を使用しているので、クエリとの間のブリッジを提供するLinqToCacheに興味があるかもしれません。LinqSqlDependency

別のコメント:しないStart()Stop()くださいSqlDependency。すぐに後悔するでしょう。Start()は、アプリの起動時に 1 回だけ呼び出され、Stop()アプリのシャットダウン時にも 1 回だけ呼び出されることになっています (厳密に言えば、appdomain のロードとアンロード中に呼び出されます)。

さて、あなたの問題について: 重要な分離レベルは、通知された queryの 1 つです。つまり、サブスクリプションをアタッチするクエリであり、実行するクエリではありません(ダーティ リードで UPDATE を実行する知恵や、何かにダーティ リードを使用する知恵についてUPDATEはコメントしません)。私が知る限り、あなたが示すコードは、read_uncommitted の下にクエリを投稿するべきではありません。そのセッションで後続のすべてのトランザクション (エルゴすべてのステートメント)を発行した後は、その分離レベルになります。(DataContext の破棄によって) 接続を閉じてから、別の接続を使用します。...接続プールを使用しない限り。無実の犠牲者のクラブへようこそ:)。SET TRANSACTION ISOLATION ...接続プーリングは、Close()/Open()境界を越えて分離レベルの変更をリークします。そして、それはあなたの問題です。簡単な解決策がいくつかあります。

そして、私たちが話している間、これも読む必要があります: Using Tables as Queues

于 2013-11-05T14:29:03.927 に答える
1

Remus Rusanu が回答で提供したヒントに基づいて更新されたコードを次に示します。

private static string connString = "the connection string";
[MTAThread]
private static void Main(string[] args)
    // Start() is supposed to be called exactly once, during app startup
    // and Stop() exactly once during app shutdown:
    SqlDependency.Start(connString);
    AppDomain.CurrentDomain.ProcessExit += delegate
    {
        SqlDependency.Stop(connString);
    };

    while(true) // to infinity, and beyond.
    {
        using (var context = new LinqDataContext(connString))
        {
            var conn = context.Connection;
            // Connection pooling leaks isolation level changes across 
            // Close()/Open() boundaries, use TransactionScope to avoid this.
            using (var scope = CreateTransactionScope(TransactionScopeOption.Required, transactionOptions))
            {
                conn.Open();
                const string sqlCommand = "UPDATE TOP(1) [Table] SET [Status] = 'budy' OUTPUT INSERTED.[Column], */... MORE ...*/ WHERE [Status] = 'ready'";
                results = conn.Query(sqlCommand);
                scope.Complete();
            }
            DoAwesomeStuffWithTheResults(results, context);
        }
        WaitForWork();
    }
}

SqlDependency 関連のコード:

/// <summary>
/// Sets up a SqlDependency and doesn't return until it receives 
/// a Change notification
/// </summary>
private static void WaitForWork(string connString)
{
    var changedEvent = new AutoResetEvent(false);
    OnChangeEventHandler dataChangedDelegate = (sender, e) => changedEvent.Set();
    using (var conn = new SqlConnection(connString))
    {
        using (var scope = Databases.TransactionUtils.CreateTransactionScope())
        {
            conn.Open();
            var txtCmd = "SELECT [FileID] FROM dbo.[File] WHERE [Status] = 'ready'";
            using (var cmd = new SqlCommand(txtCmd, conn))
            {
                var dependency = new SqlDependency(cmd);
                OnChangeEventHandler dataChangedDelegate = null;
                dataChangedDelegate = (sender, e) =>
                {
                    dependency.OnChange -= dataChangedDelegate;
                    changedEvent.Set();
                };
                dependency.OnChange += dataChangedDelegate;
                cmd.ExecuteScalar();
            }
            scope.Complete();
        }
    }
    changedEvent.WaitOne();
    dependency.OnChange -= dependencyOnDataChangedDelegate;
}

新しい TransactionScope コード:

/// <summary>
/// Using {the default} new TransactionScope Considered Harmful
/// http://blogs.msdn.com/b/dbrowne/archive/2010/06/03/using-new-transactionscope-considered-harmful.aspx
/// </summary>
private static TransactionScope CreateTransactionScope(System.Transactions.IsolationLevel isolationLevel = System.Transactions.IsolationLevel.ReadCommitted)
{
    var transactionOptions = new TransactionOptions
    {
        IsolationLevel = isolationLevel,
        Timeout = TransactionManager.MaximumTimeout
    };
    return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
}
于 2013-11-05T17:15:02.253 に答える