Dapper - return GUID generated by SQL Server - dapper

All of the Dapper posts on S/O related to returning a value after insert seem to relate only to the Identity value. I have tried to apply the logic in those answers to my issue, but it's not working.
I want to return a GUID generated by SQL Server which is not an Identity field. This is my code:
public bool Insert(Record record)
{
SqlConnection connection = new SqlConnection(_configuration.GetConnectionString("Production"));
connection.Open();
using (connection)
{
string query = "DECLARE #RSGUID uniqueidentifier SET #RSGUID = NEWID(); INSERT INTO [dbo].[Table] ([Result], [ResultSetKey]) VALUES (#Result, #RSGUID); SELECT #RSGUID";
// it's this next line that I'm confused on:
var resultSetKey = connection.Query<string>(query, #RSGUID).Single();
return connection.Execute(query, record) > 0;
}
}
I know the var resultsSetKey line is not correct, and this is what I need help with. How can I get the GUID generated by SQL Server into a variable?

I can't test right now, but this should work:
public bool Insert(Record record)
{
using (var connection = new SqlConnection(_configuration.GetConnectionString("Production"))
{
string query = "DECLARE #RSGUID uniqueidentifier; SET #RSGUID = NEWID(); INSERT INTO [dbo].[Table] ([Result], [ResultSetKey]) VALUES (#Result, #RSGUID); SELECT #RSGUID";
var resultSetKey = connection.ExecuteScalar<string>(query).SingleOrDefault();
return !String.IsNullOrEmpty(resultSetKey);
}
}
You can get a kickstart with Dapper following the tutorial here:
https://medium.com/dapper-net/get-started-with-dapper-net-591592c335aa
but honestly it's really not clear what you are trying to do. What is the #Result variable that you are using in the T-SQL code?

A very old question, I know - but I've just had exactly the same issue here and found a working solution, so I'm sure somebody else will in benefit from this late answer in the future.
The following works as desired. The key for me was understanding the datatype to use in C# to contain the returned stream_id value, which is System.Guid, not string. Though of course you may wish to convert it to a string to display it to a user for example as plain text.
My stored procedure for entering a new file into the filetable is
USE [dbname]
GO
/****** Object: StoredProcedure [dbo].[newFile] Script Date: 28/11/2022 14:34:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[newFile]
#fileBinary varbinary(max) = 0,
#fileName varchar(256) = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #newFileId table (stream_id uniqueidentifier);
INSERT INTO dbo.files (file_stream, name) OUTPUT INSERTED.[stream_id] into #newFileId
VALUES (#fileBinary, #fileName);
SELECT * FROM #newFileId
END
And here is the method to add said new file programmatically (I'm fully aware this example includes no error handling etc)
public class fileAccess
{
public System.Guid newFileId;
public void uploadNewFile(byte[] newFileBinary, string newFileName)
{
var procedure = "[newFile]";
var parameters = new
{
fileBinary = newFileBinary,
fileName = newFileName,
};
using (DbConnection connection = new Microsoft.Data.SqlClient.SqlConnection(helper.cnnVal("dbname")))
{
newFileId = connection.ExecuteScalar<System.Guid>(procedure, parameters, commandType: CommandType.StoredProcedure);
}
}
}
Called here (a test example, uploading a file by clicking a button, which pulls a file from my desktop)
private void button6_Click(object sender, EventArgs e)
{
fileAccess db = new fileAccess();
byte[] fileBinary = File.ReadAllBytes(#"C:\Users\username\Desktop\test.docx");
db.uploadNewFile(fileBinary, "test.docx");
string message = "New file succesfully uploaded. File ID: " + Convert.ToString(db.newFileId);
string caption = "Success.";
MessageBox.Show(message, caption, MessageBoxButtons.OK);
}

Related

How to call stored procedure from EntityFramework 6 with 'hierarchyid' parameter

I am developing a service using WebApi2 and EntityFramework6.
I have a legacy SQLServer DB that my service must work with.
That DB is using heavily the 'hierarchyid' data type and this type is used internally in DB's stored procedures.
Seems like EF6 is not supporting 'hierarchyid' data type, so i used this fork that adds support for 'hierarchyid'.
While the retrieval from the DB is working great with the 'hierarchyid' type, my problem is with the Stored Procedures that need a 'hierarchyid' as a parameter.
The stored procedure looks like this:
CREATE PROCEDURE [dbo].[GetSomethingByNodeId]
(
#startingRoot HIERARCHYID
,#return HIERARCHYID OUTPUT
)
My client code for invoking this stored procedure looks like this:
var param1 = new SqlParameter("#startingRoot", new HierarchyId("/"));
var param2 = new SqlParameter{ ParameterName = "#return", Value = 0, Direction = ParameterDirection.Output };
var obj = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId" #startingRoot, #return out", param1, param2).ToList();
But unfortunately calling this query throws an exception that says:
An unhandled exception of type 'System.ArgumentException' occurred in EntityFramework.SqlServer.dll
Additional information: No mapping exists from object type System.Data.Entity.Hierarchy.HierarchyId to a known managed provider native type.
Any ideas on how i can make this work?
Unfortunately, MetaType.GetMetaTypeFromValue does not allow to add types (all supported types are hardcoded).
I think you can accomplish your goal with nvarchar parameters and conversions.
In your C# code:
var param1 = new SqlParameter("#startingRoot", "/1/");
var param2 = new SqlParameter { ParameterName = "#return", Value = "", Size = 1000, Direction = ParameterDirection.Output };
var ids = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId #startingRoot, #return out", param1, param2).ToList();
var returnedId = new HierarchyId(param2.Value.ToString());
In your procedure (I wrote some test code inside):
CREATE PROCEDURE [dbo].[GetSomethingByNodeId]
(
#startingRoot nvarchar(max), #return nvarchar(max) OUTPUT
)
as
declare #hid hierarchyid = hierarchyid::Parse('/1/')
select #return = #hid.ToString()
declare #root hierarchyid = hierarchyid::Parse(#startingRoot)
select #root as field
Also, you can try to use Microsoft.SqlServer.Types and SqlHierarchyId type like this:
var sqlHierarchyId = SqlHierarchyId.Parse("/");
var param1 = new SqlParameter("#startingRoot", sqlHierarchyId) { UdtTypeName = "HierarchyId" };
But, I think, this is wrong direction.
Oleg's answer is correct, hierarchyid is still not integrated to the EF very well, and you should operate with strings in .net. Here is one more approach which was used from the first days of HierarchyId datatype:
Stored Procedure:
CREATE PROCEDURE GetSomethingByNodeId
#startingRoot hierarchyid, -- you don't need to use nvarchar here. String which will come from the application will be converted to hierarchyId implicitly
#return nvarchar(500) OUTPUT
AS
BEGIN
SELECT #return = #startingRoot.GetAncestor(1).ToString();
END
In an application you are adding a partial class for your EF data context with the SP call using plain old ADO.NET. Probably you will write this other way or use Dapper instead, but the main idea here is passing parameter as string to SQL Server, and it will convert to the HierarchyId implicitly:
public partial class TestEntities
{
public string GetSomethingByNodeId(string startingRoot)
{
using (var connection = new SqlConnection(this.Database.Connection.ConnectionString))
{
var command = new SqlCommand("GetSomethingByNodeId", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#startingRoot", startingRoot);
var outParameter = new SqlParameter("#return", SqlDbType.NVarChar, 500);
outParameter.Direction = ParameterDirection.Output;
command.Parameters.Add(outParameter);
connection.Open();
command.ExecuteNonQuery();
return outParameter.Value.ToString();
}
}
}
Then call this method as any other stored procedure using your EF context:
using (var context = new TestEntities())
{
var s = context.GetSomethingByNodeId("/1/1.3/");
}
UPD: here is how the extension method for legacy HierarchyId procedure call will look like with Dapper (as for me it looks much better than plain ADO.NET):
public string GetSomethingByNodeId(string startingRoot)
{
using (var connection = new SqlConnection(this.Database.Connection.ConnectionString))
{
var parameters = new DynamicParameters();
parameters.Add("startingRoot", startingRoot);
parameters.Add("return", null, DbType.String, ParameterDirection.Output, 500);
connection.Open();
connection.Execute("GetSomethingByNodeId", parameters, commandType: CommandType.StoredProcedure);
return parameters.Get<string>("return");
}
}

Reader.IsConsumed is false but object was disposed

I'm using QueryMultiple which returns a GridReader.
Since I don't know how much data I'm gonna read, I'm looping over the reader with the stop condition of IsConsumed:
using (var reader = conn.QueryMultiple(mySql)) {
while(!reader.IsConsumed) {
reader.Read<...>
}
}
However, I'm always getting an ObjectDisposedException on the last read. The value of IsConsumed is still false.
I've tried to pass DynamicParameters to the query with the intention of getting a callback (which seems to be useful via IParameterCallbacks), but I couldn't patch it together.
I would really rather not to have such an expected exception in the code. Thanks for any help.
I'm using SQL Server, my provider is System.Data.SqlClient in .NET 4.5, Dapper version 1.40.0.0
A failing test for example:
[TestMethod]
public void QueryMultipleWithCursor()
{
const string sql = #"
DECLARE #CurrentDate DATE
DECLARE DatesCursor CURSOR LOCAL FOR
SELECT DISTINCT DataDate FROM Data_Table ORDER BY DataDate
OPEN DatesCursor
FETCH NEXT FROM DatesCursor INTO #CurrentDate
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT DISTINCT
DataDate AS Date1,
DataDate AS Date2
FROM Data_Table
WHERE DataDate=#CurrentDate
FETCH NEXT FROM DatesCursor INTO #CurrentDate
END
CLOSE DatesCursor
DEALLOCATE DatesCursor";
using (var conn = _database.GetConnection())
{
var reader = conn.QueryMultiple(sql);
while (!reader.IsConsumed)
{
reader.Read<DateTime, DateTime, DateTime>(
(date1, date2) => date1,
splitOn: "Date2").ToList();
}
}
}
I'm getting a NullReferenceException with the following stack:
at Dapper.SqlMapper.GridReader.NextResult() in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 4440
at Dapper.SqlMapper.GridReader.<MultiReadInternal>d__9`8.System.IDisposable.Dispose()
at Dapper.SqlMapper.GridReader.<MultiReadInternal>d__9`8.MoveNext() in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 4309
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Dapper.SqlMapper.GridReader.Read[TFirst,TSecond,TReturn](Func`3 func, String splitOn, Boolean buffered) in D:\Dev\dapper-dot-net\Dapper NET40\SqlMapper.cs:line 4330
at Project.MyTests.QueryMultipleWithCursor() in C:\Project\MyTests.cs:line 171
Result Message:
Test method Project.MyTests.QueryMultipleWithCursor threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
I've pushed the following, which passes on SQL Server / SqlConnection; so it can work:
[Fact]
public void SO35554284_QueryMultipleUntilConsumed()
{
using (var reader = connection.QueryMultiple(
"select 1 as Id; select 2 as Id; select 3 as Id;"))
{
List<HazNameId> items = new List<HazNameId>();
while (!reader.IsConsumed)
{
items.AddRange(reader.Read<HazNameId>());
}
items.Count.IsEqualTo(3);
items[0].Id.IsEqualTo(1);
items[1].Id.IsEqualTo(2);
items[2].Id.IsEqualTo(3);
}
}
I wonder if the issue here is a problem with a specific ADO.NET provider. You might want to specify exactly:
what backend RDBMS / etc you are using (SQL Server? Oracle? Postgresql? ...?)
what ADO.NET provider you are using
what runtime (.NET what.what? core-clr?) / OS you are using
what exact library version you are using (the above is against the source code, which is most similar to 1.50.0-beta8)
I am running into the same issue with Dapper and I am using the version 1.42.0 and SQL Server 2012 as the back end. Upon debugging I found that this issue is happening only when we try to create multiple objects using the Dapper's splitOn option on the last result set.
I have submitted a new issue on GitHub
https://github.com/StackExchange/dapper-dot-net/issues/469
Well it seems to be an issue with Dapper implementation, for the mean time I'm using both Dapper and SqlDataReader, which is more reliable:
public static SqlMapper.GridReader QueryMultipleStoredProcedure(this IDbConnection dbConnection, string spName, object parameters, out SqlDataReader sqlDataReader)
{
var gridReader = dbConnection.QueryMultiple(spName, new DynamicParameters(parameters), commandType: CommandType.StoredProcedure);
sqlDataReader = typeof (SqlMapper.GridReader).GetInstanceField<SqlDataReader>(gridReader, "reader");
return gridReader;
}
private static T GetInstanceField<T>(this Type type, object instance, string fieldName)
{
var bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var field = type.GetField(fieldName, bindFlags);
return (T) field?.GetValue(instance);
}
And then I can use sqlDataReader.HasRows

Dapper calls sp_executesql when I have parameters, is there a way around that?

When I call
connection.Execute(sql);
Dapper executes and everything is fine. When I call
connection.Execute(sql, new { UserId = _userId });
it executes with sp_executesql.
The issue is when it uses sp_executesql it's in its own scope. If it creates a temporary table, it's not accessible to subsequent queries that use the same connection. I could get around it by using global temporary tables, but I don't want to risk having two processes interfere with each other.
Does anybody know a way around that?
Update: I have the same problem when I use SqlCommand objects without Dapper. I wrote a unit test that illustrates the problem I'm having. WorksWithParameters fails with System.Data.SqlClient.SqlException : Invalid object name '#TEMP_OBJECTLIST'.
[TestFixture]
public class DapperTest
{
private const string TestObjectType = "S";
private const string ConnectionString = "XXXXXXXXX";
private static void CreateTempTableWithoutParameters(SqlConnection connection)
{
const string sql = "SELECT TOP 10 * INTO #TEMP_OBJECTLIST FROM sys.objects WHERE TYPE = 'S'";
connection.Execute(sql);
}
private static void UseTempTableWithoutParameters(SqlConnection connection)
{
const int expectedCount = 10;
const string sql = "SELECT COUNT(*) FROM #TEMP_OBJECTLIST WHERE TYPE = 'S'";
var count = connection.Query<int>(sql).First();
Assert.AreEqual(expectedCount, count);
}
private static void CreateTempTableWithParameters(SqlConnection connection)
{
const string sql = "SELECT TOP 10 * INTO #TEMP_OBJECTLIST FROM sys.objects WHERE TYPE = #OBJECT_TYPE";
connection.Execute(sql, new {OBJECT_TYPE = TestObjectType});
}
private static void UseTempTableWithParameters(SqlConnection connection)
{
const int expectedCount = 10;
const string sql = "SELECT COUNT(*) FROM #TEMP_OBJECTLIST WHERE TYPE = #OBJECT_TYPE";
var param = new {OBJECT_TYPE = TestObjectType};
var count = connection.Query<int>(sql, param).First();
Assert.AreEqual(expectedCount, count);
}
[Test]
public void WorksWithParameters()
{
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
CreateTempTableWithParameters(connection);
UseTempTableWithParameters(connection);
}
}
[Test]
public void WorksWithoutParameters()
{
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
CreateTempTableWithoutParameters(connection);
UseTempTableWithoutParameters(connection);
}
}
}
One way around the temp table scope problem is to create the temp table with one dummy column in the outer scope, then use alter table statements to add all the desired columns and use it.
Additionally, How to share data between procedures by Erland Sommarskog may be useful to you or another person looking for different options for sharing data.
I ran into the same problem with Dapper, but it's not Dapper's fault. sp_executesql is called by ADO.NET and this switches the "scope" so temp tables become invisible.
As a workaround:
//no parameters, so it runs without sp_executesql
conn.Execute("CREATE TABLE #temp BLAHBLAH");
//do your thing
conn.Execute("INSERT INTO #temp BLAHBLAH", parameters);
//cleanup (no parameters again)
conn.Execute("DROP TABLE #temp");

Pass Dictionary<string,int> to Stored Procedure T-SQL

I have mvc application. In action I have Dictionary<string,int>. The Key is ID and Value is sortOrderNumber. I want to create stored procedure that will be get key(id) find this record in database and save orderNumber column by value from Dictionary. I want to call stored procedure once time and pass data to it, instead of calling many times for updating data.
Have you any ideas?
Thanks!
The accepted answer of using a TVP is generally correct, but needs some clarification based on the amount of data being passed in. Using a DataTable is fine (not to mention quick and easy) for smaller sets of data, but for larger sets it does not scale given that it duplicates the dataset by placing it in the DataTable simply for the means of passing it to SQL Server. So, for larger sets of data there is an option to stream the contents of any custom collection. The only real requirement is that you need to define the structure in terms of SqlDb types and iterate through the collection, both of which are fairly trivial steps.
A simplistic overview of the minimal structure is shown below, which is an adaptation of the answer I posted on How can I insert 10 million records in the shortest time possible?, which deals with importing data from a file and is hence slightly different as the data is not currently in memory. As you can see from the code below, this setup is not overly complicated yet highly flexible as well as efficient and scalable.
SQL object # 1: Define the structure
-- First: You need a User-Defined Table Type
CREATE TYPE dbo.IDsAndOrderNumbers AS TABLE
(
ID NVARCHAR(4000) NOT NULL,
SortOrderNumber INT NOT NULL
);
GO
SQL object # 2: Use the structure
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
#ImportTable dbo.IDsAndOrderNumbers READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE SchemaName.TableName;
INSERT INTO SchemaName.TableName (ID, SortOrderNumber)
SELECT tmp.ID,
tmp.SortOrderNumber
FROM #ImportTable tmp;
-- OR --
some other T-SQL
-- optional return data
SELECT #NumUpdates AS [RowsUpdated],
#NumInserts AS [RowsInserted];
GO
C# code, Part 1: Define the iterator/sender
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> SendRows(Dictionary<string,int> RowData)
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("ID", SqlDbType.NVarChar, 4000),
new SqlMetaData("SortOrderNumber", SqlDbType.Int)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
// read a row, send a row
foreach (KeyValuePair<string,int> _CurrentRow in RowData)
{
// You shouldn't need to call "_DataRecord = new SqlDataRecord" as
// SQL Server already received the row when "yield return" was called.
// Unlike BCP and BULK INSERT, you have the option here to create an
// object, do manipulation(s) / validation(s) on the object, then pass
// the object to the DB or discard via "continue" if invalid.
_DataRecord.SetString(0, _CurrentRow.ID);
_DataRecord.SetInt32(1, _CurrentRow.sortOrderNumber);
yield return _DataRecord;
}
}
C# code, Part 2: Use the iterator/sender
public static void LoadData(Dictionary<string,int> MyCollection)
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
SqlDataReader _Reader = null; // only needed if getting data back from proc call
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "#ImportTable";
// _TVParam.TypeName = "IDsAndOrderNumbers"; //optional for CommandType.StoredProcedure
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = SendRows(MyCollection); // method return value is streamed data
_Command.Parameters.Add(_TVParam);
_Command.CommandType = CommandType.StoredProcedure;
try
{
_Connection.Open();
// Either send the data and move on with life:
_Command.ExecuteNonQuery();
// OR, to get data back from a SELECT or OUTPUT clause:
SqlDataReader _Reader = _Command.ExecuteReader();
{
Do something with _Reader: If using INSERT or MERGE in the Stored Proc, use an
OUTPUT clause to return INSERTED.[RowNum], INSERTED.[ID] (where [RowNum] is an
IDENTITY), then fill a new Dictionary<string, int>(ID, RowNumber) from
_Reader.GetString(0) and _Reader.GetInt32(1). Return that instead of void.
}
}
finally
{
_Reader.Dispose(); // optional; needed if getting data back from proc call
_Command.Dispose();
_Connection.Dispose();
}
}
Using Table Valued parameters is really not that complex.
given this SQL:
CREATE TYPE MyTableType as TABLE (ID nvarchar(25),OrderNumber int)
CREATE PROCEDURE MyTableProc (#myTable MyTableType READONLY)
AS
BEGIN
SELECT * from #myTable
END
this will show how relatively easy it is, it just selects out the values you sent in for demo purposes. I am sure you can easily abstract this away in your case.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace TVPSample
{
class Program
{
static void Main(string[] args)
{
//setup some data
var dict = new Dictionary<string, int>();
for (int x = 0; x < 10; x++)
{
dict.Add(x.ToString(),x+100);
}
//convert to DataTable
var dt = ConvertToDataTable(dict);
using (SqlConnection conn = new SqlConnection("[Your Connection String here]"))
{
conn.Open();
using (SqlCommand comm = new SqlCommand("MyTableProc",conn))
{
comm.CommandType=CommandType.StoredProcedure;
var param = comm.Parameters.AddWithValue("myTable", dt);
//this is the most important part:
param.SqlDbType = SqlDbType.Structured;
var reader = comm.ExecuteReader(); //or NonQuery, etc.
while (reader.Read())
{
Console.WriteLine("{0} {1}", reader["ID"], reader["OrderNumber"]);
}
}
}
}
//I am sure there is a more elegant way of doing this.
private static DataTable ConvertToDataTable(Dictionary<string, int> dict)
{
var dt = new DataTable();
dt.Columns.Add("ID",typeof(string));
dt.Columns.Add("OrderNumber", typeof(Int32));
foreach (var pair in dict)
{
var row = dt.NewRow();
row["ID"] = pair.Key;
row["OrderNumber"] = pair.Value;
dt.Rows.Add(row);
}
return dt;
}
}
}
Produces
0 100
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109
Stored procedures do not support arrays as inputs. Googling gives a couple of hacks using XML or comma separated strings, but those are hacks.
A more SQLish way to do this is to create a temporary table (named e.g. #Orders) and insert all the data into that one. Then you can call the sp, using the same open Sql Connection and insie the SP use the #Orders table to read the values.
Another solution is to use Table-Valued Parameters but that requires some more SQL to setup so I think it is probably easier to use the temp table approach.

Change the ANSI_NULLS setting for all Stored Procedures in the Database

We have some problems with the ANSI_NULLS setting and computed columns and we have a ton of stored procedures that have
SET ANSI_NULLS OFF
We want to change them all to
SET ANSI_NULLS ON
Is there an easy way to do that or must I extract all the SPs to a script, change it and run it again to drop and recreate all the SPa
You must script all the procedures, and re-create them with ANSI_NULLS on.
If i had a lot to do, i might add a function to my client app.
PseudoCode:
procedure FixAllStoredProcedureAnsiNullness(connection)
{
Strings spNames = GetStoredProcedureNames(connection);
foreach spName in spNames
{
String sql = GetStoredProcedureSQL(connection, spName);
//turn on option for remainder of connection
connection.ExecuteNoRecords("SET ANSI_NULLS ON");
BeginTransaction(connection);
try
connection.ExecuteNoRecords("DROP PROCEDURE "+spName);
connection.ExecuteNoRecords(sql);
CommitTranasction(connection);
except
RollbackTransaction(connection);
raise;
end;
}
}
i had code on how to get the SQL of a stored procedure programatically on SQL Server: How to generate object scripts without DMO/SMO?
But normally i'll just use Enterprise Manager, starting at the top of the stored procedure list:
Return
Ctrl+Home
Ctrl+V
Click OK
Down
Goto 1
Where my clipboard contains:
SET ANSI_NULLS ON
GO
If you're unfortunate enough to be stuck with SSMS, then you're SOL with that POS, IIRC. TWSS.
The solution that we use was the posted by Ian and now we have an automated procedure to solve the problem.
Here is the final code that we use to recreate all the SPs from the database:
public static class AnsiNullsManager
{
public static void ReCreateAllStoredProcedures(SqlConnection connection, bool ansiNullsOn)
{
var sql =
#"select object_name(sys.all_sql_modules.object_id) as Name, definition as Code
from sys.all_sql_modules inner join sys.objects ON
sys.all_sql_modules.object_id = sys.objects.object_id
where objectproperty(sys.all_sql_modules.object_id, 'IsProcedure') = 1 AND is_ms_shipped = 0 and uses_ansi_nulls = " +
(ansiNullsOn ? "0" : "1") +
"ORDER BY Name ";
if (connection.State == ConnectionState.Closed)
connection.Open();
var sps = new List<SpObject>();
var cmd = connection.CreateCommand();
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
sps.Add(new SpObject(reader.GetString(0), reader.GetString(1)));
}
}
var cmdSetAnsiNulls = connection.CreateCommand();
cmdSetAnsiNulls.CommandText = "SET ANSI_NULLS " + (ansiNullsOn ? "ON" : "OFF") + ";";
cmdSetAnsiNulls.ExecuteNonQuery();
foreach (var sp in sps)
{
var trans = connection.BeginTransaction();
try
{
var cmdDrop = connection.CreateCommand();
cmdDrop.CommandText = "DROP PROCEDURE " + sp.Name;
cmdDrop.Transaction = trans;
cmdDrop.ExecuteNonQuery();
var cmdReCreate = connection.CreateCommand();
cmdReCreate.CommandText = sp.Code;
cmdReCreate.Transaction = trans;
cmdReCreate.ExecuteNonQuery();
trans.Commit();
}
catch (Exception)
{
trans.Rollback();
throw;
}
}
}
private class SpObject
{
public SpObject(string name, string code)
{
Name = name;
Code = code;
}
public string Name { get; private set; }
public string Code { get; private set; }
}
}
Just wanted to throw a warning in there. I can't imagine why you had ansi_nulls set off for ALL your SPs but if any of them were counting on comparisons to NULL in any way (and there can be a lot of different ways that could happen) your results will different when you change that setting. I recommend some rigorous regression testing in a safe environment.
By far the easiest way is to script the s'procs, run find and replace command, then run the proc definitions again.

Resources