Add statements to existing StoredProcedure in mssql2008 - c#

what is the best way to alter a stored procedure from C#(2010) Application code itself at installation time of an add-on.
That means there are existing SP's and different add-on's... So that I have to check the SP. is there a easy way of doing this? or do I have to read this with sp_helptext sp.... add or Change My sentence with reader/writer/string... and execute this?
Regards Oliver

This can be achieved in two ways.
Run the attached query in "Reports to File" option selected in SSMS or press ctrl+Shift+F. In the output, replace an existing column (I assume a column exists in all the Procedures and you wanted to add a new column to the Procedure) with "that column name" + "new column name". For example, if you wanted to add "coly" to the existing procedure and "colx" exists in all the procedure then do "Find and Replace" "colx" with "colx, coly". You need to make sure "colx" is not used anywhere in the condition. You should pick the column which exists only in the select clause.
Pass those two values as parameters to this script and the script does the job and produces the output.
DECLARE #to_Be_Replaced_Value nvarchar(130) = ''
DECLARE #replacing_Value nvarchar(130) = ''
SELECT REPLACE(M.DEFINITION,#to_Be_Replaced_Value,#replacing_Value) DEFINITION
FROM SYS.sql_modules M
INNER JOIN SYS.objects O ON M.object_id = O.object_id
WHERE O.type = 'P'
This may not work for you if my assumption is not met.

so I try to run a different way... but I do have some issues.
Server srv = new Server(#"server\instance");
ServerConnection conContext = new ServerConnection();
conContext = srv.ConnectionContext;
conContext.LoginSecure = false;
conContext.Login = "user";
conContext.Password = "passw";
Server srv2 = new Server(conContext);
Database db;
db = srv2.Databases["mydb"];
StoredProcedure sp = db.StoredProcedures["spMySP"];
sp.TextMode = false;
sp.AnsiNullsStatus = true;
sp.QuotedIdentifierStatus = true;
sp.TextBody = SPBody;
try
{
sp.Alter();
}
catch (SqlServerManagementException ex)
{
}
The String has something like 6000 characters and I only want to change the Body text not the keys itself. The solution postet in the first answer won't work according to the things that I have to many exeptions.. to Declaration etc...
Here I don't want to update the header of the SP, just right after as Begin... but from where I have to use the Body? without the last End?
ALTER proc [dbo].[spMySP]
#Object nvarchar(20), -- comments
#Type nchar(1), -- comments
#num_of_cols_in_key int,
#listCols nvarchar(255),
#ListColdel nvarchar(255)
AS
begin
-- Return values
.... (some Values here)...
---#mybegin#---
....
---#myend#---
the rest
end
I don't know from where I have to pick this.

OK it's done.
I used the sp.bodytext for scratching... find my part that has to be updated and store this back to sp.bodytext... starting from begin up to the last end and this will work as expected.
thanks for help

Related

"There is already an open DataReader associated with this command which must be closed first." - but where?

This is literally my entire transaction:
adc.BeginTransaction();
// create/edit quote assocated with quote request
adc.CreateOrEditQuote(model.QuoteRequestID, model.Amount);
adc.CommitTransaction();
I don't see anywhere that I have an open DataReader lying around that could be interfering with anything else - there's only one database call inside the transaction! I do have another database call in another class which should be finished by the time this is called:
var model = new ViewQuoteRequestModel();
var qr = DataContextProvider.REDACTED.ListQuoteRequests().SingleOrDefault(q => q.QuoteRequestID == id);
if (null != qr)
{
qr.CopyTo<IViewQuoteRequestModel>(model);
}
return model;
Calling SingleOrDefault on my data should be resolving it and not leaving it lazy-loaded, right? So that shouldn't be a problem anyway...
edit: if I comment out the line where I call CreateOrEditQuote then there is no error, but of course the data is not saved. So in case it helps, here is my stored procedure:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[REDACTED].[CreateOrEditQuote]') AND type in (N'P', N'PC'))
DROP PROCEDURE [REDACTED].[CreateOrEditQuote]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [REDACTED].[CreateOrEditQuote]
(
#QuoteRequestID int,
#Amount money
)
as
begin
declare #qid int
select #qid = QuoteID from QuoteRequest where ID = #QuoteRequestID
if #qid is null
begin
-- quote does not exist, create it
insert into quote (Amount) values (#Amount)
select #qid = ##IDENTITY
end
else
begin
-- quote exists, edit it
update quote set Amount = #Amount where ID = #qid
end
update QuoteRequest set QuoteID = #qid where ID = #QuoteRequestID
select #qid as QuoteID
end
GO
Weird thing is, if I run the stored procedure manually though SQL Server Management Studio, it runs, but if I do it through my web app (calling the code I shared at the top of my post) it crashes...
Because my stored procedure was returning data, I had to retrieve that data:
var qid = adc.CreateOrEditQuote(model.QuoteRequestID, model.Amount).SingleOrDefault()?.QuoteID;
rather than simply calling the CreateOrEditQuote method without assigning the result to a variable.

Pass a list of integers from C# into Oracle stored procedure

I have a oracle stored procedure which updates a table with the following statement.
update boxes
set location = 'some value'
where boxid = passed value
I have a page where the user selects 100+ boxes and updates them with a new location value. Currently, I have to call the stored procedure 100+ times to update each box(by passing a boxid each time).
I want to know how I can pass a list of boxids from C# into the stored procedure so that I have to call the stored procedure just one time.
I am hoping to use a where in(boxids) kind of where clause in the update statement.
Please let know how can I achieve this. Thanks in advance!
Oracle allows you to pass arrays of values as parameters. Borrowing from this SO question and this one you can define an INT_ARRAY type like this:
create or replace type CHAR_ARRAY as table of INTEGER;
Then define your stored procedure as:
CREATE OR REPLACE PROCEDURE product_search(
...
myIds IN CHAR_ARRAY,
...)
AS
SELECT ...
...
WHERE SomeIdField IN (Select column_value FROM TABLE(myIds))
...
You can then pass the list of values by setting the OracleParameter.CollectionType property like this:
OracleParameter param = new OracleParameter();
param.OracleDbType = OracleDbType.Int32;
param.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
I'd create a new procedure, designed to handle a list of values. An efficient approach would be to load the multiple values into a global temp table, using a bulk insert, and then have the procedure update using a join to the GTT.
A notional example would look like this:
OracleTransaction trans = conn.BeginTransaction(IsolationLevel.RepeatableRead);
OracleCommand cmd = new OracleCommand(insertSql, conn, trans);
cmd.Parameters.Add(new OracleParameter("BOX_ID", OracleDbType.Number));
cmd.Parameters[0].Value = listOfBoxIds; // int[] listOfBoxIds;
cmd.ExecuteArray();
OracleCommand cmd2 = new OracleCommand(storedProc, conn, trans);
cmd2.CommandType = CommandType.StoredProcedure;
cmd2.ExecuteNonQuery();
trans.Commit();
Your PL/SQL block may look like this one:
CREATE OR REPLACE PACKAGE YOUR_PACKAGE AS
TYPE TArrayOfNumber IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
PROCEDURE Update_Boxes(boxes IN TArrayOfNumber );
END YOUR_PACKAGE;
/
CREATE OR REPLACE PACKAGE BODY YOUR_PACKAGE AS
PROCEDURE Update_Boxes(boxes IN TArrayOfNumber) is
BEGIN
FORALL i IN INDICES OF boxes
update boxes
set location = boxes(i)
where boxid = ...;
END Update_Boxes;
END YOUR_PACKAGE;
The C# code you get already in answer from Panagiotis Kanavos
I understand your concern - the round trips will be taxing.
Unfortunately I don't have anything to test, but you can try
Oracle bulk updates using ODP.NET
or
-- edit1: go with Panagiotis Kanavos's answer if your provider supports it, else check below --
-- edit12 as highlighted by Wernfried, long is deprecated. Another thing consider is max length varchar2: it doesn't scale on a very big set. Use the one below as the last resort. --
changing your stored procedure to accept string
implement string_2_list in asktom.oracle.com.
create or replace type myTableType as table of varchar2 (255);
create or replace function in_list( p_string in varchar2 ) return myTableType
as
l_string long default p_string || ',';
l_data myTableType := myTableType();
n number;
begin
loop
exit when l_string is null;
n := instr( l_string, ',' );
l_data.extend;
l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
l_string := substr( l_string, n+1 );
end loop;
return l_data;
end;
Above is early variant and splice to varchar2, but if you read more (including other threads) at that site
you'll find more advanced variants (optimized, better exception handling)

Call procedure on another database with user defined table type

I have a connection to a database with right to another. I want to call a procedure on the other database which has a user table data type parameter. But the user table data type isn't found whatever I try.
I tried using database name in front of [dbo].[myType] but it's not a valid syntax.
I tried creating the same type in the current database
I tried creating the same type in the model database
I tried appending "USE MyOtherDatabase" at the top of my SqlCommand.Text
Everything failed (I'm really abashed the "USE ..." approach failed).
How can I do that?
Sample of code:
// While connected to MyOtherDatabase
CREATE TYPE dbo.typeClubMembersVersion AS TABLE (
ID INT
, UNIQUE(ID)
, [version] INT
)
GO
CREATE PROCEDURE dbo.spCheckCMembersMods (
#pVersions AS dbo.typeClubMembersVersion READONLY
, #pWhoID AS BIGINT
)
AS
BEGIN
[...]
END
SqlCommand com = new SqlConnection(functions.ConnectionString).CreateCommand();
com.CommandText = #"
// While connected to CurrentDatabase
USE MyOtherDatabase
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
com.Parameters.Add("#Title", SqlDbType.NVarChar, 20).Value = this.Title;
com.Parameters.Add("#IdMember", SqlDbType.BigInt).Value = this.Id;
com.Parameters.Add("#whoID", SqlDbType.BigInt).Value = (object)whoID ?? DBNull.Value;
com.Connection.Open();
try
{
com.ExecuteNonQuery();
}
catch (Exception exe)
{
throw exe;
}
finally
{
com.Connection.Close();
}
First, what you are calling "Schemas" are actually "Databases" in SQL Server. The "dbo." in your object names is a "Schema" in SQL Server. The "USE.." command only works on Databases.
Secondly, you cannot reference or use Types from another database, it has to be defined in the same database(s) that it is used in. Types can be in other SQL Server schemas, but not in other Databases, which is what you are actually trying to do here.
OK, as you noted, your Type is defined in [myOtherDatbase] so why doesn't it work? Probably because the USE.. and SQL command strings do not work the way you might think. Whenever you pass a string like this to SQL Server and try to execute it:
com.CommandText = #"
// While connected to CurrentDatabase
USE MyOtherDatabase
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
SQL Server will first compile the entire string and then try to execute it. This means that all of the commands are compiled first before any of them are executed. And that means that your DECLARE #tbl and UPDATE.. commands are compiled before the USE command is executed. So when they are compiled you are still in the previous database where the Type has not been defined. This is what leads to your syntax errors (which are coming from the compiler, not from their execution).
There are three possible solutions:
Define the Type in currentDatabase also (I am pretty sure that this works, but not 100%).
Reconnect with a connection string that specifies "Initial Catalog=MyOtherDatabase".
Re-execute everything after your USE command with Dynamic SQL.
Of these I would recommend #2.
Silly me, I just realized that there is another option:
First execute just the USE command by itself,
then, execute the rest of the SQL commands on the same connection.
Of course this will leave you in [MyOtherDatabase], so you may want to end this by executing another USE command back to your original database.
It's been such a very long time since I had to use SqlConnection.ChangeDatabase I fergot about it. Until now I've always been able to use "fully named objects" to make my databases interract with each other.
Since I'm currently stuck I'll use it but I hope somebody tells me a way that don't force me to let go the current database connection.
SqlCommand com = new SqlConnection(functions.ConnectionString).CreateCommand();
com.CommandText = #"
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
com.Parameters.Add("#Title", SqlDbType.NVarChar, 20).Value = this.Title;
com.Parameters.Add("#IdMember", SqlDbType.BigInt).Value = this.Id;
com.Parameters.Add("#whoID", SqlDbType.BigInt).Value = (object)whoID ?? DBNull.Value;
com.Connection.Open();
try
{
com.Connection.ChangeDatabase("MyOtherDatabase");
com.ExecuteNonQuery();
}
catch (Exception exe)
{
throw exe;
}
finally
{
com.Connection.Close();
}

Linq to SQL, Stored Procedure with different return types based on an If/Else

I have an existing Stored Procedure which I am trying to now call with LINQ to SQL, here is the stored procedure:
ALTER procedure [dbo].[sp_SELECT_Security_ALL] (
#UID Varchar(15)
)
as
DECLARE #A_ID int
If ISNULL(#UID,'') = ''
SELECT DISTINCT
App_ID,
App_Name,
App_Description,
DB,
DBNameApp_ID,
For_One_EVA_List_Ind
From v_Security_ALL
ELSE
BEGIN
Select #A_ID = (Select Assignee_ID From NEO.dbo.v_Assignees Where USER_ID = #UID and Inactive_Ind = 0)
SELECT DISTINCT
Security_User_ID,
Security_Company,
Security_MailCode,
Security_Last_Name,
Security_First_Name,
Security_User_Name,
Security_User_Info,
Security_User_CO_MC,
Security_Email_Addr,
Security_Phone,
Security_Security_Level,
Security_Security_Desc,
Security_Security_Comment,
Security_Security_Inactive_Ind,
App_ID,
App_Name,
App_Description,
DB,
DBNameApp_ID,
For_One_EVA_List_Ind,
#A_ID as Assignee_ID
From v_Security_ALL
Where Security_User_ID = #UID
END
My problem is that the intellsense only sees the first set of return values in the IF statement and I can not access anything from the "else" part of my stored procedure. so when I try to do this:
var apps = dataContext.sp_SELECT_Security_ALL(userId);
foreach (var app in apps)
{
string i = app.
}
On the app. part the only available values I have there is the results of the the first Select distinct above.
Is it possible to use LINQ with this type of stored procedure?
Scott Guthrie has covered this case in a blog post. Scroll down to "Handling Multiple Result Shapes from SPROCs."
The problem isn't with Intellisense. dataContext.sp_SELECT_Security_ALL() is returning a fixed data type. You may be hiding that behind a "var", but it's nevertheless a concrete type with a fixed number of properties. There is still C# remember, and a function can only return one type of object. Look in your dataContext.designer.cs file to see how it's actually defined.
The quick and dirty way to fix this is to coerce every returning statement to return the same thing:
IF #theSkyIsBlue
SELECT CustomerNumber, CustomerName, null as OrderNumber, null as OrderName
FROM Customers
ELSE
SELECT null as CustomerNumber, null as CustomerName, OrderNumber, OrderName
FROM Orders
You may have to watch/(manually change) the nullability of properties in the mapped type, but this will get you where you're going.

String list in SqlCommand through Parameters in C#

Working with a SqlCommand in C# I've created a query that contains a IN (list...) part in the where clause. Instead of looping through my string list generating the list I need for the query (dangerous if you think in sqlInjection). I thought that I could create a parameter like:
SELECT blahblahblah WHERE blahblahblah IN #LISTOFWORDS
Then in the code I try to add a parameter like this:
DataTable dt = new DataTable();
dt.Columns.Add("word", typeof(string));
foreach (String word in listOfWords)
{
dt.Rows.Add(word);
}
comm.Parameters.Add("LISTOFWORDS", System.Data.SqlDbType.Structured).Value = dt;
But this doesn't work.
Questions:
Am I trying something impossible?
Did I took the wrong approach?
Do I have mistakes in this approach?
Thanks for your time :)
What you are trying to do is possible but not using your current approach. This is a very common problem with all possible solutions prior to SQL Server 2008 having trade offs related to performance, security and memory usage.
This link shows some approaches for SQL Server 2000/2005
SQL Server 2008 supports passing a table value parameter.
I hope this helps.
You want to think about where that list comes from. Generally that information is in the database somewhere. For example, instead of this:
SELECT * FROM [Table] WHERE ID IN (1,2,3)
You could use a subquery like this:
SELECT * FROM [Table] WHERE ID IN ( SELECT TableID FROM [OtherTable] WHERE OtherTableID= #OtherTableID )
If I understand right, you're trying to pass a list as a SQL parameter.
Some folks have attempted this before with limited success:
Passing Arrays to Stored Procedures
Arrays and Lists in SQL 2005
Passing Array of Values to SQL Server without String Manipulation
Using MS SQL 2005's XML capabilities to pass a list of values to a command
Am I trying something impossible?
No, it isn't impossible.
Did I took the wrong approach?
Your approach is not working (at least in .net 2)
Do I have mistakes in this approach?
I would try "Joel Coehoorn" solution (2nd answers) if it is possible.
Otherwise, another option is to send a "string" parameter with all values delimited by an separator. Write a dynamic query (build it based on values from string) and execute it using "exec".
Another solution will be o build the query directly from code. Somthing like this:
StringBuilder sb = new StringBuilder();
for (int i=0; i< listOfWords.Count; i++)
{
sb.AppendFormat("p{0},",i);
comm.Parameters.AddWithValue("p"+i.ToString(), listOfWords[i]);
}
comm.CommandText = string.Format(""SELECT blahblahblah WHERE blahblahblah IN ({0})",
sb.ToString().TrimEnd(','));
The command should look like:
SELECT blah WHERE blah IN (p0,p1,p2,p3...)...p0='aaa',p1='bbb'
In MsSql2005, "IN" is working only with 256 values.
I would recommend setting the parameter as a comma delimited string of values and use a Split function in SQL to turn that into a single column table of values and then you can use the IN feature.
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=50648 - Split Functions
If you want to pass the list as a string in a parameter, you could just build the query dynamically.
DECLARE #query varchar(500)
SET #query = 'SELECT blah blah WHERE blahblah in (' + #list + ')'
EXECUTE(#query)
I used to have the same problem, I think there is now way to do this directly over the ADO.NET API.
You might consider inserting the words into a temptable (plus a queryid or something) and then refering to that temptable from the query. Or dynamically creating the query string and avoid sql injection by other measures (e.g. regex checks).
This is an old question but I've come up with an elegant solution for this that I love to reuse and I think everyone else will find it useful.
First of all you need to create a FUNCTION in SqlServer that takes a delimited input and returns a table with the items split into records.
Here is the following code for this:
ALTER FUNCTION [dbo].[Split]
(
#RowData nvarchar(max),
#SplitOn nvarchar(5) = ','
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
You can now do something like this:
Select Id, Data from dbo.Split('123,234,345,456',',')
And fear not, this can't be susceptible to Sql injection attacks.
Next write a stored procedure that takes your comma delimited data and then you can write a sql statement that uses this Split function:
CREATE PROCEDURE [dbo].[findDuplicates]
#ids nvarchar(max)
as
begin
select ID
from SomeTable with (nolock)
where ID in (select Data from dbo.Split(#ids,','))
end
Now you can write a C# wrapper around it:
public void SomeFunction(List<int> ids)
{
var idsAsDelimitedString = string.Join(",", ids.Select(id => id.ToString()).ToArray());
// ... or however you make your connection
var con = GetConnection();
try
{
con.Open();
var cmd = new SqlCommand("findDuplicates", con);
cmd.Parameters.Add(new SqlParameter("#ids", idsAsDelimitedString));
var reader = cmd.ExecuteReader();
// .... do something here.
}
catch (Exception)
{
// catch an exception?
}
finally
{
con.Close();
}
}

Categories