SqlClient: SqlNotificationInfo.None enum value missing, causing unnecessary exceptions.
Describe the bug
In SqlDependencyListener, when an incoming XML message is parsed, sometimes these messages look like this:
<qn:QueryNotification xmlns:qn="http://schemas.microsoft.com/SQL/Notifications/QueryNotification" id="4579" type="change" source="timeout" info="none" database_id="9" sid="0x01050000000000051500000002AAFCB5FFBA57356D2C83B7E9030000"><qn:Message>52cdaaaf-f5e5-4342-8c10-dfec05cf4950;ec06ef71-ec94-4e56-a11e-c1905ebf4cf9</qn:Message></qn:QueryNotification>
The info
attribute is parsed at https://github.com/dotnet/SqlClient/blob/main/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs#L1133-L1167. However, the enum that the value is parsed into does not included a value “None”, causing an exception to be thrown. It is ultimately handled by the containing try/catch, but there are a few issues with this:
- Exception flow and handling incurs a performance cost.
- The exception is System.ArgumentException and is thrown from mscorlib, which means that suppressing it in Visual Studio’s Exception Settings causes all such exceptions to be supressed, making debugging more difficult when there are other AgumentExceptions thrown by mscorlib that I do want to break on.
To reproduce
This is a bit weird to reproduce. The first few notifications where will be of Info == Error
. But sometimes, it will be "none"
in the XML, which then throws and gets turned into Unknown
.
Set exception settings in Visual Studio to break on all CLR exceptions.
// See https://aka.ms/new-console-template for more information
using Microsoft.Data.SqlClient;
var connectionString = "Server=localhost;Database=BugReproDb;Trusted_Connection=True;Command Timeout=5";
SqlDependency.Start(connectionString);
var connection = new SqlConnection(connectionString);
connection.Open();
var cmd1 = connection.CreateCommand();
cmd1.CommandText = "IF OBJECT_ID('TestTable') IS NULL CREATE TABLE TestTable (c1 int);";
cmd1.ExecuteNonQuery();
cmd1.Dispose();
Console.WriteLine("Usually the notification here will be 'Change Timeout Error'.");
Console.WriteLine("But sometimes, it will be 'Change Timeout Unknown'. This is the error.");
while (true)
{
var tcs = new TaskCompletionSource();
var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT c1 FROM dbo.TestTable";
var dependency = new SqlDependency(cmd, null, (int)1);
dependency.OnChange += (o, e) => {
Console.WriteLine($"{e.Type} {e.Source} {e.Info}");
if (e.Info == SqlNotificationInfo.Unknown)
{
Environment.Exit(0);
}
tcs.SetResult();
};
cmd.ExecuteNonQuery();
await tcs.Task;
cmd.Dispose();
}
Expected behavior
SqlNotificationInfo.None
is defined so exception flow isn’t needed.
Further technical details
Microsoft.Data.SqlClient version: 3.0.1 .NET target: net6.0 SQL Server version: 15.0.2000.5 Operating system: Win 10
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 18 (13 by maintainers)
I agree, I’ll make a PR for it.
Thanks, it works (added a review to the PR).
On an unrelated note, I had to add “TrustServerCertificate=True” to my connection string (connecting to localhost). I’m guessing the defaults around cert trusting are being changed in 4.0?
I think we have to add handling of it somehow but I don’t know whether we want to add it to the enum. If we’re going to change the enum (which is a breaking change) then it would probably be a good idea to ask the Sql Server team what possible values it can take from their side and make sure everything is covered. It’s clearly no longer limited to the originally defined values.