So as the title says DML queries fail when the QUOTED_IDENTIFIER is set to OFF whilst using SqlDependancy to monitor a table. I get the following errors for INSERT/UPDATE/DELETE statements:
- INSERT failed because the following SET options have incorrect settings: 'QUOTED_IDENTIFIER'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.
- UPDATE failed because the following SET options have incorrect settings: 'QUOTED_IDENTIFIER'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.
- DELETE failed because the following SET options have incorrect settings: 'QUOTED_IDENTIFIER'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.
Here is code to replicate the issue, its as minimal as i have been able to create.
I used SQL 2008 R2 and Visual Studio 2013 (C# using .Net Framework v4.5.1)
First create a test DB, table and populate it.
USE [master]
GO
CREATE DATABASE [BUG0001]
GO
ALTER DATABASE [BUG0001] SET ENABLE_BROKER
GO
USE [BUG0001]
GO
CREATE TABLE [dbo].[Test]
(
[RowVersion]
ROWVERSION,
[TestId]
INT
NOT NULL,
[Flipper]
BIT
NOT NULL,
CONSTRAINT [PK_dbo_Test_TestId] PRIMARY KEY ([TestId])
)
GO
INSERT INTO [dbo].[Test] ([TestId], [Flipper])
VALUES (1, 0);
Now create a console application and run it (remember to set the connection string for your DB)
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BUG0001
{
class Program
{
const string CN_STRING = "Server=TESTSERVER;Database=BUG0001;Trusted_Connection=True;";
const string SQL_DATA = "SELECT [RowVersion], [TestId], [Flipper] FROM [dbo].[Test] WHERE [RowVersion] > @LastRowVersion ORDER BY [RowVersion];";
const int FIELD_ROW_VERSION = 0;
const int FIELD_TEST_ID = 1;
const int FIELD_FLIPPER = 2;
static SqlConnection cn = null;
static byte[] lastRowVersion = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
static void Main(string[] args)
{
SqlDependency.Start(CN_STRING);
using (cn = new SqlConnection(CN_STRING))
{
cn.Open();
Console.WriteLine("Press any key to exit.");
Console.WriteLine();
GetData();
ConsoleKeyInfo keyInfo;
do { keyInfo = Console.ReadKey(true); }
while (keyInfo.Key == ConsoleKey.LeftWindows || keyInfo.Key == ConsoleKey.RightWindows);
cn.Close();
}
SqlDependency.Stop(CN_STRING);
}
static void GetData()
{
using (var cmd = cn.CreateCommand())
{
cmd.CommandText = SQL_DATA;
cmd.Parameters.Add(new SqlParameter("@LastRowVersion", lastRowVersion));
/* The SqlDependency is a one event only mechignisum and must be reset each time the event is triggered. */
var depenency = new SqlDependency(cmd);
depenency.OnChange += depenency_OnChange;
RenderResults(cmd);
}
}
static void depenency_OnChange(object sender, SqlNotificationEventArgs e)
{
Console.WriteLine(e.Info);
Console.WriteLine(e.Source);
Console.WriteLine(e.Type);
Console.WriteLine();
GetData();
}
static void RenderResults(SqlCommand cmd)
{
try
{
using (var reader = cmd.ExecuteReader())
{
foreach (DbDataRecord record in reader)
{
var rowVersion = (byte[])record.GetValue(FIELD_ROW_VERSION);
Console.WriteLine(
"[RowVersion] = 0x{0}\r\n[TestId] = '{1}'\r\n[Flipper] = '{2}'\r\n",
BitConverter.ToString(rowVersion).Replace("-", string.Empty),
record.GetInt32(FIELD_TEST_ID),
record.GetBoolean(FIELD_FLIPPER)
);
lastRowVersion = rowVersion;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
Now run the following using SQL Management Studio to get the errors
--SET QUOTED_IDENTIFIER ON /* Without Error */
SET QUOTED_IDENTIFIER OFF /* With Error */
GO
INSERT INTO [dbo].[Test] ([TestId], [Flipper]) VALUES (2, 0);
GO
UPDATE [dbo].[Test] SET [Flipper] = ~[Flipper] WHERE [TestId] = 1
GO
SELECT * FROM [dbo].[Test]
GO
DELETE FROM [dbo].[Test] WHERE [TestId] = 2
GO
SELECT * FROM [dbo].[Test]
GO
So much like if you said to a doctor "it hurts when i touch it" and the doctor responds "well don't touch it" i know i can use "SET QUOTED_IDENTIFIER ON" in my application but i'm monitoring a table created and maintained by another application and can't control how they set the QUOTED_IDENTIFIER.
Is there a way to monitor the table without causing the issue demonstrated above?
Also can anybody explain why it happens?
I also found a similar question here (SQL Server: INSERT/UPDATE/DELETE failed because the following SET options have incorrect settings: ‘QUOTED_IDENTIFIER’) but it provided no clues on how to deal with this issue.