Unable to get Dynamic SQL to pass a dynamic table parameter - c#

Not worried about SQL Injection or anything of the like, just trying to get this to work. Using SSMS and Visual Studio.
I have C# code that passes a variable, GlobalVariables.username, to an SQL parameter.
private void btnNext_Click(object sender, EventArgs e)
{
if (checkIntrotoPublicSpeaking.Checked || checkEffectiveOralCommunication.Checked || checkProfComm.Checked)
{
List<SqlParameter> sqlOralComm = new List<SqlParameter>();
sqlOralComm.Add(new SqlParameter("Username", GlobalVariables.username));
sqlOralComm.Add(new SqlParameter("IntrotoPublicSpeaking", cboxIntrotoPublicSpeaking.Text));
sqlOralComm.Add(new SqlParameter("EffectiveOralCommunication", cboxEffectiveOralCommunication.Text));
sqlOralComm.Add(new SqlParameter("ProfComm", cboxProfComm.Text));
DAL.ExecSP("CreateOralComm", sqlOralComm);
}
}
I've been reading into Dynamic SQL and saw that to pass the table name as a parameter, you have to construct it manually and execute it as "SET..." etc, etc. I've been trying slightly different modifications of the last 3 lines below. Each time, I'm greeted with an "invalid syntax near ..." exception pertaining to different parts of that line. In stack exchange it's broken into 3 lines but in SSMS it's one line, a little easier to read.
Status is nvarchar column and Course is an int column.
ALTER PROCEDURE [dbo].[CreateOralComm]
-- Add the parameters for the stored procedure here
#Username nvarchar(30),
#IntrotoPublicSpeaking nvarchar(3),
#EffectiveOralCommunication nvarchar(3),
#ProfComm nvarchar(3)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
END
GO
I know that global variable works, I have another line of code that's just a MessageBox displaying the value and it's correct. Just can't get those last few lines of SQL to work. I'm trying out just this first part, #IntrotoPublicSpeaking, before I move onto the other 2.
Any help would be really appreciated.

Two things here:
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
Missing comma before Status and I think you do need to use single quotes
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ', Status = ''Completed'' WHERE Course = 7600105';
EXEC sp_executesql #sql;

Related

Update a null column in sql with spexecutesql

I encountered a problem in the following query. When I go to update the column ID_Premesso with the value NULL the value does not change, while if I go to update with the value 0 it works.
SET #SQL = 'UPDATE ' + #NameTB + ' SET ID_Permesso = NULL WHERE ID_Timbratura = #IDTimbratura '
SET #PARAMS = '#IDTimbratura INT'
EXECUTE sp_executesql #SQL, #PARAMS, #IDTimbratura;
= does not work for NULL values. So, if ID_Timbratura can be NULL, you need to take that into account:
SET #SQL = '
UPDATE ' + #NameTB + '
SET ID_Permesso = NULL
WHERE ID_Timbratura = #IDTimbratura OR
(ID_Timbratura IS NULL AND #IDTimbratura IS NULL)' ;
SET #PARAMS = N'#IDTimbratura INT';
EXECUTE sp_executesql #SQL, #PARAMS, #IDTimbratura;
Note that SQL Server lets you break strings over multiple lines. I reformatted the query string so it is easier to read.

SQL Server – pulling user data using dynamic query in stored procedure

I have problem, I'm having trouble viewing user information using stored procedure. The procedure accepts three parameters: table, column and searchBySomething. Every time I want to search for a user using another column, the column variable receives the column of the id and the searchBySomething variable receives specific id, the procedure is work but when I'm send another column I'm get the error message
Invalid column name (the data)
The procedure looks like this :
ALTER PROCEDURE [dbo].[userDetailsDisplay]
#table NVARCHAR(30),
#column NVARCHAR(30),
#searchBySomething NVARCHAR(30)
DECLARE #sql NVARCHAR(100)
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' + #searchBySomething
EXECUTE sp_executesql #sql
So the specific error you're getting is because you're not checking the input to see if the string being passed into #column actually exists. You can check for it's existence against the metadata catalog view sys.columns doing something like this:
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
However I would be remiss if I didn't point out two things.
First, this dynamic table dynamic where clause pattern is very bad practice. If it's for someone who already has database access, they can simply query the tables themselves. And if it's for an external user, well, you've basically given them full database read access through this procedure. Of course there are some rare occasions where this pattern is needed, so if you're dead set on using dynamic sql, that leads me to my next point.
The code you've written is vulnerable to a SQL injection attack. Any time you use dynamic SQL you must be VERY careful how it's constructed. Say I passed in the column name ; drop database [admin]-- Assuming you had such a database, my could would happily be executed and your database would disappear.
How to make dynamic SQL safe is a complex topic, but if you're serious about learning more about it, this is probably one of the best articles you can find. http://www.sommarskog.se/dynamic_sql.html
By parameterizing your query and using quotename() on the table and column, I modified it to look like this. This will still throw weird errors if someone tries to do an injection attack, but at least it wont actually execute their code.
create procedure [dbo].[userDetailsDisplay]
#table nvarchar(30),
#column nvarchar(30),
#searchBySomething nvarchar(30)
as
begin
declare
#sql nvarchar(max),
#params nvarchar(1000)
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
select #sql = '
select *
from ' + quotename(#table) + ' WHERE ' + quotename(#column) + ' = #searchBySomething'
execute sp_executesql
#stmt = #sql,
#params = '#searchBySomething nvarchar(30)',
#searchBySomething = #searchBySomething
end
Just check to make sure that the column exist in the table.
for each #table called, check that the #column variable is in that table.
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' +''' #searchBySomething +''''
Ex : select * from table where column ='value'

Sql Cache dependency in Asp.Net

I m loading table data into cache .And binding it to dropdown .But if there any new value added in datatable then that cache should invalid and new cache should get create.I am using Asp.Net 3.5 ,linq to SQL
Folloing is my .aspx code
public List<Employee> GetEmployeeData()
{
//get from cache
object obj = HttpRuntime.Cache.Get(Constants.Constants.cacheEmployee);
if (obj != null)
{
return obj as List<Employee>;
}
else // get from database
{
using (TestdbEntities entities = new TestdbEntities(connectionString))
{
try
{
IEnumerable<Employee> employee= entities.Employees.OrderBy(l => l.EmployeeName);
System.Web.Caching.SqlCacheDependencyAdmin.EnableNotifications(connectionString);
if (!System.Web.Caching.SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(connectionString).Contains ("master.Employee"))
System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connectionString, "master.Employee");
System.Web.Caching.SqlCacheDependency sqldep = new System.Web.Caching.SqlCacheDependency(connectionString, "master.Employee"); ;
HttpRuntime.Cache.Insert(Constants.Constants.cacheEmployee, employee.ToList(), sqldep);
return employee.ToList();
}
catch (Exception ex)
{
throw ex;
}
}
}
}
But i am getting following error
Please make sure the database name and the table name are valid. Table names must conform to the format of regular identifiers in SQL.
Please suggest
The other answer is correct that the "." in the table name is the issue. For some reason the AspNet_SqlCacheRegisterTableStoredProcedure wasn't written to handle different schemas.
I must credit this link with cluing me into the issue:
http://forums.asp.net/t/1249281.aspx?SQL+Cache+Dependency+with+custom+schema
However, I came up with my own re-write of the stored proc below that is a little better :) It's a little cleaner, handles the situation even if [ are included in the name, and also keeps the schema name in the AspNet_SqlCacheTablesForChangeNotification table in case you have the same table name in two different schemas.
DECLARE #triggerName AS NVARCHAR(3000)
DECLARE #fullTriggerName AS NVARCHAR(3000)
DECLARE #canonTableName NVARCHAR(3000)
DECLARE #quotedTableName NVARCHAR(3000)
DECLARE #schemaName NVARCHAR(3000)
DECLARE #schemaAndTableName NVARCHAR(3000)
--Replace these if they are in the param, we'll add them back later for the #canonTableName only
SET #tableName = REPLACE(REPLACE(#tableName, '[', ''), ']', '')
IF(CHARINDEX('.',#tableName) <> 0)
BEGIN
SET #schemaName = SUBSTRING(#tableName,0,CHARINDEX('.',#tableName))
SET #tableName = SUBSTRING(#tableName,CHARINDEX('.',#tableName) + 1,LEN(#tableName) - CHARINDEX('.',#tableName))
SET #schemaAndTableName = #schemaName + '.' + #tableName
END
ELSE
BEGIN
SET #schemaName = 'dbo' --If the param doesn't have a schema, use the default
SET #schemaAndTableName = #tableName --If they didn't pass in the schema, we don't want it in the AspNet_SqlCacheTablesForChangeNotification table
END
SET #triggerName = #tableName + '_AspNet_SqlCacheNotification_Trigger'
SET #fullTriggerName = '[' + #schemaName + '].[' + #triggerName + ']'
SET #canonTableName = '[' + #schemaName + '].[' + #tableName + ']'
/* First make sure the table exists */
IF (SELECT OBJECT_ID(#schemaAndTableName, 'U')) IS NULL
BEGIN
RAISERROR ('00000001', 16, 1)
RETURN
END
BEGIN TRAN
/* Insert the value into the notification table */
IF NOT EXISTS (SELECT tableName FROM dbo.AspNet_SqlCacheTablesForChangeNotification WITH (NOLOCK) WHERE tableName = #schemaAndTableName)
IF NOT EXISTS (SELECT tableName FROM dbo.AspNet_SqlCacheTablesForChangeNotification WITH (TABLOCKX) WHERE tableName = #schemaAndTableName)
INSERT dbo.AspNet_SqlCacheTablesForChangeNotification
VALUES (#schemaAndTableName, GETDATE(), 0)
/* Create the trigger */
SET #quotedTableName = QUOTENAME(#schemaAndTableName, '''')
IF NOT EXISTS (SELECT name FROM sysobjects WITH (NOLOCK) WHERE name = #triggerName AND type = 'TR')
IF NOT EXISTS (SELECT name FROM sysobjects WITH (TABLOCKX) WHERE name = #triggerName AND type = 'TR')
EXEC('CREATE TRIGGER ' + #fullTriggerName + ' ON ' + #canonTableName +'
FOR INSERT, UPDATE, DELETE AS BEGIN
SET NOCOUNT ON
EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N' + #quotedTableName + '
END
')
COMMIT TRAN
The problem is the . in the table name master.Employee. The EnableTableForNotifications method calls the stored procedure AspNet_SqlCacheRegisterTableStoredProcedure in the database. Inside that is a call to the OBJECT_ID function, and I think that is parsing the table name as being a table called Employee in the master database. (When I first looked at this question, I was thinking the same way).
If you were to replace the . with an underscore _ in the table name, then your code will run correctly.
(You might also want to review your code in the case where the data isn't in the cache; data can disappear from the cache for any number of reasons but currently your code enables the database up for notifications every time, regardless of whether it's already enabled. The Single Responsibility Principle applies here to the GetEmployeeData method; it should only be concerned with getting data from the cache or database and not with setting up caching and notifications, that code should be broken out into a separate method.)

How do I get the correct sql quoted string for a .NET DbType

I want to run an ALTER TABLE that adds a default value constraint to a column.
I generate this statement dynamically from a .NET program.
How do I best format and quote the value when building my sql - now that ALTER TABLE statements does not support parameters (Gives the error 'Variables are not allowed in the ALTER TABLE statement').
Is there a utility for that in .NET? Or another solution?
You can do this in TSQL; for example, say you parameterize the command, passing in #DefaultValue, a varchar which may or may not be a valid TSQL literal. Because we are writing DDL, we will need to concatenate and exec, however we clearly don't want to blindly concatenate, as the value could be illegal. Fortunately, quotename does everything we need. By default, quotename outputs [qualified object names], but you can tell it to operate in literal escaping mode, for both single-quote and double-quote literals.
So our query that accepts #DefaultValue can build an SQL string:
declare #sql nvarchar(4000) = 'alter table ...';
-- ... blah
-- append the default value; note the result includes the outer quotes
#sql = #sql + quotename(#DefaultValue, '''');
-- ... blah
exec (#sql);
Full example:
--drop table FunkyDefaultExample
create table FunkyDefaultExample (id int not null)
declare #tableName varchar(20) = 'FunkyDefaultExample',
#colName varchar(20) = 'col name',
#defaultValue varchar(80) = 'test '' with quote';
-- the TSQL we want to generate to exec
/*
alter table [FunkyDefaultExample] add [col name] varchar(50) null
constraint [col name default] default 'test '' with quote';
*/
declare #sql nvarchar(4000) = 'alter table ' + quotename(#tablename)
+ ' add ' + quotename(#colName) + 'varchar(50) null constraint '
+ quotename(#colName + ' default') + ' default '
+ quotename(#defaultValue, '''');
exec (#sql);
-- tada!
string.Format("alter table YourTable add constraint DF_YourTable_Col1 default '{0}'",
inputValue.Replace("'", "''"));

What is the best way to get rows from different databases?

I have 10 identical databases.
I get the database names at runtime.
I want to store rows into a collection of objects.
I also only want one hit on the database server.
My current approach:-
In a query (no stored procedures for X reason) I get list of databases and store in a temporary table.
Then I iterate through each database and create a dynamic query and execute it.
DECLARE #MaxRownum int SET #MaxRownum = (SELECT MAX(RowNum) FROM #Databases)
DECLARE #Iter int SET #Iter = 1
WHILE #Iter <= #MaxRownum
BEGIN
DECLARE #Database varchar(255) SELECT #Database = Databases FROM #Databases
WHERE RowNum = #Iter
IF HAS_DBACCESS(#Database) > 0
BEGIN
//appending query
END
SET #Iter = #Iter + 1
END
EXEC(#Query)
Can I use Linq + entity framework with one hit to server, without dynamic query and without hampering the performance? Is there any better solution?
Having no idea what your query is (I asked but you did not supply it), and not sure that you understand it is going to be extremely difficult to supply database names as variables without a "dynamic query", here is a much simpler way to do it IMHO:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'UNION ALL'
--// you will need to fill in your "//appending query" stuff here:
+ ' SELECT ... FROM ' + QUOTENAME(Databases) + '.dbo.tablename'
FROM #Databases
WHERE HAS_DBACCESS(Databases) = 1;
SET #sql = STUFF(#sql, 1, 9, '');
EXEC sp_executesql #sql;

Categories

Resources