The app is .Net Core 3.1, using EF Core 3 and a SQL Server on Azure
So I'm trying to create a table in my database with data from the client and I want to be safe from SQL injection.
So far I've tried with using a FormattableString which according to the doc is safe against SQL injection:
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = "CREATE TABLE {0} (";
var sqlParams = new List<object>
{
tableName
};
var first = true;
var count = 1;
foreach (var entry in fields)
{
// entry.Value is from code so it's safe againt injection
sql += first ? $"{{{count}}} {entry.Value}" : $", {{{count}}} {entry.Value}";
first = false;
sqlParams.Add(entry.Key);
count++;
}
sql += ");";
var safe = FormattableStringFactory.Create(sql, sqlParams.ToArray());
// Create the table
await _dbContext.Database.ExecuteSqlInterpolatedAsync(safe);
});
}
But I've an error : "incorrect syntax near '#p0'", despite it seems to generate a valid query (when getting the value of sage I got :
"CREATE TABLE sqlDataSourceGrainTest (Id uniqueidentifier NOT NULL PRIMARY KEY, CreatedAt datetime2(0), UpdatedAt datetimeoffset(3), FirstName nvarchar(4000), Birthdate date, XId uniqueidentifier, Datetime datetime2(0), Timestamp timestamp, Height decimal(18, 2), HasFoodAllergy bit, Age bigint);"
I've also tried to use with SQLParameter (which I prefer):
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = "CREATE TABLE #tableName (";
var sqlParams = new List<SqlParameter>()
{
new SqlParameter
{
ParameterName = "tableName",
Value = tableName,
}
};
var first = true;
foreach (var entry in fields)
{
sql += first ? $"#{entry.Key} {entry.Value}" : $", #{entry.Key} {entry.Value}";
first = false;
var sqlParam = new SqlParameter
{
ParameterName = $"{entry.Key}",
Value = entry.Key
};
sqlParams.Add(sqlParam);
}
sql += ");";
// Create the table
await _dbContext.Database.ExecuteSqlRawAsync(sql, sqlParams);
});
}
But I've have the error : "Incorrect syntax near '#tableName'."
Can someone help me to find the correct way to create the table? Is there any rules that say we can't use sql with parameters to create the table.
I've will also need to made update of the table, insert records and update records
Thanks
Edit: Based on answers from DavidG and HoneyBadger I've tried:
public Task CreateTableAsync(string tableName, JSchema tableSchema)
{
return TransactionAsync(async () =>
{
// Get the fields for the table creation
var fields = await ParseJSchemaForCreationAsync(tableSchema);
var sql = $"CREATE TABLE {tableName} (";
var sqlParams = new List<SqlParameter>();
var first = true;
foreach (var entry in fields)
{
sql += first ? $"#{entry.Key} {entry.Value}" : $", #{entry.Key} {entry.Value}";
first = false;
var sqlParam = new SqlParameter
{
ParameterName = $"{entry.Key}",
Value = entry.Key
};
sqlParams.Add(sqlParam);
}
sql += ");";
// Create the table
await _dbContext.Database.ExecuteSqlRawAsync(sql, sqlParams);
});
}
But now the error is "Incorrect syntax near '#id'" which is the name of the first parameter
SQL I see: CREATE TABLE tableTests ( #Id uniqueidentifier NOT NULL PRIMARY KEY, #CreatedAt datetime2(0), #UpdatedAt datetimeoffset(3), #FirstName nvarchar(4000), #Birthdate date, #XId uniqueidentifier, #Datetime datetime2(0), #Timestamp timestamp, #Height decimal(18, 2), #HasFoodAllergy bit, #Age bigint);"
Can't I use any parameters at all in the creation of a table?
Object names can't be parameters, so you'll need to use concatenation:
var sql = "CREATE TABLE " + tableName + " (";
I hope your users aren't involved in deciding the name of the table, so sql injection shouldn't be an issue.
Related
I am attempting to parameterize multiple values that are going into my query, but keep getting errors. This is what I am currently working on, in which I am getting a "must declare the scalar variable" error on the userClientIds parameter. Can someone help me figure out what I am missing.
public async Task<IEnumerable<SharedUser>> GetUsersForSharing(Guid userId, Guid
templateId, string? searchedEmpName, string? searchedEmpNumber)
{
// this is a list of ints, which will need passed into the WHERE IN clause below
var userClientIds = userClients.Select(client => client.ClientId).ToList();
var sql = $#"SELECT DISTINCT
UserId,
ClientId,
FullName,
EmployeeNumber
FROM dbo.ClientUser
WHERE
UserId <> #UserId
AND ClientId in (
#userClientIds
)";
if(searchedEmpName != null)
{
sql += $#"AND FullName LIKE '%#searchedEmpName%'";
}
if(searchedEmpNumber != null)
{
sql += $#"AND EmployeeNumber LIKE '%#searchedEmpNumber%'";
}
using(var conn = _connectionFactory.GetDbConnection())
{
var parameters = new DynamicParameters();
parameters.Add("#userId", userId.ToString());
parameters.Add("#userClientIds", new[] { userClientIds });
parameters.Add("#searchedEmpName", searchedEmpName);
parameters.Add("#searchedEmpNumber", searchedEmpNumber);
conn.Open();
var result = await conn.QueryAsync<SharedUser>(sql, new { parameters });
return result;
}
}
You just need to pass the whole list to DyanmicParameters without a containing array, and Dapper will inject it for you.
You must remove the parenthesis () of ClientId in (#userClientIds) otherwise you get a syntax error.
Some more notes:
Pass the dynamic parameters directly to Query, not inside a contatining array.
Use of DISTINCT is a code-smell: why does your tabel have duplicates in the first place? Perhaps you should improve your tabel design.
userId.ToString() why? If it's a Guid keep it as such.
The LIKE parameters are not going to work like that. instead you need to concatenate them within SQL $#"AND FullName LIKE '%' + #searchedEmpName + '%' etc.
Dapper will open and close the connection for you automatically.
public async Task<IEnumerable<SharedUser>> GetUsersForSharing(Guid userId, Guid
templateId, string? searchedEmpName, string? searchedEmpNumber)
{
var userClientIds = userClients.Select(client => client.ClientId).ToList();
var sql = $#"
SELECT
UserId,
ClientId,
FullName,
EmployeeNumber
FROM dbo.ClientUser
WHERE
UserId <> #UserId
AND ClientId in #userClientIds
";
if(searchedEmpName != null)
{
sql += $#"AND FullName LIKE '%' + #searchedEmpName + '%'
";
}
if(searchedEmpNumber != null)
{
sql += $#"AND EmployeeNumber LIKE '%' + #searchedEmpNumber + '%'
";
}
using(var conn = _connectionFactory.GetDbConnection())
{
var parameters = new DynamicParameters();
parameters.Add("#userId", userId);
parameters.Add("#userClientIds", userClientIds);
parameters.Add("#searchedEmpName", searchedEmpName);
parameters.Add("#searchedEmpNumber", searchedEmpNumber);
var result = await conn.QueryAsync<SharedUser>(sql, parameters);
return result;
}
}
If you have a big list then do not do the above code, as performance will be terrible. Instead use a table valued parameter, and pass it with .AsTableValuedParameter
First, create a table type
CREATE TYPE dbo.IdList (Id int PRIMARY KEY)
Then pass it like this
var table = new DataTable { Columns = {
{ "Id", typeof(int) },
} };
foreach (var id in userClientIds)
table.Rows.Add(id);
parameters.Add("#userClientIds", table.AsTableValuedParameter("dbo.IdList"));
And the syntax in SQL is
WHERE
UserId <> #UserId
AND ClientId in (SELECT uci.Id FROM #userClientIds uci)
Create client id to comma separated string;
var clientIds = String.Join(",", userClientIds.Select(i => i.ToString()).ToArray())
Then add parameter:
parameters.Add("#userClientIds", clientIds);
Picture with the error
public bool UpdateDistrict(string name, int id,int primarySeller, string primarySellerName)
{
bool result = false;
string connStr = #"Data Source=.;Initial Catalog=DataBase;Integrated Security=True";
string sql = "UPDATE District" + " SET Name = #name, Id = #id, PrimSellerId = #primarySeller , PrimSellerName = #primarySellerName" +
" WHERE Id = #id";
var district=GetDistrict(id);
if(name==null || name=="")
{
name=district.Name;
}
using (var conn = new SqlConnection(connStr))
{
conn.Query(sql, new
{
Name = name,
Id = id,
PrimSellerId = primarySeller,
PrimSellerName = primarySellerName,
});
result = true;
}
return result;
}
This is what I get as error when I try to update in the database
Microsoft.Data.SqlClient.SqlException: 'Must declare the scalar
variable "#primarySeller".
To expand on the comment, when you write a query in SQL:
UPDATE District
SET
Name = #name,
Id = #id,
PrimSellerId = #primarySeller ,
PrimSellerName = #primarySellerName
WHERE Id = #id
It is the parameter names (text after #) that should be specified in the anonymous type passed as the parameter arguments:
new
{
name,
id,
primarySeller,
primarySellerName,
}
In your SQL all your parameter names are the same as the c# variable names you use to make the anonymous type, so you can abbreviate the creation of the AT by just mentioning the variable names; the compiler will use them and the property names for the AT unless you're on an old version of .Net that doesn't support this (you'll find out), in which case you'll have to specify the column name too:
primarySeller = primarySeller
If your names differ you'll need to ensure that the AT property names are matched to the SQL parameter names:
conn.Query(
"UPDATE t SET userAge = #u WHERE id = #i",
new { u = 19, i = someId }
);
If you're running an update, insert, delete, merge (most things that aren't a select and don't produce rows of values, use Execute; it returns an integer of the number of rows affected, which you may want to utilise to indicate something didn't save:
int r = conn.Execute(...);
return r == 1;
I have a SQL Server 2005 database. In a few procedures I have table parameters that I pass to a stored proc as an nvarchar (separated by commas) and internally divide into single values. I add it to the SQL command parameters list like this:
cmd.Parameters.Add("#Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
I have to migrate the database to SQL Server 2008. I know that there are table value parameters, and I know how to use them in stored procedures. But I don't know how to pass one to the parameters list in an SQL command.
Does anyone know correct syntax of the Parameters.Add procedure? Or is there another way to pass this parameter?
DataTable, DbDataReader, or IEnumerable<SqlDataRecord> objects can be used to populate a table-valued parameter per the MSDN article Table-Valued Parameters in SQL Server 2008 (ADO.NET).
The following example illustrates using either a DataTable or an IEnumerable<SqlDataRecord>:
SQL Code:
CREATE TABLE dbo.PageView
(
PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
#Display dbo.PageViewTableType READONLY
AS
BEGIN
MERGE INTO dbo.PageView AS T
USING #Display AS S
ON T.PageViewID = S.PageViewID
WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END
C# Code:
private static void ExecuteProcedure(bool useDataTable,
string connectionString,
IEnumerable<long> ids)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "dbo.procMergePageView";
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter;
if (useDataTable) {
parameter = command.Parameters
.AddWithValue("#Display", CreateDataTable(ids));
}
else
{
parameter = command.Parameters
.AddWithValue("#Display", CreateSqlDataRecords(ids));
}
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.PageViewTableType";
command.ExecuteNonQuery();
}
}
}
private static DataTable CreateDataTable(IEnumerable<long> ids)
{
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(long));
foreach (long id in ids)
{
table.Rows.Add(id);
}
return table;
}
private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids)
{
SqlMetaData[] metaData = new SqlMetaData[1];
metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
SqlDataRecord record = new SqlDataRecord(metaData);
foreach (long id in ids)
{
record.SetInt64(0, id);
yield return record;
}
}
Further to Ryan's answer you will also need to set the DataColumn's Ordinal property if you are dealing with a table-valued parameter with multiple columns whose ordinals are not in alphabetical order.
As an example, if you have the following table value that is used as a parameter in SQL:
CREATE TYPE NodeFilter AS TABLE (
ID int not null
Code nvarchar(10) not null,
);
You would need to order your columns as such in C#:
table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals
If you fail to do this you will get a parse error, failed to convert nvarchar to int.
Generic
public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
{
var tbl = new DataTable();
tbl.Columns.Add("Id", typeof(T));
foreach (var item in list)
{
tbl.Rows.Add(selector.Invoke(item));
}
return tbl;
}
The cleanest way to work with it. Assuming your table is a list of integers called "dbo.tvp_Int" (Customize for your own table type)
Create this extension method...
public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
if(paramCollection != null)
{
var p = paramCollection.Add(parameterName, SqlDbType.Structured);
p.TypeName = "dbo.tvp_Int";
DataTable _dt = new DataTable() {Columns = {"Value"}};
data.ForEach(value => _dt.Rows.Add(value));
p.Value = _dt;
}
}
Now you can add a table valued parameter in one line anywhere simply by doing this:
cmd.Parameters.AddWithValueFor_Tvp_Int("#IDValues", listOfIds);
Use this code to create suitable parameter from your type:
private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
DataTable dt = new DataTable();
var properties = typedParameter.GetType().GetProperties().ToList();
properties.ForEach(p =>
{
dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
});
var row = dt.NewRow();
properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
dt.Rows.Add(row);
return new SqlParameter
{
Direction = ParameterDirection.Input,
ParameterName = name,
Value = dt,
SqlDbType = SqlDbType.Structured
};
}
If you have a table-valued function with parameters, for example of this type:
CREATE FUNCTION [dbo].[MyFunc](#PRM1 int, #PRM2 int)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM MyTable t
where t.column1 = #PRM1
and t.column2 = #PRM2
)
And you call it this way:
select * from MyFunc(1,1).
Then you can call it from C# like this:
public async Task<ActionResult> MethodAsync(string connectionString, int? prm1, int? prm2)
{
List<MyModel> lst = new List<MyModel>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = $"select * from MyFunc({prm1},{prm2})";
using (var reader = await command.ExecuteReaderAsync())
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
MyModel myModel = new MyModel();
myModel.Column1 = int.Parse(reader["column1"].ToString());
myModel.Column2 = int.Parse(reader["column2"].ToString());
lst.Add(myModel);
}
}
}
}
}
View(lst);
}
I have a stored procedure that accepts around 8 parameters and returns a bunch of data. The stored procedure itself runs fine and it returns the data when I call it in SSMS. But when I call it from my application using Dapper ORM, I don't get any data back.
I looked up similar threads here and I've tried everything that was suggested like this one: Dapper multi-parameter stored procedure query returns nothing back from database
Here's my code:
localConnection = new SqlConnection(defaultSettings.SetConnectionString(auth.DB_Name));
var parameters = new DynamicParameters();
try
{
parameters.Add("#param2", "val1");
parameters.Add("#param3", "val2");
parameters.Add("#param4", "val3");
parameters.Add("#param5", "val4");
parameters.Add("#param6", "val5");
parameters.Add("#param7", "val6");
parameters.Add("#param8", "val7");
var orders = localConnection.Query<Order>("spGetData", parameters, commandType: CommandType.StoredProcedure).ToList();
}
catch (Exception ex)
{
log.Error("Getdata Error: " + ex.Message);
}
Here's the SQL Server stored procedure:
CREATE PROCEDURE [dbo].[spGetData]
#param1 VARCHAR(8),
#param2 VARCHAR(8),
#param3 VARCHAR(8),
#param4 VARCHAR(8),
#param5 VARCHAR(8),
#param6 VARCHAR(8),
#param7 VARCHAR(8)
AS
SET NOCOUNT ON
SELECT TOP 1 *
FROM Orders a
JOIN ChangeLog b ON a.id = b.id
WHERE a.name = #param1
AND a.col2 = #param2
AND b.col1 = #param3
AND b.col3 = #param4
AND b.col4 = #param5
AND a.col3 = #param6
AND b.col5 = #param7
RETURN 0
Any idea what is going on? Or what is it that I'm doing wrong?
There's no problem with Dapper and multiple parameters as far as I can see. Here's a test code I created:
class Program
{
static void Main(string[] args)
{
SqlConnection c = new SqlConnection(#"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=tempdb;Data Source=.\SQLEXPRESS");
c.Execute(#"
CREATE PROCEDURE GetPerson(
#N VARCHAR(10),
#A INT,
#S INT
)AS
BEGIN
SELECT #N as Name, #A as Age, #S as Salary;
END;");
//works
var p = c.Query<Person>("GetPerson", new { A = 1, N = "John", S = 1000 }, commandType: System.Data.CommandType.StoredProcedure);
//doesn't work, "procedure expects parameter #A which was not supplied"
int i = 2, j = 2000; string n = "Frank";
var q = c.Query<Person>("GetPerson", new { i, n, j }, commandType: System.Data.CommandType.StoredProcedure);
//works
int A = 3, S = 3000; string N = "Joe";
var r = c.Query<Person>("GetPerson", new { S, A, N }, commandType: System.Data.CommandType.StoredProcedure);
//works
DynamicParameters dp = new DynamicParameters();
dp.Add("#A", 4);
dp.Add("#N", "Derek");
dp.Add("#S", 4000);
var s = c.Query<Person>("GetPerson", dp, commandType: System.Data.CommandType.StoredProcedure);
DynamicParameters dp2 = new DynamicParameters();
dp2.Add("A", 5);
dp2.Add("N", "Tim");
dp2.Add("S", 5000);
var t = c.Query<Person>("GetPerson", dp2, commandType: System.Data.CommandType.StoredProcedure);
}
}
class Person
{
public int Age { get; set; }
public string Name { get; set; }
}
All the queries that //works return a list of 1 person. The Salary column output by the query is not represented in the Person class so it just gets lost.
Key takeaways from this:
dapper is flexible about how it accepts parameters
dapper parameter name is important
dapper doesn't care about # in a parameter name
query columns that have no matching property in the strong class are not represented
And as you identified it's the last one that was critical in your case; your query wasn't outputting columns with names that matched your orders object
I found the issue. The Order class I'm using has properties that should match the stored procedure's data columns, which it was not.
Once I fixed that, I got it working.
I am trying to delete one record from oracle table in C#.net.
but i am getting the following error message.
"ORA-00932: inconsistent datatypes: expected NUMBER got BINARY"
Following is my table defination:
CREATE Table Study
(
UID RAW (16) DEFAULT SYS_GUID() NOT NULL ,
Notification VARCHAR2 (5) NOT NULL
);
I am writing the delete query like: DELETE FROM Study WHERE UID = MyByteArry
Here MyByteArry is the value of UID field from Study table read before executing the delete query.
See if the field data type is varchar then normally we provide the value in criteria expression as:
DELETE FROM Study WHERE UID = 'Some Value'
But how can i specify value in criteria expression for field which having a Raw data type.
Following is the C# code:
private void ProcessItem(DataRow thisDataRow)
{
ArrayList cmdParams = new ArrayList();
string strSQL = "";
strSQL = #"DELETE FROM Study
WHERE UID = :paramUID";
cmdParams.Add(thisDataRow["UID"]);
objDbCommExt.ExecuteNonQuery(strSQL, cmdParams);
}
public int ExecuteNonQuery(string strSQL, ArrayList cmdParams = null)
{
IDbCommand objIDbCommand = GetCommand();
objIDbCommand.CommandText = strSQL;
if (cmdParams != null)
{
for(int i = 0; i < cmdParams.Count; i++)
{
IDataParameter objIDataParameter = GetDataParameter(_commandChar + "param" +
i.ToString(), cmdParams[i]);
objIDbCommand.Parameters.Add(objIDataParameter);
}
}
return ExecuteNonQuery(objIDbCommand);
}
public int ExecuteNonQuery(IDbCommand objIDbCommand)
{
objIDbCommand.Connection = _connection;
return objIDbCommand.ExecuteNonQuery();
}
public IDataParameter GetDataParameter(string parameterName = "", object parameterValue = null)
{
IDbCommand objIDbCommand = new OracleCommand();
IDataParameter parameter = objIDbCommand.CreateParameter();
parameter.ParameterName = parameterName;
parameter.Value = parameterValue;
return parameter;
}