Different behaviors from SQL Server than from Visual Studio - c#

I am currently developing a C# web application that executes a SQL Server stored procedure. The stored procedure is an OpenRowSet query, that pulls data from an excel file and then inputs the data into a SQL Server table.
If I execute the stored procedure in an SSMS query window everything works fine:
(160 rows affected) Completion time: 2021-09-07T15:54:14.1558312-04:00
If I execute the stored procedure from Visual Studio, I get the following message:
OLE DB provider "Microsoft.ACE.OLEDB.12.0" for linked server "(null)" returned message "The Microsoft Access database engine could not find the object 'Table$'. Make sure the object exists and that you spell its name and the path name correctly. If 'Table$' is not a local object, check your network connection or contact the server administrator.
I had assumed this was a permissions issue, but after running a profile on the call, Both the SSMS execution and the VS call are executed by the same user.
Here is the C# code:
public class DataPut
{
public static string _connection = ConfigurationManager.ConnectionStrings["_connection"].ConnectionString;
SqlConnection conn = new SqlConnection(_connection);
public void putReports(string sheetname, string filename, string tablename)
{
string query = "sp_DataImport";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#tablename", tablename);
cmd.Parameters.AddWithValue("#SheetName", sheetname);
cmd.Parameters.AddWithValue("#FilePath", filename);
conn.Open();
//cmd.CommandTimeout = 120;
cmd.ExecuteNonQuery();
conn.Close();
}
}
Here is the stored procedure:
CREATE PROCEDURE [dbo].[sp_DataImport]
#SheetName varchar(20),
#FilePath varchar(200),
#TableName varchar(50)
AS
BEGIN
DECLARE #SQL nvarchar(1000)
IF OBJECT_ID (#TableName,'U') IS NOT NULL
SET #SQL = 'insert INTO [' + #TableName +'] SELECT * FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0;Database=' + #FilePath + ''',
''SELECT * FROM [' + #SheetName + '] Where [Column] = ''''Value'''' AND [Column] =''''Value'''' AND [Column] = ''''Value'''''')'
ELSE
SET #SQL = 'SELECT * INTO [' + #TableName +'] FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',
''Excel 12.0;Database=' + #FilePath + ''',
''SELECT * FROM [' + #SheetName + '] Where [Column] = ''''Value'''' AND [Column] =''''Value'''' AND [Column] = ''''Value'''''')'
PRINT #SQL
EXEC sp_executesql #SQL
END
Any idea why these would have different behaviors?

Related

SQL Server : executing stored procedure problems

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.. (: )

Invalid column name on stored procedure

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+'"

Error calling SQL Server stored procedure from C# code

I have a stored procedure to create table in a SQL Server database. I need to call this stored procedure from C#. I get an exception
Incorrect Syntax near #TABLENAME
How do I fix this? I have the table name and column list from a xml file.
Stored procedure:
CREATE PROCEDURE PROC_CREATE_SFCOM_TABLE2
#TABLENAME VARCHAR(4000) ,
#COLUMNLIST VARCHAR(4000) ,
#ERRORMSG VARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON
DECLARE #EXEC_IMMEDIATE_VAR VARCHAR (4000)
SELECT #EXEC_IMMEDIATE_VAR = 'CREATE TABLE #TABLENAME#COLUMNLIST '
EXECUTE (#EXEC_IMMEDIATE_VAR)
END
GO
C# Code:
using (SqlConnection conn = new SqlConnection(connstring)
{
using (SqlCommand cmd = new SqlCommand("dbo.PROC_CREATE_SFCOM_TABLE2",conn))
{
cmd.CommandType = CommandType.StoredProcedure;
createScript = "("+columnScript+")";
if (tableTableSpace != null)
{
if (tableTableSpace != "" || tableTableSpace != string.Empty)
{
createScript += "TABLESPACE " + tableTableSpace;
}
}
SqlParameter parameter = new SqlParameter("#TableName",SqlDbType.NVarChar);
parameter.Direction = ParameterDirection.Input;
parameter.Value = tableName;
cmd.Parameters.Add(parameter);
SqlParameter parameter2 = new SqlParameter("#COLUMNLIST",SqlDbType.NVarChar);
parameter2.Direction = ParameterDirection.Input;
parameter2.Value = createScript;
cmd.Parameters.Add(parameter2);
SqlParameter parameter3 = new SqlParameter("#ErrorMsg", SqlDbType.NVarChar);
parameter3.Direction = ParameterDirection.Output;
parameter3.Size = 4000;
cmd.Parameters.Add(parameter3);
conn.Open();
cmd.ExecuteNonQuery();
string errorMsg = parameter3.Value.ToString();
if (errorMsg != string.Empty)
LogInfo("Error: " + errorMsg);
You can't parameterize your table or column names. You can only parameterize your values. Only way is to execute dynamic sql.
But you should have a very strong validation for that if you want to create a table dynamic. Create a white list for that for example.
Before you do that, read: The Curse and Blessings of Dynamic SQL
You didn't create the dynamic SQL string correctly. Change it to this...
CREATE PROCEDURE PROC_CREATE_SFCOM_TABLE2
#TABLENAME VARCHAR(4000) ,
#COLUMNLIST VARCHAR(4000) ,
#ERRORMSG VARCHAR(4000) OUTPUT
AS
BEGIN
SET NOCOUNT ON
DECLARE #EXEC_IMMEDIATE_VAR VARCHAR (4000)
SELECT #EXEC_IMMEDIATE_VAR = 'CREATE TABLE [' + #TABLENAME + '] ' + #COLUMNLIST
EXECUTE (#EXEC_IMMEDIATE_VAR)
END
GO
However, please note that this is a very dangerous thing to do. #COLUMNLIST could contain anything, including other SQL commands, which could be used for a SQL injection attack

How to change a sql login password with variables

I am trying to update the password for an existing SQL login using Alter LOGIN
I know the following works
ALTER LOGIN [username1] WITH PASSWORD = 'somenewpassword123';
However when I try to use a local variable
DECLARE #newpass nvarchar(max);
SET #newpass = 'P#ssw0rd12345';
ALTER LOGIN [username1] WITH PASSWORD = #newpass;
This fails. Adding [] braces to the variable seems to resolve this within the SSMS query editor however using this programmaticlly by writing out the query in C# it fails as the above statement with the same error ( syntax error at PASSWORD)
Code within c# app
public static int UpdateSqlLoginPassword(DbContext context, string loginName, string password)
{
try
{
string updatePassword =
#" SET NOCOUNT ON
DECLARE #loginName AS nvarchar(max) = {0}
DECLARE #password AS nvarchar(max) = {1}
EXEC('
USE master
ALTER LOGIN ['+ #loginName + '] WITH PASSWORD = ['+ #password + ']
')";
return context.Database.ExecuteSqlCommand(updatePassword, loginName, password);
}
catch (Exception)
{
return -2;
}
}
I have also tried to hash the password (thinking that was the issue with the variable) but the syntax here is not being accepted
DECLARE #newpass nvarchar(max);
SET #newpass = 'P#ssw0rd12345';
DECLARE #hashedpass varbinary(max);
SET #hashedpass = HASHBYTES('SHA1', CONVERT(nvarchar(max),#newpass));
ALTER LOGIN [newuser10] WITH PASSWORD = #hashedpass HASHED;
SELECT #hashedpass;
Can anyone help me understand how to update a login's password in sql using a variable instead of a fixed value?
thanks in advance
Update
Based upon a suggestion from Charlie I also tried the following
public static int UpdateSqlLoginPassword(DbContext context, string loginName, string password)
{
try
{
string updatePassword =
#"ALTER LOGIN [' + #loginName +'] WITH PASSWORD = #password ";
return context.Database.ExecuteSqlCommand(updatePassword, new SqlParameter("loginName", loginName), new SqlParameter("password", password));
}
catch (Exception)
{
return -2;
}
}
This still generates a sqlException Incorrect Syntax new '#password'.
If I brace the parameter
public static int UpdateSqlLoginPassword(DbContext context, string loginName, string password)
{
try
{
string updatePassword =
#"ALTER LOGIN [' + #loginName +'] WITH PASSWORD = [' + #password +']";
return context.Database.ExecuteSqlCommand(updatePassword, new SqlParameter("loginName", loginName), new SqlParameter("password", password));
}
catch (Exception)
{
return -2;
}
}
I then generate a sqlException Incorrect syntax near PASSWORD.
Update2
Using the updated suggestions from Charlie I attempted to use the QuoteName function
string sql = #"DECLARE #sql NVARCHAR(500)
SET #sql = 'ALTER LOGIN ' + QuoteName(#loginName) +
' WITH PASSWORD = ' + QuoteName(#password, '''')
EXEC #sql";
return context.Database.ExecuteSqlCommand(sql, new SqlParameter("loginName", loginName), new SqlParameter("password", password));
While it appears that the query string is properly formed the following SQLException is thrown
*The name 'ALTER LOGIN [newuser10] WITH PASSWORD = 't#P#ssw0rd'' is not a valid identifier.
EDIT
After some more reading the error was generated by a syntax error wrapping the #sql allows the query to execute with no errors
string sql = #"DECLARE #sql NVARCHAR(500)
SET #sql = 'ALTER LOGIN ' + QuoteName(#loginName) +
' WITH PASSWORD = ' + QuoteName(#password, '''')
EXEC(#sql)";
On a side note: by simply building the string and running it as
string updatePassword = "USE MASTER ALTER LOGIN [" + loginName + "] WITH PASSWORD = '" + password + "'";
return context.Database.ExecuteSqlCommand(updatePassword);
the above is also a workaround and updates the sql login. While the implementation of this code minimizes the potential for sql injections this is not the most desirable approach.
-Thanks
You need to use parameters at the DbContext level. See this answer for more details, but, here's a code example (adapted from that same page):
string sql = "ALTER LOGIN #loginName WITH PASSWORD = #password";
ctx.Database.ExecuteSqlCommand(
sql,
new SqlParameter("loginName", loginName),
new SqlParameter("password", password));
The purpose of using the parameters here (and everywhere) is to prevent a SQL injection attack. This is especially important given that you are writing code that changes a password.
UPDATE
The ALTER LOGIN statement won't work with variables; it must be done through dynamic SQL. Here's an example of the updated code:
string sql = #"DECLARE #sql NVARCHAR(500)
SET #sql = 'ALTER LOGIN ' + QuoteName(#loginName) +
' WITH PASSWORD= ' + QuoteName(#password, '''')
EXEC #sql ";
ctx.Database.ExecuteSqlCommand(
sql,
new SqlParameter("loginName", loginName),
new SqlParameter("password", password));
Note we're still using the SqlParameters to prevent SQL injection attacks. We are also using the T-SQL method QuoteName to do proper quoting in the SQL we are generating; but this method simply doubles any [ characters (in the first call) or ' characters (in the second). There are many other vectors for a SQL injection attack, so merely relying on QuoteName wouldn't be enough.
I'm using the above answer with Azure SQL and I was getting the "not a valid identifier" error until I surrounded replaced "EXEC #sql" with "EXEC (#sql)". See Msg 203, Level 16, State 2, is not a valid identifier
Additionally, I had to use "ALTER USER" instead of "ALTER LOGIN"
After Preparing SQL query string and executing using c# SQL Command, I was always getting Invalid Identifier error.
It was because QuoteName should get executed before executing change password sql statements.
So I created stored procedure using above solutions then called procedure from c#, it worked for me.
Create procedure usp_updateSqlUsers(#loginName nVarchar(100), #pwd nvarchar(100))
as
begin
DECLARE #sql NVARCHAR(500)
set #sql='Alter LOGIN '+QUOTENAME(#loginName)+' WITH
password=N'+ QUOTENAME(#pwd,'''')
exec sp_sqlexec #sql
end
Then execute from C#
SqlCommand cmd = new SqlCommand("usp_updateSqlUsers", con) {CommandType =
CommandType.StoredProcedure};
var passwordParam = new SqlParameter("#pwd", password);
var sqlLoginParameter = new SqlParameter("#loginName", "SqlLoginName");
cmd.Parameters.Add(passwordParam);
cmd.Parameters.Add(sqlLoginParameter);
cmd.ExecuteNonQuery();

Large SQL script calls every 5min that is crashing IIS pool?

Context:
I have a dozen of servers.
Each server have a IIS with a site that executes the following large SQL script every 5 minutes.
On some servers, the pool that hosts the site crash. The pool contains this site only.
I need to recycle the pool after each crash... with my hands currently.
So there is an issue with the site and, I think, with the large SQL script.
The C# code that calls the SQL script:
string root = AppDomain.CurrentDomain.BaseDirectory;
string script = File.ReadAllText(root + #"..\SGBD\select_user_from_all_bases.sql").Replace("$date", dtLastModif);
string connectionString = #"Data Source=(local);Integrated Security=SSPI";
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var command = new SqlCommand(script, connection);
var reader = command.ExecuteReader();
var users = new List<UserModel>();
while (reader.Read())
{
users.Add(new UserModel()
{
dbName = String.Format("{0}", reader[0]),
idExternal = int.Parse(String.Format("{0}", reader[1])),
firstname = String.Format("{0}", reader[2]),
lastname = String.Format("{0}", reader[3]),
login = String.Format("{0}", reader[4]),
password = String.Format("{0}", reader[5]),
dtContractStart = reader[6] != DBNull.Value ? (DateTime?)reader[6] : null,
dtContractEnd = reader[7] != DBNull.Value ? (DateTime?)reader[7] : null,
emailPro = String.Format("{0}", reader[8]),
emailPerso = String.Format("{0}", reader[9])
});
}
return users;
}
And the SQL script:
USE master
DECLARE db_names CURSOR FOR
SELECT name FROM sysdatabases WHERE [name] LIKE 'FOO_%' AND [name] NOT LIKE 'FOO_TRAINING_%'
DECLARE #db_name NVARCHAR(100)
DECLARE #query NVARCHAR(MAX)
DECLARE #queryFinal NVARCHAR(MAX)
SET #query = ''
OPEN db_names
FETCH NEXT FROM db_names INTO #db_name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #query = #query + 'SELECT ''' + #db_name + ''', id_salarie, nom, prenom, login COLLATE SQL_Latin1_General_CP1_CI_AS, password COLLATE SQL_Latin1_General_CP1_CI_AS, date_arrivee, date_depart, email COLLATE SQL_Latin1_General_CP1_CI_AS, persoMail COLLATE SQL_Latin1_General_CP1_CI_AS FROM [' + #db_name + '].dbo.utilisateurs WHERE dt_last_modif >= ''$date'' UNION '
FETCH NEXT FROM db_names INTO #db_name
END
DEALLOCATE db_names
SET #queryFinal = left(#query, len(#query)-6)
EXEC sp_executesql #queryFinal
More information about servers:
Server0 : 8 databases, 1050 users, no crash
Server1 : 88 databases, 18954 users, crash often
Server2 : 109 databases, 21897 users, crash often
Server3 : 26 databases, 1612 users, no crash
etc
Questions :
What is the issue with the script ? Any idea how I can stop crashs ?
And if no solution, how can I automatically recycle the pool?
Have you tried to make shure that the reader is cloesd after usage, too?
using(var reader = command.ExecuteReader()) { ...
I am not shure if the closed connection
using (var connection = new SqlConnection(connectionString))
takes care of the command and the reader resources.
I would do a few things here... if your problem is that persistent. First, I WOULD NOT generate one complete sql query trying to get data from all those tables all at once. Next, the queries are querying, and implied might be trying to LOCK the records associated with the query for POSSIBLE update... even though you are not probably going to be doing that.
I would add a WITH (NOLOCK) on the from tables.
select columns from yourTable WITH(NOLOCK) where...
This prevents any overhead with locking all the pages associated with the query.
Now, how to better handle your loop. Immediately BEFORE your fetch loop, I would create a temp table of the expected output results... something like
(unsure of column name lenghts for your structures...
create #C_TempResults
( fromDBName char(20),
id_salarie int,
nom char(10),
prenom char(10),
login char(10),
password char(10),
date_arivee datetime,
date_depart datetime,
email char(60),
persoMail char(60) );
then, in your loop where you are already cycling through all the tables you are querying, instead of building a concatenated SQL statement to execute at the end, just run ONE AT A TIME, and insert into the temp table like...
(same beginning to prepare your fetch cursor...)
BEGIN
SET #query = 'INSERT INTO #C_TempResults '
+ ' SELECT ''' + #db_name + ''' as fromDBName, id_salarie, nom, prenom, '
+ 'login COLLATE SQL_Latin1_General_CP1_CI_AS, '
+ 'password COLLATE SQL_Latin1_General_CP1_CI_AS, '
+ 'date_arrivee, date_depart, '
+ 'email COLLATE SQL_Latin1_General_CP1_CI_AS, '
+ 'persoMail COLLATE SQL_Latin1_General_CP1_CI_AS '
+ 'FROM [' + #db_name + '].dbo.utilisateurs WITH (NOLOCK) '
+ 'WHERE dt_last_modif >= ''$date'' ';
-- Run this single query now, get the data and release any "lock" resources
EXEC sp_executesql #queryFinal
-- now, get the next database to query from and continue
FETCH NEXT FROM db_names INTO #db_name
END
DEALLOCATE db_names
-- FINALLY, just run your select from the temp table that has everything all together...
select * from #C_TempResults;
-- and get rid of your "temp" table
drop table #C_TempResults;

Categories