我设法让SqlDependency工作,但只要我不使用IsolationLevel.ReadUncommited
我认为是与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之前,我觉得我已经正确处理了上下文,它的连接和事务,但看起来情况并非如此。
我在这里做错了什么?
答案 0 :(得分:7)
祝贺SqlDependency
工作(我根本没有讽刺,很多人都失败了。)
现在是时候在MSDN上阅读Creating a Query for Notification主题了。您将看到查询对通知有效的条件,包括此要求:
该语句不得在READ_UNCOMMITTED或SNAPSHOT隔离级别下运行。
我写过关于the basics of how SqlDependency
works的文章,也许会解决一些误解。而且,作为副节点,由于您使用的是Linq,因此您可能会对LinqToCache感兴趣,the wisdom of using dirty reads for anything会在Linq
个查询与SqlDependency
之间建立桥梁。
另一条评论:不要Start()
和Stop()
你的SqlDependency
nilly-willy。你很快就会后悔的。应用程序启动期间Start()
应该只调用一次,而应用程序关闭期间Stop()
恰好一次调用(严格来说,是在appdomain加载和卸载期间)。
现在,关于您的问题:重要的隔离级别是通知的查询之一。这意味着,您附加订阅的查询,不您执行UPDATE
的查询(我不会评论在脏读取下执行UPDATE的智慧......或Connection pooling leaks isolation level changes across Close()
/Open()
boundaries)。据我所知,您显示的代码不应在read_uncommitted下发布查询。发出SET TRANSACTION ISOLATION ...
后,该会话中的所有后续事务(ergo all statements)将处于该隔离级别。您关闭连接(通过处理DataContext),然后使用不同的连接。除非......你使用连接池。欢迎来到无辜受害者俱乐部:)。 using new TransactionScope() Considered Harmful。那是你的问题。有一些简单的解决方案:
Open()
在我们谈话时,您还需要阅读此内容:{{3}}。
答案 1 :(得分: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);
}