Why does CONVERT() allow me to use parameterized query in this case? - c#

During the development of an windows mobile application we used the following code:
const string sqlString2 =
#"INSERT INTO WorkOrderComment
(WOInstructionID,WorkOrderCommentID,ClientChange,Void,UserID,VoidTimestamp)
(SELECT WOInstructionID, WOInstructionID, 1, #VoidBit,#VoidUser ,#VoidTimeStamp
FROM WorkOrderInstruction
WHERE InstructionGroupQuestion = 0 AND InstructionGroupNumber = #insGroupNo AND WorkOrderID=#WoID)";
var sqlcom2 = new SqlCeCommand();
sqlcom2.CommandType = CommandType.Text;
sqlcom2.CommandText = sqlString2;
sqlcom2.Parameters.Add("#VoidBit", action);
sqlcom2.Parameters.Add("#VoidUser", DBNull.Value);
sqlcom2.Parameters.Add("#VoidTimeStamp", DBNull.Value);
sqlcom2.Parameters.Add("#WoID", workOrderID);
sqlcom2.Parameters.Add("#insGroupNo", instructionGroupNumber);
sqlcom2.ExecuteNonQuery();
When running this with I receive the following error on ExecuteNonQuery:
The conversion is not supported. [ Type to convert from (if known) = int, Type to convert to (if known) = uniqueidentifier ]
Even if I add sqlcom2.Parameters["#VoidUser"].DbType = DbType.Guid; I got the same error. After a while I found that this could be helped with using CONVERT.
I changed #VoidUser to CONVERT(uniqueidentifier,#VoidUser).
This resulted in the following error: A parameter is not allowed in this location. Ensure that the '#' sign is in a valid location or that parameters are valid at all in this SQL statement.
After a couple of hours of reading documentation and other posts on why I could not use variables at these places I just tried to put in CONVERT everywhere; changing #VoidBit to CONVERT(bit,#VoidBit) and #VoidTimeStamp to CONVERT(datetime,#VoidTimeStamp).
And for some reason it works.
The table is created with this command:
CREATE TABLE WorkOrderComment ( WorkOrderCommentID uniqueidentifier NOT NULL CONSTRAINT WorkOrderCommentPK Primary Key,
WOInstructionID uniqueidentifier NOT NULL,
WorkOrderID_Update uniqueidentifier NULL,
TextData NTEXT NULL,
Value float NULL,
Category nvarchar(50) NULL,
BarcodeScanned bit NULL,
Timestamp DateTime NULL,
Time float NULL,
UserID uniqueidentifier NULL,
Void bit NULL,
VoidTimeStamp DateTime NULL,
FaultComplaintID uniqueidentifier NULL,
ClientChange bit NOT NULL)
Why am I not allowed to use "naked" parameters?
Why did it throw an error on the conversion in the first place, and not the usage of a parameter?

The Parameters.Add() method takes 2 parameters: Parameter name and data type. It seems you are confusing with Parameters.AddWithValue()
Also SQLCE seems to be more difficult with type conversion than SQL Server. So the best is you specifically mention the type of the parameters. (it only really matters for uniqueidentifier though)
I would slightly rewrite the code like this as well:
using (var sqlConn = new SqlCeConnection(connStr))
using (var sqlCmd = new SqlCeCommand(sqlString2, sqlConn))
{
sqlConn.Open();
sqlCmd.Parameters.Add("#VoidBit", SqlDbType.Bit).Value = action;
sqlCmd.Parameters.Add("#VoidUser", SqlDbType.UniqueIdentifier).Value = DBNull.Value;
sqlCmd.Parameters.Add("#VoidTimeStamp", SqlDbType.DateTime).Value = DBNull.Value;
sqlCmd.Parameters.Add("#WoID", SqlDbType.UniqueIdentifier).Value = workOrderID;
sqlCmd.Parameters.Add("#insGroupNo", SqlDbType.Int).Value = instructionGroupNumber;
sqlCmd.ExecuteNonQuery();
}
See https://gist.github.com/1932722 for full repro script

Related

Implicit casting VARCHAR to VARBINARY is not allowed

I'm stuck with a problem. I'm developing a ASP.net MVC application that manages file uploads to a DB. Not that big of a deal. But every time I execute my SQL-Command, he tells me that I need to convert to VARBINARY first.
That problem is asked a lot here and on the Internet, but I still can't get it working..
That's what I got:
The SQL table:
DocID INT IDENTITY(1,1) NOT NULL,
DocName VARCHAR(512) NOT NULL,
DocData VARBINARY(max) NOT NULL,
ContentType NVARCHAR(100) NOT NULL,
ContentLength BIGINT NOT NULL,
InsertionDate DATETIME NOT NULL DEFAULT GETDATE(),
CONSTRAINT PK_DOC_STORE PRIMARY KEY NONCLUSTERED (DocID)
Read the file to a byte[] with BinaryReader.
var reader = new BinaryReader(file.InputStream);
var data = reader.ReadBytes(file.ContentLength);
And the INSERT INTO C# code:
sqlConnection.Open();
var sqlCommand = new SqlCommand
(
"INSERT INTO DocStore VALUES ('#DocumentName', '#DocumentData', '#DocumentType', '#DocumentSize', '#DocumentDate')"
, sqlConnection
);
sqlCommand.Parameters.AddWithValue("#DocumentName", file.FileName);
sqlCommand.Parameters.AddWithValue("#DocumentData", data);
sqlCommand.Parameters.AddWithValue("#DocumentType", file.ContentType);
sqlCommand.Parameters.AddWithValue("#DocumentSize", file.ContentLength);
sqlCommand.Parameters.AddWithValue("#DocumentDate", DateTime.Now);
var success = sqlCommand.ExecuteNonQuery();
sqlConnection.Close();
What's wrong here? I can't see the problem.. Shouldn't the byte[] work in a parameterized command string like this for the VARBINARY part?
You put quotes around parameter names, which will make them string literals.
Also, I would suggest to specify the columns in the insert statement. If you don't specify the columns to insert on, it takes the exact definition from your table (excluding the ID field since it is auto incremented). It is possible to break your query if you insert a field in between.
INSERT INTO DocStore (DocName, DocData, ContentType, ContentLength, InsertionDate)
VALUES (#DocumentName, #DocumentData, #DocumentType, #DocumentSize, #DocumentDate)
Solution
Instead of
var sqlCommand = new SqlCommand
(
"INSERT INTO DocStore VALUES ('#DocumentName', '#DocumentData', '#DocumentType', '#DocumentSize', '#DocumentDate')"
, sqlConnection
);
I would use
var sqlCommand = new SqlCommand
(
"INSERT INTO DocStore VALUES (#DocumentName, #DocumentData, #DocumentType, #DocumentSize, #DocumentDate)"
, sqlConnection
);
Why ?
Because "INSERT INTO ... '#DocumentData' ... " string contain a T-SQL statement. Within T-SQL, single quotes ('bla') are used to delimit the start and the end of string constant and also, in some cases, it can be used for column delimiters. So '#DocumentData' represents a string / VARCHAR constant from the point of view of SQL Server. In this case, it tries to do an implicit conversion of VARCHAR values ('#D...') to VARBINARY (data type of DocData colum; first column is skipped because it has IDENTITY property). But according to
between VARCHAR and VARBINARY are allowed only explicit conversions.
Note: as a best practice I would explicit define the list of target columns for INSERT statement.

Failed to convert parameter value from a String to a Int32 for DateTime

I have a C# form linked to an SQL database, and for one of the columns I wish to insert the current date each time a new row is added. I am baffled because at one point this code was doing exactly what I wanted, and then after changing a few things it now keeps throwing the error "Failed to convert parameter value from a String to a Int32." and I don't know what happened.
Here is my table definition. I know the value types are probably a bit arbitrary, but all I care about is that the date is stored as a string.
CREATE TABLE [dbo].[InvTable] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[Item] NVARCHAR (50) NULL,
[QuantRem] INT NULL,
[Type] NCHAR (10) NULL,
[DateOrd] NCHAR(10) NULL,
[QuantOrd] INT NULL,
[Received] BIT NULL,
[DateRec] NCHAR(10) NULL,
PRIMARY KEY CLUSTERED ([ID] ASC)
);
And the code on the form is below. The second date will be defined differently later, but my primary concern is getting rid of the error in the title.
private void Submitbutton1_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(textBox1.Text))
{
MessageBox.Show("Cannot Submit Empty Request");
return;
}
if (comboBox1.SelectedItem == null)
{
MessageBox.Show("Please Use Drop Down Menu To Select Item Category");
return;
}
SqlDataAdapter da = new SqlDataAdapter();
da.InsertCommand = new SqlCommand("INSERT INTO InvTable VALUES(#Item, #QuantRem, #Type, #DateReq, #QuantOrd, #Received, #DateRec)", con);
da.InsertCommand.Parameters.Add("#Item", SqlDbType.NChar).Value = textBox1.Text;
da.InsertCommand.Parameters.Add("#QuantRem", SqlDbType.Int).Value = textBox2.Text;
da.InsertCommand.Parameters.Add("#Type", SqlDbType.NChar).Value = comboBox1.Text;
da.InsertCommand.Parameters.Add("#DateReq", SqlDbType.NChar).Value = DateTime.Today.ToString("d");
da.InsertCommand.Parameters.Add("#QuantOrd", SqlDbType.Int).Value = 0;
da.InsertCommand.Parameters.Add("#Received", SqlDbType.Bit).Value = 0;
da.InsertCommand.Parameters.Add("#DateRec", SqlDbType.NChar).Value = DateTime.Today.ToString("d");
con.Open();
da.InsertCommand.ExecuteNonQuery();
da.SelectCommand = new SqlCommand("SELECT * FROM InvTable WHERE Received=0", con);
con.Close();
ds.Clear();
da.Fill(ds);
invTableDataGridView.DataSource = ds.Tables[0];
textBox1.Clear();
}
So this code used to work at one point, giving me a string "07/20/2015", but now it just gives me an error.
You are a victim of choosing the wrong data type.
Change your DateOrd and DateRec column types to date type and pass your DateTime.Today directly to your parameterized query (since you keep only date part of a DateTime).
[DateOrd] date NULL,
...
...
[DateRec] date NULL,
and
da.InsertCommand.Parameters.Add("#DateReq", SqlDbType.Date).Value = DateTime.Today;
da.InsertCommand.Parameters.Add("#DateRec", SqlDbType.Date).Value = DateTime.Today;
Also for QuantRem column, looks like you need to parse textBox2.Text value to int before you pass it as a parameter.
da.InsertCommand.Parameters.Add("#QuantRem", SqlDbType.Int).Value = int.Parse(textBox2.Text);
Don't forget to use using statement to dispose your connections, commands and adapters automatically instead of calling Close methods manually.
As others have said, don't use text when you could/should be using an actual date type ... it will save you a lot of hassle now and in the future.
But your actual error is more likely to be this line:
da.InsertCommand.Parameters.Add("#QuantRem", SqlDbType.Int).Value = textBox2.Text;
Where you are taking text and trying to set an integer parameter. Convert that text to an integer, and then set your integer parameter.

Select NULL values from SQL Server table

I am working with ASP.NET MVC 4 using C# and SQL Server
I am selecting a row of data from the following table
CREATE TABLE [dbo].[Mem_Basic] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Mem_NA] VARCHAR (100) NOT NULL,
[Mem_Occ] VARCHAR (200) NOT NULL,
[Mem_Role] VARCHAR (200) NOT NULL,
[Mem_Email] VARCHAR (50) NULL,
[Mem_MPh] VARCHAR (15) NULL,
[Mem_DOB] DATE NULL,
[Mem_BGr] NCHAR (10) NULL,
[Mem_WAnn] DATE NULL,
[Mem_Spouse] VARCHAR (75) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
using the following code
public MemberBasicData GetMemberProfile(int id)
{
MemberBasicData mb = new MemberBasicData();
using (SqlConnection con = new SqlConnection(Config.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Mem_Basic WHERE Id="+id+"", con))
{
try
{
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
if(reader.Read()==true)
{
mb.Id = (int)reader["Id"];
mb.Mem_NA = (string)reader["Mem_NA"];
mb.Mem_Occ = (string)reader["Mem_Occ"];
mb.Mem_Role = (string)reader["Mem_Role"];
mb.Mem_Email = (string)reader["Mem_Email"];
mb.Mem_MPh = (string)reader["Mem_MPh"];
mb.Mem_DOB = (Convert.ToDateTime(reader["Mem_DOB"]));
mb.Mem_BGr = (string)reader["Mem_BGr"];
mb.Mem_WAnn = (Convert.ToDateTime(reader["Mem_WAnn"]));
mb.Mem_Spouse = (string)reader["Mem_Spouse"];
}
}
catch (Exception e) { throw e; }
finally { if (con.State == System.Data.ConnectionState.Open) con.Close(); }
}
}
return mb;
}
This shows the error
Unable to cast object of type 'System.DBNull' to type 'System.String'.
(Mem_Email, MPh.. etc sometimes contain a NULL value.. if the value is null I want return null). Anybody please help me.
Just make some short if, you should do the same for all the other variables:
mb.Mem_Email = reader["Mem_Email"] == System.DBNull.Value ? null : (string) reader["Mem_Email"];
You could save yourself a serious amount of pain here with a tool like dapper (http://www.nuget.org/packages/Dapper):
public MemberBasicData GetMemberProfile(int id)
{
using (var con = new SqlConnection(Config.ConnectionString))
{
return con.Query<MemberBasicData>(
"SELECT * FROM Mem_Basic WHERE Id=#id",
new { id } // full parameterization, done the easy way
).FirstOrDefault();
}
}
things this does:
does correct parameterization (for both performance and safety), but without any inconvenience
does all the materialization, handling nulls (both in parameters and columns) for you
is insanely optimized (basically, it is measurably the same speed as writing all that code yourself, except fewer things to get wrong)
Alternatively to King King's answer you can write code like this:
mb.Mem_Email = reader["Mem_Email"] as string;
For value types, if the column allows nulls, it's a good practice to map them to nullable value types in C# so that this code reader["Mem_DOB"] as DateTime? works
Change for all columns, that might be NULL from this
mb.Mem_NA = (string)reader["Mem_NA"];
to that
mb.Mem_NA = reader["Mem_NA"].ToString();
Treat the nullable fields:
mb.Mem_Email = System.DBNull.Value.Equals(reader["Mem_Email"])?"":
(string)reader["Mem_Email"];
Do the same for:
mb.Mem_MPh, mb.Mem_BGr and mb.Mem_Spouse.
I don't mean to sound like a SQL bigot (which of course means I DO mean to sound like a SQL bigot), but if you followed SQL best practices and used a column list instead of SELECT * you could resolve this problem by using COALESCE on the nullable columns thus:
SELECT
[Id],
[Mem_NA],
[Mem_Occ],
[Mem_Role],
COALESCE( [Mem_Email], '' ) AS [Mem_Email],
COALESCE( [Mem_MPh], '' ) AS [Mem_MPh],
COALESCE( [Mem_DOB], CAST( '1753-1-1' AS DATE ) ) AS [Mem_DOB],
COALESCE( [Mem_BGr, '' ) AS [Mem_BGr],
COALESCE( [Mem_WAnn], CAST( '1753-1-1' AS DATE ) ) AS [Mem_WAnn],
COALESCE( [Mem_Spouse], '' ) AS [Mem_Spouse]
FROM
[dbo].[Mem_Basic];
Your c# code can now dependably process the result set without having to account for outliers (the exception being the dates; you should probably check for whatever default you use in the COALESCE for those (I used the minimum allowable value for a SQL Date variable in the above example), and handle them appropriately.
Additionally, you can get rid of the finally block in your c# code. You wrapped the connection in a "using" block; it will automatically close the connection when you go out of scope (that is the purpose of the "using" block).

Output variable not getting set in SQL Server stored procedure

I hope this is not one of those questions where I slap myself afterwards, but this is really confusing me. I have this working for another one of my stored procedures which is why this is so confusing. It's basically the same setup in both. Here's what's happening.
Here's an example of my stored procedure:
ALTER PROCEDURE [dbo].[CreateRecord]
-- Add the parameters for the stored procedure here
#Link1Id INT = NULL,
#Link2Id INT = NULL,
#Amount MONEY,
#Output int out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET #Output = 0
-- Insert statements for procedure here
IF #Link1Id = NULL
BEGIN
IF NOT EXISTS(SELECT * FROM dbo.Records WHERE Link2Id = #Link2Id)
INSERT INTO [dbo].[Records]
([Link1Id]
,[Link2Id])
VALUES
(#Link1Id
,#Link2Id)
SET #Output = (SELECT RecordId FROM dbo.Records WHERE Link2Id = #Link2Id)
END
ELSE
BEGIN
IF NOT EXISTS(SELECT * FROM dbo.Records WHERE Link1Id = #Link1Id)
INSERT INTO [dbo].[Records]
([Link1Id]
,[Link2Id])
VALUES
(#Link1Id
,#Link2Id)
SET #Output = (SELECT RecordId FROM dbo.Records WHERE Link1Id = #Link1Id)
END
END
Now, I have created a unit test that basically runs this procedure, and tries to Assert that the returned #Output is greater than 0, but the #Output parameter never has a value on the SqlCommand in the code. Here's some of the C# code:
private int ExecuteNonQueryWithOutput(string procedureName, SqlParameter[] parameters)
{
SqlCommand command = this.GenerateCommand(procedureName, parameters);
connection.Open();
command.ExecuteNonQuery();
int retval = (int)command.Parameters[OUTPUT].Value;
connection.Close();
return retval;
}
Now, I can step over the line that calls ExecuteNonQuery(), and verify in the database that the new (and correct) record is there, but then on the next line, it throws an exception when it calls (int)command.Parameters[OUTPUT].Value; as the Value is not there.
This is working perfectly for another procedure that I have which is setup in the same exact fashion. Do you know why it wouldn't be working here?
Thanks, I'm kind of stumped. I've debugged for a while now with no luck.
Edit:
Code that generates the parameters array:
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter { ParameterName = "#Link1Id", SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Input, Value = link1Val });
parameters.Add(new SqlParameter { ParameterName = "#Link2Id", SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Input, Value = link2Val });
parameters.Add(new SqlParameter { ParameterName = OUTPUT, SqlDbType = SqlDbType.Int, Direction = ParameterDirection.Output });
return this.ExecuteNonQueryWithOutput("CreateRecord", parameters.ToArray());
I don't see where you've declared #Output. Did you mean:
ALTER PROCEDURE [dbo].[CreateRecord]
-- Add the parameters for the stored procedure here
#Link1Id INT = NULL,
#Link2Id INT = NULL,
#Amount MONEY,
#Output INT = NULL OUTPUT
AS
Also I'm not 100% sure you have the syntax right for retrieving a named output parameter. But the parameter has to exist before you can reference it anyway. How did you save that stored procedure without declaring #Output?
There are numerous things wrong with the code that go beyond the output parameter issue.
To answer the actual question, you are likely passing a NULL value back as the Output. When it tries to convert this to an Int you are getting an error.
Also the sql line:
IF #Link1ID = null
Will ALWAYS fail. In SQL parlance, null is an indeterminate value, so (null != null). The way to test for null values is to use IS. For example:
IF (#Link1ID is null)
Which leads me to believe that you are actually getting a primary key violation in the sql code.
Now, onto the bigger issue. Your C# code is flawed. The command object is never disposed of and if there are any issues your connection object won't be disposed of either. This will lead to fun sql errors due running out of available sql connections..
It should look something like the following:
private int ExecuteNonQueryWithOutput(string procedureName, SqlParameter[] parameters)
{
int retval = 0;
using (SqlConnection conn = new SqlConnection("connection string here"))
using (SqlCommand command = this.GenerateCommand(procedureName, parameters)) {
connection.Open();
command.ExecuteNonQuery();
retval = (int)command.Parameters[OUTPUT].Value;
}
return retval;
}
Note that this declares, uses and disposes of your connection and command objects locally. If there is a problem this will make sure the resources are properly disposed of.
Also note that it does not use a global "connection" object. Connection pooling offered by the operating system is incredibly efficient at opening/closing connections as needed. Because of this the best practice is to instantiate and keep them around only long enough to deal with the current operation. The longer it's open the more likely you'll run into issues.

"There was an error parsing the query. SQL Server CE

I wrote an little app in C# to keep track of customers and jobs but I get an error
"There was an error parsing the query.[Token line number = 1, Token
line offset = 14,Token in error = ']
I'm using a SQL Server CE .sdf database
public static void CreateEmployeeTable(string name)
{
conn.Open();
using (SqlCeCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"CREATE TABLE `#Name` (
`Id` INT(10) NOT NULL AUTO_INCREMENT,
`Job` VARCHAR(50) NULL,
`Pay` VARCHAR(50) NULL,
`TotalJobs` VARCHAR(50) NULL,
`TotalPay` VARCHAR(50) NULL,
PRIMARY KEY (`Id`)";
cmd.Parameters.AddWithValue("#Name", name);
cmd.ExecuteNonQuery();
}
}
I researched the error a lot and seems this error is pretty vague
You cannot parameterise a CREATE table statement that way.
Simply construct the CommandText string in code using string concatenation (being careful of possible SQL Injection vulnerabilities) and execute it.
You cannot parameterize the table name in your example. I altered your create table SQL to conform to supported data types for SQL CE. Please note that the NVARCHAR statements are set to a length of 100 to support up to the 50 characters you were specifying (VARCHAR is not supported).
I tested this code sample, but there are a couple more things to think about:
Your method is currently public. I don't know the context in which you are calling this, but consider making it private or internal.
Consider querying first to determine whether the table already exists to avoid an exception.
If at all possible I would create the connection in a using block directly above your command creation. That way you will know the connection gets closed and disposed. I'll leave that to your discretion.
public static void CreateEmployeeTable(string name)
{
const string createTableFormat = #"CREATE TABLE [{0}] (
Id INT IDENTITY NOT NULL PRIMARY KEY,
Job NVARCHAR(100) NULL,
Pay NVARCHAR(100) NULL,
TotalJobs NVARCHAR(100) NULL,
TotalPay NVARCHAR(100) NULL)";
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name");
}
// Just replace with your connection string.
using (SqlCeConnection cn = new SqlCeConnection(Settings.Default.LocalDbConnectionString))
using (SqlCeCommand cmd = cn.CreateCommand())
{
cmd.CommandText = string.Format(createTableFormat, name);
cn.Open();
cmd.ExecuteNonQuery()
}
}

Categories

Resources