Here is my stored procedure in SQL Server 2014:
CREATE PROCEDURE [dbo].[spSelectUserFromProfileUsers]
#TableName NVARCHAR(20),
#User NVARCHAR(20)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #query NVARCHAR(MAX);
SET #query = 'SELECT * FROM ' + QUOTENAME(#TableName) + ' WHERE Users =' + #User
EXECUTE sp_executesql #query
END
and here is my code in Visual Studio:
if (TableFunctions.doesTableExist(ComboBoxSelectedProfile.SelectedItem + "Users", dbConnectionString))
{
// DynamicSQL
using (SqlCommand command = new SqlCommand("spSelectUserFromProfileUsers", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#TableName", ComboBoxSelectedProfile.SelectedItem + "Users");
command.Parameters.AddWithValue("#User", TextBoxUserName.Text);
command.ExecuteNonQuery();
}
}
and I'm getting the error:
Invalid column name /Text that I entered the textbox/
I've been looking for a solution a long time and I can't find anything, I'll appreciate your help very much!
You need to make sure that your Users value is inside quotes. As it's a sql and there are only single quotes available, you have to do it in that strange way.
SET #query='SELECT * from '+QUOTENAME(#TableName)+' where Users='''+#User+''''
Your stored procedure that tries to execute a dynamic sql has a wrong syntax.
DECLARE #ParmDefinition NVARCHAR(2000);
SET #query='SELECT * from '+QUOTENAME(#TableName)+' where Users=#User';
SET #ParmDefinition = N'#User NVARCHAR(20)';
EXECUTE sp_executesql #query, #ParmDefinition, #User = #User;
From the documentation about sp_executesql you could see that a parameter should be used as is inside the dynamic sql text not trying to extract is value and appending the value. After that you should define the parameter for the sp_executesql (#ParmDefinition) and set its value (#User = #User) as second and third parameter to sp_executesql
query is not properly enclosed.
SET #query="SELECT * from '+QUOTENAME(#TableName)+' where Users='+#User+'"
Related
I am trying to execute a stored procedure I keep getting an error:
Procedure or function 'dynamictable' expects parameter '#tablename', which was not supplied
Can you please fix my code?
conn.Open();
SqlCommand search = new SqlCommand("dbo.dynamictable", conn);
search.CommandType = CommandType.StoredProcedure;
search.Parameters.Add(new SqlParameter("#tablename", table));
search.Parameters.Add(new SqlParameter("width", housewidth.ToString()));
Here is the query if it may help you help me:
CREATE PROCEDURE dynamictable
(#tablename nvarchar(100), #width nvarchar(50))
AS
BEGIN
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'SELECT * FROM [' + #tablename+ '] WHERE [Width] = '+ #width;
EXEC sp_executesql #SQL
END
If table is null then you will get that message.
You should really be specifying the type and size of the parameter:
search.Parameters.Add(new SqlParameter("#tablename", SqlDbType.NVarChar, 128).Value = table;
search.Parameters.Add(new SqlParameter("#width", SqlDbType.Int).value = housewidth;
Your current query is also at risk of SQL injection. It should be using proper parameterization and escaping even in the dynamic part:
CREATE procedure dynamictable
(#tablename nvarchar(128), #width int)
as
begin
declare #SQL nvarchar(max) =
N'SELECT * FROM ' + QUOTENAME(#tablename) + ' WHERE [Width] = #width;';
exec sp_executesql #SQL, N'#width int', #width = #width;
end
Remove the "#" from parameter name when passing it over
search.Parameters.Add(new SqlParameter("tablename", table));
'Procedure or function 'dynamictable' expects parameter '#tablename', which was not supplied.'
That error typically means the C# variable called table is null. Your stored procedure does not seem to have any relevant issues. (And you're mentioning '#' which is extra - so technically, tablename is not set yet.. (: )
For some reason my stored procedure is executed without any error from the code-behind in C# but it is not deleting anything at all that the stored procedure has written. I have all the correct parameters and everything. I ran the query from SQL Server with all the same parameters from the C# code and it works perfectly. I don't get why it works when I run from SQL Server but it doesn't work when I run it from my C# code in Visual Studio.
Here is my C# code that is passing the data through to the stored procedure.
string reportType = "PostClaim";
string GRNBRs = "925','926','927";
string PUNBRs = "100','100','100";
string beginningDates = "20120401";
string endDates= "20120430";
try
{
conn = new SqlConnection(ConnectionInfo);
conn.Open();
SqlDataAdapter da = new SqlDataAdapter("RemoveReport", conn);
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.Parameters.AddWithValue("#ReportType", reportType);
da.SelectCommand.Parameters.AddWithValue("#GRNBR", GRNBRs);
da.SelectCommand.Parameters.AddWithValue("#PUNBR", PUNBRs);
da.SelectCommand.Parameters.AddWithValue("#DATE1", beginningDates);
da.SelectCommand.Parameters.AddWithValue("#DATE2", endDates);
da.SelectCommand.CommandTimeout = 360;
}
catch (SqlException ex)
{
//something went wrong
throw ex;
}
finally
{
if (conn.State == ConnectionState.Open)
conn.Close();
}
Here is my stored procedure. It's executing with dynamic SQL text.
ALTER PROCEDURE [dbo].[RemoveReport] (
#ReportType NVARCHAR(20),
#GRNBR VARCHAR(4000),
#PUNBR VARCHAR(4000),
#DATE1 DATETIME,
#DATE2 DATETIME
)
AS
DECLARE #SQLTEXT VARCHAR(4000)
BEGIN
SET #SQLTEXT = 'DELETE FROM TestingTable
WHERE Report='''+#ReportType+''' AND
PUNBR IN ('''+#PUNBR+''') AND
[Group] IN ('''+#GRNBR+''') AND
StartedAt BETWEEN '''+CONVERT(VARCHAR(10),#DATE1,121)+'''
AND '''+CONVERT(VARCHAR(10),#DATE2,121)+''''
PRINT #SQLTEXT <---I'll print this out to show you what exactly it is executing.
EXECUTE (#SQLTEXT)
END
Here is what the PRINT #SQLTEXT is running:
DELETE FROM MonthlyReportSchedule
WHERE Report='PostClaim' AND
PUNBR IN ('100','100','100') AND
[Group] IN ('925','926','927') AND
StartedAt BETWEEN '2012-04-01' AND '2012-04-30'
When I actually go into SQL Server to run this query, it works perfectly. But why does it not work on when executed from the C# code. Any help?
Avoid concatenating parameters to your sql, use parameterised query,
Try this...
Just noticed that you have some comma delimited lists in params.....
ALTER PROCEDURE [dbo].[RemoveReport]
#ReportType NVARCHAR(20),
#GRNBR VARCHAR(4000),
#PUNBR VARCHAR(4000),
#DATE1 DATETIME,
#DATE2 DATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLTEXT NVARCHAR(MAX);
Declare #GRNBR_xml xml,#PUNBR_xml xml;
SET #GRNBR_xml = N'<root><r>' + replace(#GRNBR, ',','</r><r>') + '</r></root>';
SET #PUNBR_xml = N'<root><r>' + replace(#PUNBR, ',','</r><r>') + '</r></root>';
SET #SQLTEXT = N'DELETE FROM TestingTable
WHERE Report = #ReportType
AND PUNBR IN (select r.value(''.'',''varchar(max)'') as item
from #PUNBR_xml.nodes(''//root/r'') as records(r))
AND [Group] IN (select r.value(''.'',''varchar(max)'') as item
from #GRNBR_xml.nodes(''//root/r'') as records(r))
AND StartedAt BETWEEN #DATE1 AND #DATE2'
EXECUTE sp_executesql #SQLTEXT
,N'#ReportType NVARCHAR(20) , #GRNBR_xml xml,
#PUNBR_xml xml,#DATE1 DATETIME,#DATE2 DATETIME'
,#ReportType
,#GRNBR_xml
,#PUNBR_xml
,#DATE1
,#DATE2
END
Note
Make sure you pass the comma delimited list as 925,926,927 and not as '925','926','927'
Try adding this line in order to be executed
da.SelectCommand.ExecuteNonQuery();
This will execute a call to your stored procedure.
good luck
I created a SqlDependency so that an event would fire when the results of a particular query change.
// Create a command
SqlConnection conn = new SqlConnection(connectionString);
string query = "SELECT MyColumn FROM MyTable;";
SqlCommand cmd = new SqlCommand(query, conn)
cmd.CommandType = CommandType.Text;
// Register a dependency
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += DependencyOnChange;
When this code executes, a stored procedure is automatically created with a name like
SqlQueryNotificationStoredProcedure-82ae1b92-21c5-46ae-a2a1-511c4f849f76
This procedure is unencrypted, which violates requirements I have been given. I have two options:
Convince the customer that it doesn't matter that the auto generated procedure is unencrypted because it only does cleanup work and contains no real information (thanks to ScottChamberlain for pointing this out).
Find a way to encrypt the stored procedure generated by SqlDependency.
How can I accomplish option 2?
Contents of the stored procedure in question:
CREATE PROCEDURE [dbo].[SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b]
AS
BEGIN
BEGIN TRANSACTION;
RECEIVE TOP (0) conversation_handle
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (
SELECT COUNT(*)
FROM [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b]
WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer'
) > 0
BEGIN
IF (
(
SELECT COUNT(*)
FROM sys.services
WHERE NAME = 'SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b'
) > 0
)
DROP SERVICE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
IF (OBJECT_ID('SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b', 'SQ') IS NOT NULL)
DROP QUEUE [SqlQueryNotificationService-b124707b-23fc-4002-aac3-4d52a71c5d6b];
DROP PROCEDURE [SqlQueryNotificationStoredProcedure-b124707b-23fc-4002-aac3-4d52a71c5d6b];
END
COMMIT TRANSACTION;
END
GO
Create a DDL trigger that checks if a procedure with a name like "SqlQueryNotificationStoredProcedure-" is being created, and if so, immediately alter it WITH ENCRYPTION instead:
CREATE TRIGGER [TR_EncryptQueryNotificationProcedures]
ON DATABASE
AFTER CREATE_PROCEDURE, ALTER_PROCEDURE
AS
BEGIN
SET ARITHABORT ON;
SET NOCOUNT ON;
IF TRIGGER_NESTLEVEL() > 1 RETURN;
-- For debugging purposes only
PRINT CONVERT(NVARCHAR(MAX), EVENTDATA());
DECLARE #DatabaseName NVARCHAR(128);
SET #DatabaseName = EVENTDATA().value(
'(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(128)'
);
DECLARE #Schema NVARCHAR(128);
SET #Schema = EVENTDATA().value(
'(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(128)'
);
DECLARE #Name NVARCHAR(128);
SET #Name = EVENTDATA().value(
'(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(128)'
);
DECLARE #Definition NVARCHAR(MAX);
SELECT #Definition =
OBJECT_DEFINITION(
OBJECT_ID(
QUOTENAME(#DatabaseName) + '.' +
QUOTENAME(#Schema) + '.' +
QUOTENAME(#Name),
'P'
)
)
;
-- If the sproc is already encrypted, we can't do anything with it
IF #Definition IS NULL RETURN;
SELECT #Definition = STUFF(
#Definition,
CHARINDEX('CREATE', #Definition),
LEN('CREATE'),
'ALTER'
);
IF
#Name LIKE 'SqlQueryNotificationStoredProcedure-%' AND
-- this should always be false since we can't read encrypted definitions,
-- but just to make sure
#Definition NOT LIKE '%WITH ENCRYPTION AS BEGIN%'
BEGIN;
SET #Definition = REPLACE(
#Definition, 'AS' + CHAR(13) + CHAR(10) + 'BEGIN',
'WITH ENCRYPTION AS BEGIN'
);
EXEC (#Definition);
END;
END;
GO
ENABLE TRIGGER [TR_EncryptQueryNotificationProcedures] ON DATABASE;
Disclaimer: not tested against an actual dependency notification, but the basic idea is sound. It's quite brittle because it depends on the exact form of the procedure, of course -- making it more robust is possible, but tedious.
I'm experimenting with SEQUENCE objects in SQL Server, and getting the next value with C# by specifying the sequence name. Ranges are simple, because there is a stored procedure for them, and you can pass the sequence name;
public static T Reserve<T>(string name, int count, SqlConnection sqlConn)
{
using (var sqlCmd = new SqlCommand("sp_sequence_get_range", sqlConn))
{
sqlCmd.CommandType = CommandType.StoredProcedure;
var firstValueParam = new SqlParameter("#range_first_value", SqlDbType.Variant) { Direction = ParameterDirection.Output };
sqlCmd.Parameters.AddWithValue("#sequence_name", name);
sqlCmd.Parameters.AddWithValue("#range_size", count);
sqlCmd.Parameters.Add(firstValueParam);
sqlCmd.ExecuteNonQuery();
return (T)firstValueParam.Value;
}
}
But what about single values? It seems to me that I can either call the above with a count of '1', or I can construct the SQL dynamically. i.e.
var sqlCmdStr = string.Format("SELECT NEXT VALUE FOR {0}", name);
Which I know to generally be bad practice (i.e. SQL injection).
What would anyone suggest?
Which I know to generally be bad practice (i.e. SQL injection).
Not every dynamic SQL is evil.
Whether you are open to SQL injection depends on where the value (that gets inserted in SQL text) comes from. If it comes from a place under a tight control of your code (e.g. a switch statement that chooses from a set of string constants) then SQL injection is not an issue.
Or, you could simply have a separate query for each sequence (assuming you don't have very many of them).
My suggestion is a combination of both #Gserg's answer and your current solution. Write a stored procedure that takes a VARCHAR parameter #Name. Build the sql string in the stored procedure, using QUOTENAME as suggested by #GSerg. Use EXEC or sp_executesql to run the script.
Something like this (freehand):
CREATE PROCEDURE [GetNext]
#Name VARCHAR(50)
AS
BEGIN
DECLARE #sql VARCHAR(200);
SET #Name = QUOTENAME(#Name, '[');
SET #sql = 'SELECT NEXT VALUE FOR ' + #Name;
EXEC (#sql);
END
Another version of Paul's solution, which will return formatted alphanumeric Key from SQL Sequence
CREATE PROCEDURE [sp_GetNextKey]
#Name NVARCHAR(50),
#FormatText NVARCHAR(50)
AS
--DECLARE #Name NVARCHAR(50)='CustomerKeySequence'
--DECLARE #FormatText NVARCHAR(50) = 'CUS0000#'
DECLARE #sql NVARCHAR(200) = 'SELECT FORMAT((NEXT VALUE FOR ' + QUOTENAME(#Name, '"') + '),'+QUOTENAME(#FormatText, '''')+')';
EXEC (#sql)
/*
RETURNS i.e CUS00184
*/
When I need to do a similar thing, I do this:
string sanitized_name;
using (var sqlCmd = new SqlCommand("select quotename(#unsafe_name, '[');", sqlConn))
{
sqlCmd.Parameters.AddWithValue("#unsafe_name", name);
sanitized_name = (string)sqlCmd.ExecuteScalar();
}
using (var sqlCmd = new SqlCommand(string.Format("select next value for {0};", sanitized_name), sqlConn))
{
...
}
Or create a server-side procedure that does the same.
I am trying to return scalar from a database like this:
DbConnection cn = GetConnection2();
cn.Open();
// stored procedure
DbCommand cmd = GetStoredProcCommand(cn, "GetReason");
DbParameter param;
param = CreateInParameter("Reason_Number", DbType.String);
param.Value = number;
cmd.Parameters.Add(param);
param = CreateOutParameter("Result", DbType.String);
param.Direction = ParameterDirection.Output;
cmd.Parameters.Add(param);
cmd.ExecuteScalar();
string reason;
reason = cmd.Parameters["#Result"].Value.ToString();
if (cn.State == ConnectionState.Open)
cn.Close();
return reason;
Here is my stored procedure:
-- =============================================
-- Create date: Today
-- Description: Input Reason # and Return Full Reason Name
-- =============================================
ALTER PROCEDURE [dbo].[GetReason]
#Reason_Number nvarchar(50),
#Result nvarchar(50) output
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT #Result = Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
END
I am getting an error on the ExecuteScalar line:
System.InvalidOperationException occurred
Message="String[1]: the Size property has an invalid size of 0."
What am I doing wrong?
If you want to use ExecuteScalar, your stored proc needs to return the single row, single column from a SELECT:
ALTER PROCEDURE [dbo].[GetReason]
#Reason_Number nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
END
and then your code needs to read that value:
var returnedValue = cmd.ExecuteScalar();
and use it from there. Of course, in that case, you also do not need an OUTPUT parameter in your C# code....
Word of warning: that SELECT in your stored proc could potentially return multiple rows. You might want to add a TOP 1 to your select - just to be safe:
SELECT TOP 1 Field1
FROM dbo.Reasons
WHERE Field1 LIKE #Reason_Number + '%';
Just add an other one statement to the end of your stored procedure and remove OUTPUT parameter
SELECT #Result as 'Result'