How can I change a Stored Procedure's Collation in SQL? - c#

In my C# program when I try to run one of my stored procedures, it gives me an exception which is related to a conflict between collations. How can I fix it?
Error Description: "Cannot resolve the collation conflict between
"Arabic_CI_AS" and "Latin1_General_CI_AS" in the equal to operation."

Collations in SQL Server define set of rules for comparing and sorting strings.
99 times out of 100 this particular error is a result of a cross-database query, with a join or where condition involving 'string' columns from tables in different databases (one of them typically being the tempdb).
You can specify a collation as part of a query, either a named one or the database_default keyword. See https://msdn.microsoft.com/en-us/library/ms184391.aspx for more info.
Just beware that this will practically disable (as far as this particular query is concerned) an index on the dynamically collated column if one exists.

I had the same error when creating sp that had some nvarchar arguments. These arguments were conditionally modified inside of this sp. Here's an example of the code that causes the error same as yours:
create procedure a
#name nvarchar(128),
as
if (#name = '')
throw 51000, 'Empty name', 1;
go
The solution here seems to be defining a collation (the best would be the database_default):
create procedure a
#name nvarchar(128),
as
if (#name = '' collate database_default)
throw 51000, 'Empty name', 1;
go

Related

Stored procedure with parameters to return specific multiple columns, not all (*)

Currently, I have a stored procedure that returns all the columns (Select *). I am using this stored procedure to retrieve data from multiple tables, each table with different number of columns and obviously different column names, so Select * is convenient. However, now I need to retrieve only specific columns from each table so I want to pass the parameters something like this:
SELECT #param1, #param2, #param3, etc.
FROM #tableName
WHERE columnName = #tableId
The problem here is the number of parameters to be passed for the columns isn't set, as the table can have any number of columns. Is there a way to use some kind of loop or dynamic assignment so that I can pass any number of parameters as column names?
I know that I can filter out only the columns I want to use, and just leave out the rest, but that doesn't work in my case. I need the stored procedure to NOT return some specific columns with sensitive data.
I am using SQL Server 2008, ASP.NET MVC 4, and C# in my application.
If you are able to modify your stored procedure, you can easily put the required columns definitions as a parameter and use an auto-created temporary table:
CREATE PROCEDURE sp_GetDiffDataExample
#columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = N'SELECT ' + #columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
EXEC sp_executeSql #query
SELECT * FROM ##TempTable
DROP TABLE ##TempTable
END
In this case you don't need to create a temp table manually - it is created automatically.
Hope this helps.
You can just pass one parameter that is a comma-delimited string of the columns you want to select and build a dynamic sql string.
#sql = 'SELECT ' + #param + ' FROM MyTable...';
EXECUTE (#sql);
If you use a dynamic sql solution, you should take care to guard against sql injection attacks.
You might also consider continuing to get all columns from the stored procedure, and showing only the columns the user wants in the front end.
Although I really do not like the stored procedure approach for this problem (I agree with #Gusman that building the query in C# is a better approach) you can make a stored procedure work without opening yourself to SQL Injection attacks. The following example is one simple way to do this:
Let's say that the table in question has the columns named COL1, COL2, and COL3. The stored procedure would accept a varchar(max) parameter named #IncludeCols and have code like:
SELECT CASE WHEN #IncludeCols LIKE '%#COL1#%' THEN COL1 ELSE '' END AS COL1,
CASE WHEN #IncludeCols LIKE '%#COL2#%' THEN COL2 ELSE '' END AS COL2,
CASE WHEN #IncludeCols LIKE '%#COL3#%' THEN COL3 ELSE '' END AS COL3
FROM <Table name>
WHERE <Where clause>
Yes, a column of every name will be returned, but data will only come from columns whose names are in the parameter. For example, if you wanted COL1 and COL3, the parameter value would be #COL1#COL3#. The # is important and must be on each side of every column name or any LIKE clause could get a false positive match.
If this is to export data to Excel then the best approach is likely to have a stored procedure that returns all of your columns minus the ones that should never be exported (passwords, other protected data, timestamps perhaps) and then filter out any additional unwanted columns in your front end.
You should never be using SELECT * for several reasons. Define what your application needs and program to that.

How can I make SQL Server 2012 truncate insertions if they are too big?

So I have a table with a column of type VARCHAR (100) and I'm wondering if there's a way to configure SQL Server 2012 (T-SQL) so that if a transaction tries to submit a string of 101+ characters then it takes the first 100.
Is this possible, or should I be doing the truncation in the C# side of things ???
Normally, SQL Server will present an error on any attempt to insert more data into a field than it can hold
String or binary data would be truncated. The statement has been terminated.
SQL Server will not permit a silent truncation of data just because the column is too small to accept the data. But there are other ways that SQL Server can truncate data that is about to be inserted into a table that will not generate any form of error or warning.
By default, ANSI_WARNINGS are turned on, and certain activities such as creating indexes on computed columns or indexed views require that they be turned on. But if they are turned off, SQL Server will truncate the data as needed to make it fit into the column. The ANSI_WARNINGS setting for a session can be controlled by
SET ANSI_WARNINGS { ON|OFF }
Unlike with an insert into a table, SQL Server will quietly cut off data that is being assigned to a variable, regardless of the status of ANSI_WARNINGS. For instance:
declare #smallString varchar(5)
declare #testint int
set #smallString = 'This is a long string'
set #testint = 123.456
print #smallString
print #testint
Results is:
This
123
This can occasionally show itself in subtle ways since passing a value into a stored procedure or function assigns it to the parameter variables and will quietly do a conversion. One method that can help guard against this situation is to give any parameter that will be directly inserted into a table a larger datatype than the target column so that SQL Server will raise the error, or perhaps to then check the length of the parameter and have custom code to handle it when it is too long.
For instance, if a stored procedure will use a parameter to insert data into a table with a column that is varchar(10), make the parameter varchar(15). Then if the data that is passed in is too long for the column, it will rollback and raise a truncation error instead of silently truncating and inserting. Of course, that runs the risk of being misleading to anyone who looks at the stored procedures header information without understanding what was done.
Source: Silent Truncation of SQL Server Data Inserts
Do this on code level. When you are inserting the current field check field length and Substring it.
string a = "string with more than 100 symbols";
if(a.Length > 100)
a = a.Substring(0, 100);
After that you are adding a as sql parameter to the insert query.
The other way is to do it in the query, but again I don't advice you to do that.
INSERT INTO Table1('YourColumn') VALUES(LEFT(RTRIM(stringMoreThan100symbols), 100))
LEFT is cutting the string and RTRIM is performing Trim operation of the string.
My suggestion would be to make the application side responsible for validating the input before calling any DB operation.
SQL Server silently truncates any varchars you specify as stored procedure parameters to the length of the varchar. So you should try considering stored procedures for you requirements. So it will get handled automatically.
If you have entity classes (not necessarily from EF) you can use StringLength(your field length) attribute to do this.

Stored Procedure Return Type Can Not Be Detected

I am trying to drag this procedure over to the dbml in VS 2012 and I am getting the return type cannot be detected message.
I have tried these:
LINQ to SQL - Stored Procedure Return Type Error
The return types for the following stored procedures could not be detected
I tried re-writing the procedure as a CTE and also as union to remove the OR, but it is giving me the same message.
The only return type in the designer properties for the method is int32.
Here is my procedure:
ALTER PROCEDURE [dbo].[GetStringFromFiles]
#SearchWord NVARCHAR(100) = null
AS
BEGIN
SET NOCOUNT ON
SET #SearchWord = UPPER(#SearchWord);
Select
a.FileId <---Guid
, a.FileData <---Binary
, a.BaselineId <---Guid
, a.FileName <---NVARCHAR
, a.FileExtension <---NVARCHAR
, b.FileByItemId <----Guid
, b.ItemId <---Guid
From FileTable a
Inner Join
FileByItem b on a.FileId = b.FileId
WHERE CONTAINS(a.FileData,'FORMSOF(INFLECTIONAL, #SearchWord)') or FREETEXT(a.FileData, #SearchWord)
RETURN 1
END
UPDATE:
A. I can add it if I comment out the entire Where Clause -- Auto-Generated-ReturnType
B. If I take away the or and just use:
WHERE CONTAINS(a.FileData,'FORMSOF(INFLECTIONAL, #SearchWord)')
it lets me add it -- Auto-Generated-ReturnType
C. If I just use
WHERE FREETEXT(a.FileData, #SearchWord)
It throws the error so it doesnt like the FREETEXT
If I comment the where clause out and add it and let it generate the return type then alter the procedure in the db are there any issues with that?
This is somewhat related. I stumbled across the same error today but for a different reason. If your query uses a temporary table in its results then it is unable to automatically assign the return type.
I changed it to a table variable (performance was not a major concern/and was not impacted)
Hope this helps someone browsing.
The line
RETURN 1
is the culprit in both the cases. Lose it and Linq should be able to detect the types your result set.
Linq to Sql won't get the results directly from a Stored procedure.
Instead an ISingleResult will be returned. You have to enumerate
through the result set to get the values returned from the stored
procedure
If the stored procedure have any error also then this error can occur, for me its happened once since one of the table name was changed.
Another factor is to set Primary Keys and table relations if possible
A simple way to solve this issue is (December 2019)
Just making double # precede #tmp => ##tmp
Comment out DROP TABLE #tmp => --DROP TABLE #tmp
Execute stored procedure
Drag stored procedure again and That's it, It will generate return type
Last, Turn your store back to the first situation and then save.
Hope I can help.

How do I protect this function from SQL injection?

public static bool TruncateTable(string dbAlias, string tableName)
{
string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName);
return ExecuteNonQuery(dbAlias, sqlStatement) > 0;
}
The most common recommendation to fight SQL injection is to use an SQL query parameter (several people on this thread have suggested it).
This is the wrong answer in this case. You can't use an SQL query parameter for a table name in a DDL statement.
SQL query parameters can be used only in place of a literal value in an SQL expression. This is standard in every implementation of SQL.
My recommendation for protecting against SQL injection when you have a table name is to validate the input string against a list of known table names.
You can get a list of valid table names from the INFORMATION_SCHEMA:
SELECT table_name
FROM INFORMATION_SCHEMA.Tables
WHERE table_type = 'BASE TABLE'
AND table_name = #tableName
Now you can pass your input variable to this query as an SQL parameter. If the query returns no rows, you know that the input is not valid to use as a table. If the query returns a row, it matched, so you have more assurance you can use it safely.
You could also validate the table name against a list of specific tables you define as okay for your app to truncate, as #John Buchanan suggests.
Even after validating that tableName exists as a table name in your RDBMS, I would also suggest delimiting the table name, just in case you use table names with spaces or special characters. In Microsoft SQL Server, the default identifier delimiters are square brackets:
string sqlStatement = string.Format("TRUNCATE TABLE [{0}]", tableName);
Now you're only at risk for SQL injection if tableName matches a real table, and you actually use square brackets in the names of your tables!
As far as I know, you can't use parameterized queries to perform DDL statements/ specify table names, at least not in Oracle or Sql Server. What I would do, if I had to have a crazy TruncateTable function, that had to be safe from sql injection would be to make a stored procedure that checks that the input is a table that is safe to truncate.
-- Sql Server specific!
CREATE TABLE TruncableTables (TableName varchar(50))
Insert into TruncableTables values ('MyTable')
go
CREATE PROCEDURE MyTrunc #tableName varchar(50)
AS
BEGIN
declare #IsValidTable int
declare #SqlString nvarchar(50)
select #IsValidTable = Count(*) from TruncableTables where TableName = #tableName
if #IsValidTable > 0
begin
select #SqlString = 'truncate table ' + #tableName
EXECUTE sp_executesql #SqlString
end
END
If you're allowing user-defined input to creep into this function via the tablename variable, I don't think SQL Injection is your only problem.
A better option would be to run this command via its own secure connection and give it no SELECT rights at all. All TRUNCATE needs to run is the ALTER TABLE permission. If you're on SQL 2005 upwards, you could also try using a stored procedure with EXECUTE AS inside.
CREATE OR REPLACE PROCEDURE truncate(ptbl_name IN VARCHAR2) IS
stmt VARCHAR2(100);
BEGIN
stmt := 'TRUNCATE TABLE '||DBMS_ASSERT.SIMPLE_SQL_NAME(ptbl_name);
dbms_output.put_line('<'||stmt||'>');
EXECUTE IMMEDIATE stmt;
END;
Use a stored procedure. Any decent db library (MS Enterprise Library is what I use) will handle escaping string parameters correctly.
Also, re:parameterized queries: I prefer to NOT have to redeploy my app to fix a db issue. Storing queries as literal strings in your source increases maintenance complexity.
Have a look at this link
Does this code prevent SQL injection?
Remove the unwanted from the tableName string.
I do not think you can use param query for a table name.
There are some other posts which will help with the SQL injection, so I'll upvote those, but another thing to consider is how you will be handling permissions for this. If you're granting users db+owner or db_ddladmin roles so that they can truncate tables then simply avoiding standard SQL injection attacks isn't sufficient. A hacker can send in other table names which might be valid, but which you wouldn't want truncated.
If you're giving ALTER TABLE permissions to the users on the specific tables that you will allow to be truncated then you're in a bit better shape, but it's still more than I like to allow in a normal environment.
Usually TRUNCATE TABLE isn't used in normal day-to-day application use. It's used for ETL scenarios or during database maintenance. The only situation where I might imagine it would be used in a front-facing application would be if you allowed users to load a table which is specific for that user for loading purposes, but even then I would probably use a different solution.
Of course, without knowing the specifics around why you're using it, I can't categorically say that you should redesign, but if I got a request for this as a DBA I'd be asking the developer a lot of questions.
Use parameterized queries.
In this concrete example you need protection from SQL injection only if table name comes from external source.
Why would you ever allow this to happen?
If you are allowing some external entity (end user, other system, what?)
to name a table to be dropped, why won't you just give them admin rights.
If you are creating and removing tables to provide some functionality for end user,
don't let them provide names for database objects directly.
Apart from SQL injection, you'll have problems with name clashes etc.
Instead generate real table names yourself (e.g DYNTABLE_00001, DYNTABLE_00002, ...) and keep a table that connects them to the names provided by user.
Some notes on generating dynamic SQL for DDL operations:
In most RDBMS-s you'll have to use dynamic SQL and insert table names as text.
Be extra careful.
Use quoted identifiers ([] in MS SQL Server, "" in all ANSI compliant RDBMS).
This will make avoiding errors caused by invalid names easier.
Do it in stored procedures and check if all referenced objects are valid.
Do not do anything irreversible. E.g. don't drop tables automatically.
You can flag them to be dropped and e-mail your DBA.
She'll drop them after the backup.
Avoid it if you can. If you can't, do what you can to minimize rights to other
(non-dynamic) tables that normal users will have.
You could use SQLParameter to pass in tableName value. As far as I know and tested, SQLParameter takes care of all parameter checking and thus disables possibility of injection.
If you can't use parameterized queries (and you should) ... a simple replace of all instances of ' with '' should work.
string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName.Replace("'", "''"));

Linq To Sql: Exception "String must be exactly one character long"

Consider a SQL Server table defined with a varchar(1) NULL field. It's being used to store a gender character. Some rows have data, some not: either null or blank. Granted the blanks SHOULD be nulls, but consider that blank is a valid value here. I'd much prefer the value to be null.
ID Gender
1 'M'
4 'M'
3 ''
4 'F'
An exception is raised when running a Linq To Sql query where the value of someID is 3.
var emp = (from e in db.Employees
where e.ID == someID
select e);
Exception:
String must be exactly one character long.
Question: What is the cause of this exception? What can be done to prevent or eliminate this problem?
Check the Employee type that was created for you by the LINQ to SQL designer. Most likely the type for the Gender property is System.Char (which is the type that the LINQ to SQL designer uses for varchar(1)) and should be changed to a System.String to properly match your database schema.
The fact that the LINQ to SQL designer interprets a varchar(1) as a System.Char is foolish considering that this is valid T-SQL:
declare #foo varchar(1);
set #foo = '';
and this is invalid C#:
Char foo = '';
Since the type of the property that was generated is too restrictive you need to change it to be a System.String.
Note: You may want to consider adding some validation inside the setter of the property to throw an exception if the length of the string is greater than one.
Is it possible that the blank data like '' doesn't meet the current constraints of the table? E.g. perhaps the table doesn't permit empty strings (even though there is one in there).
Then, maybe LINQ is applying those constraints for you, or expecting those constraints to be met and complaining that they are not.
If this is what is going on, you might change the constraints/design of the table to allow blank values, or just update all the blank values to NULL (assuming NULLs are allowed)
This problem also occurs when Linq To SQL Designer tries to auto generate a results class, for holding stored procedure results. Copy the auto generated class into your own new class file (use same name) and make changes there. Each time you update your DataContext it will just be a case of deleting the auto generated class. Not an ideal solution but a workaround for 2008.

Categories