Skip to content

SqlNotificationInfo.None enum value missing, causing unnecessary exceptions. #1376

@ascott18

Description

@ascott18

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.

image

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions