Related
I am trying to insert data into a SQL Server database by calling a stored procedure, but I am getting the error
Procedure or function 'SHOWuser' expects parameter '#userID', which was not supplied.
My stored procedure is called SHOWuser. I have checked it thoroughly and no parameters is missing.
My code is:
public void SHOWuser(string userName, string password, string emailAddress, List<int> preferences)
{
SqlConnection dbcon = new SqlConnection(conn);
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = dbcon;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "SHOWuser";
cmd.Parameters.AddWithValue("#userName", userName);
cmd.Parameters.AddWithValue("#password", password);
cmd.Parameters.AddWithValue("#emailAddress", emailAddress);
dbcon.Open();
int i = Convert.ToInt32(cmd.ExecuteScalar());
cmd.Parameters.Clear();
cmd.CommandText = "tbl_pref";
foreach (int preference in preferences)
{
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("#userID", Convert.ToInt32(i));
cmd.Parameters.AddWithValue("#preferenceID", Convert.ToInt32(preference));
cmd.ExecuteNonQuery();
}
}
catch (Exception)
{
throw;
}
finally
{
dbcon.Close();
}
and the stored procedure is:
ALTER PROCEDURE [dbo].[SHOWuser]
(
#userName varchar(50),
#password nvarchar(50),
#emailAddress nvarchar(50)
)
AS
BEGIN
INSERT INTO tbl_user(userName, password, emailAddress)
VALUES (#userName, #password, #emailAddress)
SELECT
tbl_user.userID, tbl_user.userName,
tbl_user.password, tbl_user.emailAddress,
STUFF((SELECT ',' + preferenceName
FROM tbl_pref_master
INNER JOIN tbl_preferences ON tbl_pref_master.preferenceID = tbl_preferences.preferenceID
WHERE tbl_preferences.userID = tbl_user.userID
FOR XML PATH ('')), 1, 1, ' ' ) AS Preferences
FROM
tbl_user
SELECT SCOPE_IDENTITY();
END
This is the second stored procedure tbl_pref which is used in the same function:
ALTER PROCEDURE [dbo].[tbl_pref]
#userID int,
#preferenceID int
AS
BEGIN
INSERT INTO tbl_preferences(userID, preferenceID)
VALUES (#userID, #preferenceID)
END
In my case I received this exception even when all parameter values were correctly supplied but the type of command was not specified :
cmd.CommandType = System.Data.CommandType.StoredProcedure;
This is obviously not the case in the question above, but exception description is not very clear in this case, so I decided to specify that.
I came across this issue yesterday, but none of the solutions here worked exactly, however they did point me in the right direction.
Our application is a workflow tool written in C# and, overly simplified, has several stored procedures on the database, as well as a table of metadata about each parameter used by each stored procedure (name, order, data type, size, etc), allowing us to create as many new stored procedures as we need without having to change the C#.
Analysis of the problem showed that our code was setting all the correct parameters on the SqlCommand object, however once it was executed, it threw the same error as the OP got.
Further analysis revealed that some parameters had a value of null. I therefore must draw the conclusion that SqlCommand objects ignore any SqlParameter object in their .Parameters collection with a value of null.
There are two solutions to this problem that I found.
In our stored procedures, give a default value to each parameter, so from #Parameter int to #Parameter int = NULL (or some other default value as required).
In our code that generates the individual SqlParameter objects, assigning DBNull.Value instead of null where the intended value is a SQL NULL does the trick.
The original coder has moved on and the code was originally written with Solution 1 in mind, and having weighed up the benefits of both, I think I'll stick with Solution 1. It's much easier to specify a default value for a specific stored procedure when writing it, rather than it always being NULL as defined in the code.
Hope that helps someone.
Your stored procedure expects 5 parameters as input
#userID int,
#userName varchar(50),
#password nvarchar(50),
#emailAddress nvarchar(50),
#preferenceName varchar(20)
So you should add all 5 parameters to this SP call:
cmd.CommandText = "SHOWuser";
cmd.Parameters.AddWithValue("#userID",userID);
cmd.Parameters.AddWithValue("#userName", userName);
cmd.Parameters.AddWithValue("#password", password);
cmd.Parameters.AddWithValue("#emailAddress", emailAddress);
cmd.Parameters.AddWithValue("#preferenceName", preferences);
dbcon.Open();
PS: It's not clear what these parameter are for. You don't use these parameters in your SP body so your SP should looks like:
ALTER PROCEDURE [dbo].[SHOWuser] AS BEGIN ..... END
In my case I got the error on output parameter even though I was setting it correctly on C# side I figured out I forgot to give a default value to output parameter on the stored procedure
ALTER PROCEDURE [dbo].[test]
(
#UserID int,
#ReturnValue int = 0 output --Previously I had #ReturnValue int output
)
In my case, It was returning one output parameter and was not Returning any value.
So changed it to
param.Direction = ParameterDirection.Output;
command.ExecuteScalar();
and then it was throwing size error. so had to set the size as well
SqlParameter param = new SqlParameter("#Name",SqlDbType.NVarChar);
param.Size = 10;
After researching, I realized that I did not specify
command.CommandType = System.Data.CommandType.StoredProcedure;
After adding this, my application ran properly.
in my case, I was passing all the parameters but one of the parameter my code was passing a null value for string.
Eg: cmd.Parameters.AddWithValue("#userName", userName);
in the above case, if the data type of userName is string, I was passing userName as null.
I have a procedure which updates some records. When I execute it I get the following exception
"String or binary data would be truncated.\r\nThe statement has been terminated."
I could found this occur when the parameter length is larger than variable's length. I checked again changing the size. But didn't work. Go the same exception again. How can I solve this? Please help
Here is my code for update
bool isFinished = dba.update(desingnation, title, initials, surname, fullname, callingName, civilSatatus, natinality, nic, birthday, passport,
hometp, mobiletp, province, district, division, electorate, gramaNiladhari, takafull, p_city,
c_city, p_hno, c_hno, tokens_P, tokens_C, previousEmployeements, bank, branch, type, account, gender, educatinalQ, languageE, languageS, languageT, empNo, appNo);
if (isFinished)
{
WebMsgBox.Show("Successfully Inserted!");
}
else
{
WebMsgBox.Show("Some Errors Occured");
}
}
else
{
WebMsgBox.Show("Some feilds are not valid");
}
}
}
This is the code for passing parameters to stored procedures
try
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = connection;
cmd.CommandTimeout = 0;
cmd.Transaction = transactions;
/*=======================Update employee details================================*/
cmd.CommandText = "update_HS_HR_EMPLOYEE_AADM";
cmd.Parameters.Add("#appNo", SqlDbType.Int).Value = appNo;
cmd.Parameters.Add("#CALLING_NAME", SqlDbType.VarChar).Value = callingName;
cmd.Parameters.Add("#INITIALS", SqlDbType.VarChar).Value = initials;
cmd.Parameters.Add("#SURNAME", SqlDbType.VarChar).Value = surname;
cmd.Parameters.Add("#TITLE", SqlDbType.VarChar).Value = title;
cmd.Parameters.Add("#NAME", SqlDbType.VarChar).Value = fullname;
cmd.Parameters.Add("#FULLNAME", SqlDbType.VarChar).Value = fullname + " " + surname;
cmd.Parameters.Add("#NIC", SqlDbType.VarChar).Value = nic;
cmd.Parameters.Add("#BDY", SqlDbType.VarChar).Value = birthday;
cmd.Parameters.Add("#GENDER", SqlDbType.VarChar).Value = gender;
cmd.Parameters.Add("#NATIONALITY", SqlDbType.VarChar).Value = natinality;
cmd.Parameters.Add("#CIVILSTATUS", SqlDbType.VarChar).Value = civilSatatus;
cmd.Parameters.Add("#DESIGNATION", SqlDbType.VarChar).Value = desingnation;
cmd.Parameters.Add("#P_ADD1", SqlDbType.VarChar).Value = p_hno;
cmd.Parameters.Add("#P_ADD2", SqlDbType.VarChar).Value = tokens_P[0];
if (tokens_P.Length > 1)
cmd.Parameters.Add("#P_ADD3", SqlDbType.VarChar).Value = tokens_P[1];
else
cmd.Parameters.Add("#P_ADD3", SqlDbType.VarChar).Value = "";
cmd.Parameters.Add("#P_CITY", SqlDbType.VarChar).Value = p_city;
cmd.Parameters.Add("#TP_HOME", SqlDbType.VarChar).Value = hometp;
cmd.Parameters.Add("#TP_MOBILE", SqlDbType.VarChar).Value = mobiletp;
cmd.Parameters.Add("#PROVINCE", SqlDbType.VarChar).Value = province;
cmd.Parameters.Add("#DISTRICT", SqlDbType.VarChar).Value = district;
cmd.Parameters.Add("#C_ADD1", SqlDbType.VarChar).Value = c_hno;
cmd.Parameters.Add("#C_ADD2", SqlDbType.VarChar).Value = tokens_C[0];
cmd.Parameters.Add("#PER_GNDIV_CODE", SqlDbType.VarChar).Value = gramaNiladhari;
cmd.Parameters.Add("#PER_DSDIV_CODE", SqlDbType.VarChar).Value = division;
cmd.Parameters.Add("#TAKAFUL", SqlDbType.VarChar).Value = takafull;
cmd.Parameters.Add("#PASSPORT_NO", SqlDbType.VarChar).Value = passport;
if (tokens_C.Length > 1)
cmd.Parameters.Add("#C_ADD3", SqlDbType.VarChar).Value = tokens_C[1];
else
cmd.Parameters.Add("#C_ADD3", SqlDbType.VarChar).Value = "";
cmd.Parameters.Add("#C_CITY", SqlDbType.VarChar).Value = c_city;
cmd.Parameters.Add("#ELECTORATE", SqlDbType.VarChar).Value = electorate;
//int appNO = int.Parse((cmd.ExecuteScalar().ToString()));
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
}
}
This is the stored procedure
ALTER PROCEDURE [dbo].[update_HS_HR_EMPLOYEE_AADM]
#appNo Int,
#CALLING_NAME VARCHAR(50),
#INITIALS VARCHAR(50),
#SURNAME VARCHAR(50),
#TITLE VARCHAR(50),
#NAME VARCHAR(50),
#FULLNAME VARCHAR(100),
#NIC VARCHAR(15),
#BDY VARCHAR(50),
#GENDER CHAR(1),
#NATIONALITY VARCHAR(50),
#CIVILSTATUS VARCHAR(50),
#DESIGNATION VARCHAR(50),
#P_ADD1 VARCHAR(50),
#P_ADD2 VARCHAR(50),
#P_ADD3 VARCHAR(50),
#P_CITY VARCHAR(50),
#TP_HOME VARCHAR(50),
#TP_MOBILE VARCHAR(50),
#PROVINCE VARCHAR(50),
#DISTRICT VARCHAR(50),
#C_ADD1 VARCHAR(50),
#C_ADD2 VARCHAR(50),
#C_ADD3 VARCHAR(50),
#C_CITY VARCHAR(50),
#ELECTORATE VARCHAR(50),
#PER_GNDIV_CODE VARCHAR(50),
#PER_DSDIV_CODE VARCHAR(50),
#TAKAFUL VARCHAR(50),
#PASSPORT_NO VARCHAR(50)
AS
BEGIN
update [HS_HR_EMPLOYEE_AADM]
SET
[EMP_CALLING_NAME]=#CALLING_NAME
,[EMP_MIDDLE_INI]=#INITIALS
,[EMP_SURNAME]=#SURNAME
,[EMP_TITLE]=#TITLE
,[EMP_NAMES_BY_INI]=#NAME
,[EMP_FULLNAME]=#FULLNAME
,[EMP_NIC_NO]=#NIC
,[EMP_BIRTHDAY]=#BDY
,[EMP_GENDER]=#GENDER
,[NAT_CODE]=#NATIONALITY
,[EMP_MARITAL_STATUS]=#CIVILSTATUS
,[EMP_DATE_JOINED]=GETDATE()
,[EMP_CONFIRM_FLG]=0
,[CT_CODE]='000008'
,[DSG_CODE]=#DESIGNATION
,[CAT_CODE]='000001'
,[EMP_PER_ADDRESS1]=#P_ADD1
,[EMP_PER_ADDRESS2]=#P_ADD2
,[EMP_PER_ADDRESS3]=#P_ADD3
,[EMP_PER_CITY]=#P_CITY
,[EMP_PER_TELEPHONE]=#TP_HOME
,[EMP_PER_MOBILE]=#TP_MOBILE
,[EMP_PER_PROVINCE_CODE]=#PROVINCE
,[EMP_PER_DISTRICT_CODE]=#DISTRICT
,[EMP_TEM_ADDRESS1]=#C_ADD1
,[EMP_TEM_ADDRESS2]=#C_ADD2
,[EMP_PER_ELECTORATE_CODE]=#ELECTORATE
,[EMP_TEM_ADDRESS3]=#C_ADD3
,[EMP_TEM_CITY]=#C_CITY
,[EMP_PER_GNDIV_CODE]=#PER_GNDIV_CODE
,[EMP_PER_DSDIV_CODE]=#PER_DSDIV_CODE
,[EMP_PASSPORT_NO]=#TAKAFUL
,[EMP_TAK]=#PASSPORT_NO
where App_no = #appNo
END
Specify varchar size in SqlDBType.Varchar in C# code matching the size as specified in stored procedure eg.
cmd.Parameters.Add("#CALLING_NAME", SqlDbType.VarChar, 50).Value = callingName;
corresponding to parameter #CALLING_NAME VARCHAR(50) in stored procdeure.
This ensures that size is not exceeded when being passed to stored procedure.
If length is not specified for string parameter , ADO.NET picks up arbitary length value which may exceed the size specified specified in stored procedures VARCHAR parameters.
Also at front end ensure that the number of characters being entered in textboxes doesnot exceed corresponding parameters size.
This can be done using MaxLength attribute or prompting user with message using JQuery/Javascript if size exceeds.
Do it for other parameters and check.
The specified error, "String or binary data would be truncated.\r\nThe statement has been terminated." is showing when you are trying to insert a value that is higher than the specified size of the column, When we look into the given procedure we can't identify the sizes of each column, So it would better if you cross check the sizes of columns with the values that you are giving.
I can say #GENDER may cause a similar issue, since it is defined as #GENDER CHAR(1), in the procedure but you are taking a string to the method and passing as SqlDbType.VarChar instead for that you have to give the value as char. for this particular field
The String or binary data would be truncated error is telling you that you are losing data. One of the annoying things about this error is that it doesn't tell you which column(s) the problem relates to, and in a scenario like this (with lots of columns), it makes it hard to diagnose.
If you have a suitable version of SQL Server (see the following hyperlinked page), you can turn on Trace Flag 460 (this may require a restart) to tell you exactly which table and column the problem relates to.
If not, here's my more manual approach... after which there is some information about how your parameters can be silently truncated without this error (which is not good).
Notice that for each column, there is a value in a C# variable, a declared parameter type (in both the C# code and the stored proc) and the size of the column in the table (the definition of which is missing from the question - which may explain why there isn't an accepted answer yet). All of these maximum lengths and types need to tie up, for all of the columns. You really need to check all of them; but we all like shortcuts, so...
My tip for finding which column(s) are having the problem is to find a scenario where it occurs so that you can easily repeat it - this is particularly easy to do it you have a unit test of this method. Now modify the stored proc to comment out half of the columns, and try again.
If it works, then you know the uncommented columns are fine (for this particular set of data), and the problem was in one of the columns that was commented out, so uncomment half of the lines, and try again.
If it didn't work, then the problem is with the uncommented columns, so comment out half of the remaining columns and try again.
Repeat until you've worked out which columns have problems. I say 'columns', because although it may only be one column having this problem, there could be more than that.
Now put everything back as it was when you started.
Now that you've worked out which column(s) have problems check each column's definition in the table against the stored proc parameter definition, the C# parameter definition, and the value in the C# variable. You may need to track this all the way back to where the value was entered in the user interface, and ensure that suitable restrictions are in place to prevent the value being too big.
As a bonus tip, I like having unit tests that my parameter sizes correspond with the type and size of the column that they relate to. I also have constants representing the max length of each string field and the maximum value of numeric fields. These constants are unit tested against the column in the database, and are used when restricting the values given by the user in the user interface. They can also be used by the unit test of that method, to prove that inserting the largest possible value for each column actually works.
However, note that it is worth making your varchar, nvarchar and varbinary parameters larger than your column sizes due to the silent truncation that occurs with parameter coercion:
SQL server will silently coerce your values to be whatever type the parameter is. For example...
DECLARE #Varchar VARCHAR(8) = 'I will be truncated';
DECLARE #Decimal92 DECIMAL(9,2) = 123.456;
DECLARE #Int INT = 123.456;
SELECT #Varchar, #Decimal92, #Int;
will output...
I will b 123.46 123
This may come as a surprise, given that SQL will complain about something like this:
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL);
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (N'I will be truncated');
ROLLBACK TRANSACTION;
by saying String or binary data would be truncated. And yet the following code does not complain, silently coerces the value and inserts the record:
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL);
DECLARE #MyColumn NVARCHAR(5)=N'I will be truncated'
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (#MyColumn);
ROLLBACK TRANSACTION;
So if you want to be aware of truncation issues occurring, you need to be sure that your parameter has a larger capacity than the column it is going into. For example, if we just change one character...
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL);
DECLARE #MyColumn NVARCHAR(6)=N'I will be truncated'
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (#MyColumn);
ROLLBACK TRANSACTION;
...will give the truncation error. However, notice that this isn't a complete solution because if I try it with a different value...
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL);
DECLARE #MyColumn NVARCHAR(6)=N'Never complain'
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (#MyColumn);
ROLLBACK TRANSACTION;
Then it will silently coerce, truncating after the space, then (because it's a varchar) the value has trailing spaces removed, and it inserts without complaining.
So the only way to be sure is to make your parameter is several characters larger than it needs to be, because there probably won't be multiple spaces in a row. You could use VARCHAR(MAX) for everything, but there is some concern that this could have performance impacts.
One place where this is particularly important is encrypted values. If encrypted values are truncated, then you can't decrypt them. So you need to be sure that your VARBINARY parameters are sized larger than the relevant column so that you would get errors instead of inserting truncated values. In this case, I believe a single character larger is sufficient, since there is no trimming of VARBINARYs. Well, apparently VarBinarys will trim trailing "nul" (ASCII=0) characters from the end; but only if ANSI_PADDING is set to OFF, but as Microsoft say, it should always be set to "ON". This section also covers what trimming occurs with different field types with different settings.
It's also worth saying that SQL isn't even consistent with how it does this. If we re-try the original example with a DECIMAL...
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL);
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (123.456);
SELECT * FROM tbl_Test
ROLLBACK TRANSACTION;
It doesn't complain about the value having too many decimal places, it just silently coerces it. And the same is true if I do it through a parameter which has more decimal places than the column...
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL);
DECLARE #MyColumn DECIMAL(9,3)=123.456
SELECT #MyColumn
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (#MyColumn);
SELECT * FROM tbl_Test
ROLLBACK TRANSACTION;
And yet if I give it a value that is too large...
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn DECIMAL(9,2) NOT NULL);
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (1234567890.456);
SELECT * FROM tbl_Test
ROLLBACK TRANSACTION;
Then it will complain with Arithmetic overflow error converting numeric to data type numeric. (as it would if I put that value into a parameter first).
One other thing to mention relates to parameter coercion and encryption. Imagine a scenario where you have an SQL column typed DECIMAL(9,2) and a parameter of the same type, and you are giving it a dot net "decimal" from your C# code. If the "decimal" in your code has lots of decimal places, this silent coercion will effectively be asking SQL to do the rounding for you. Which is fine... Until you decide to encrypt that column, because now the value you are encrypting would be a much longer value than the SQL DECIMAL column would have been able to hold, so is probably larger than you have allowed (in terms of your VARBINARY length). In this scenario you would need to ensure the value was rounded to the correct number of decimal places before encryption.
On the trimming trailing space of the parameter. It is only trimming as much as it needs... this shows that it has trimmed one space from the parameter, but left the remaining 4 after the N.
BEGIN TRANSACTION;
CREATE TABLE tbl_Test(MyColumn NVARCHAR(5) NOT NULL);
DECLARE #MyColumn NVARCHAR(6)=N'N complain'
SELECT #MyColumn +'|',LEN(#MyColumn),DATALENGTH(#MyColumn)
INSERT INTO dbo.tbl_Test (MyColumn) VALUES (#MyColumn);
SELECT MyColumn +'|',LEN(MyColumn),DATALENGTH(MyColumn) FROM dbo.tbl_Test
ROLLBACK TRANSACTION;
Yet another learning point about this... the same concept applies to user defined table types which correlate to table definitions.
Here is an example script to demonstrate the issue. Notice that the creation and dropping of the type has to be done outside the transaction.
CREATE TYPE dbo.MyTableType AS TABLE (MyColumn NVARCHAR(5) NOT NULL);
GO
BEGIN TRANSACTION;
DECLARE #MyColumn NVARCHAR(5)=N'I will be truncated'
DECLARE #MyTable AS dbo.MyTableType;
INSERT INTO #MyTable (MyColumn) VALUES (#MyColumn);
CREATE TABLE dbo.tbl_Test (MyColumn NVARCHAR(5) NOT NULL);
INSERT INTO dbo.tbl_Test SELECT MyColumn FROM #MyTable;
ROLLBACK TRANSACTION;
GO
DROP TYPE dbo.MyTableType;
I have few suggestions to identify this: (Choose any 1, and then move to other, if issue still exists. Order doesn't matter, which ever Suggestion, you fell easy).
Suggestion 1: Open the sql profiler and track the requests. Get the query from profiler and run it directly on SQL Server. you may get more details of error.
Steps:
Start debugging, and stop just before you are going to call the db.
Open Profiler, connect to DB.
Click on Clear Trace Window.
Start the debugging.
Stop the profiler, as soon as you got few requests.
Identify the Query, and run on SQL Server.
Suggestion 2: Try to Insert data using SQL Server management studio.
Steps:
Right click on table -> Click on Script Table as -> Insert To.
Now, compare with your Input, if you are passing correctly.
Suggestion 3: Use this code to get the difference between data length and data passed or directly truncate when passing to DB.
Steps:
Create this Class DbContextExtension.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Data.Context
{
public class DbContextExtension
{
private Dictionary<IProperty, int> _maxLengthMetadataCache;
public void AutoTruncateStringToMaxLength(DbContext db)
{
var entries = db?.ChangeTracker?.Entries();
if (entries == null)
{
return;
}
var maxLengthMetadata = PopulateMaxLengthMetadataCache(db);
foreach (var entry in entries)
{
var propertyValues = entry.CurrentValues.Properties.Where(p => p.ClrType == typeof(string));
foreach (var prop in propertyValues)
{
if (entry.CurrentValues[prop.Name] != null)
{
var stringValue = entry.CurrentValues[prop.Name].ToString();
if (maxLengthMetadata.ContainsKey(prop))
{
var maxLength = maxLengthMetadata[prop];
stringValue = TruncateString(stringValue, maxLength);
}
entry.CurrentValues[prop.Name] = stringValue;
}
}
}
}
private Dictionary<IProperty, int> PopulateMaxLengthMetadataCache(DbContext db)
{
_maxLengthMetadataCache ??= new Dictionary<IProperty, int>();
var entities = db.Model.GetEntityTypes();
foreach (var entityType in entities)
{
foreach (var property in entityType.GetProperties())
{
var annotation = property.GetAnnotations().FirstOrDefault(a => a.Name == "MaxLength");
if (annotation != null)
{
var maxLength = Convert.ToInt32(annotation.Value);
if (maxLength > 0 && !_maxLengthMetadataCache.ContainsKey(property))
{
_maxLengthMetadataCache[property] = maxLength;
}
}
}
}
return _maxLengthMetadataCache;
}
private static string TruncateString(string value, int maxLength)
{
if (string.IsNullOrEmpty(value)) return value;
return value.Length <= maxLength ? value : value.Substring(0, maxLength);
}
}
}
Use it like this, before calling your SaveChanges:
public class DocumentRepository : IDocumentRepository
{
private readonly DbContext _context;
public DocumentRepository(DbContext context)
{
_context = context;
}
public async Task CreateDocument(Document obj)
{
//Feel free to make it extension method or use it as DI. To make the example easier, I am creating the object here.
var dbExtensions = new DbContextExtension();
dbExtensions.AutoTruncateStringToMaxLength(_context);
await _context.Documents.AddAsync(obj);
await _context.SaveChangesAsync();
}
I need help, I have a problem while inserting a statement in SQL.
I call a SQL statement from my ASP.NET program, some variables contain quotes so when the insert is fired I have an exception like:
Exception Details: System.Data.SqlClient.SqlException: Incorrect syntax near 'xxxxx'.
Unclosed quotation mark after the character string ''.
I don't want the content of my variable to be changed...
Any idea how to handle this?
The C# part :
SqlCommand cmdInsertAssessment = new SqlCommand("xxxxxxx", sqlCnx);
cmdInsertAssessment.CommandType = CommandType.StoredProcedure;
cmdInsertAssessment.Parameters.AddWithValue("#templateID", templateID);
cmdInsertAssessment.Parameters.AddWithValue("#companyID", companyID);
cmdInsertAssessment.Parameters.AddWithValue("#userID",userID);
cmdInsertAssessment.Parameters.AddWithValue("#opn",opn);
cmdInsertAssessment.Parameters.AddWithValue("#mn",Mm);
cmdInsertAssessment.Parameters.AddWithValue("#max",max);
cmdInsertAssessment.Parameters.AddWithValue("#remarque",remarque);
cmdInsertAssessment.Parameters.AddWithValue("#templateTheme",templateTheme);
cmdInsertAssessment.Parameters.AddWithValue("#name", sName);
cmdInsertAssessment.Parameters.AddWithValue("#finished", iFinished);
cmdInsertAssessment.Parameters.AddWithValue("#datenow", dtNow);
try
{
cmdInsertAssessment.ExecuteNonQuery();
}
catch (Exception e)
{
}
SQL part :
CREATE PROCEDURE ["xxxxxxx"] #templateID int,
#companyID int,
#userID int,
#opn nvarchar(255),
#mn nvarchar(255),
#max int,
#remarque nvarchar(255),
#templateTheme nvarchar(255),
#name nvarchar(255),
#finished int,
#datenow datetime
AS
BEGIN
DECLARE
#points AS FLOAT
SET #points=0
IF(#mn='M')
BEGIN
IF(#opn='O')
BEGIN
SET #points=10
END
IF(#opn='P')
BEGIN
SET #points=2
END
END
IF(#mn!='M')
BEGIN
IF(#opn='O')
BEGIN
SET #points=2
END
if(#opn='P')
BEGIN
SET #points=1
END
END
IF(#remarque=NULL)
BEGIN
SET #remarque='nothing'
END
MERGE INTO [dbo].[Assessment] as target
USING (SELECT #templateID,#companyID,#userID,#opn,#points,#max,#remarque,#templateTheme,#datenow,#name,#finished)
As source (_templateID,_companyID,_userID,_opn,_points,_max,_remarque,_templateTheme,_datenow,_name,_finished)
ON target.TemplateID=source._templateID
AND target.TemplateTheme=source._templateTheme
AND target.NameAssessment=source._name
WHEN MATCHED THEN
UPDATE SET Points = source._points, Remarque = source._remarque, FillDate= source._datenow, Finished = source._finished, OPN = source._opn
WHEN NOT MATCHED THEN
INSERT (TemplateID, CompanyID, UserID, OPN, Points, Max, Remarque, TemplateTheme, FillDate, NameAssessment,Finished)
VALUES (source._templateID,source._companyID,source._userID,source._opn,source._points,source._max,source._remarque,source._templateTheme,source._datenow,source._name,source._finished);
END
GO
Thanks :)
Taking things from the begining ! Your procedure calculates a number of points, based on parameters you supply (#mn, #opn), then inserts or updates table Assessment. The first thing to say is that this is not a job for Merge. Merge is intended to operate on two tables, and using it for a row and a table is using a sledgehammer to crack a nut. You should really use
IF EXISTS( SELECT ID FROM ASSESSMENT WHERE... )
then write a classic insert and a classic update. Your procedure will be easier to understand, easier to maintain, and likely run much faster.
If you're still reading, I'll keep going. The calculation of points, business logic that uses nothing from the DB, will be much happier in the C#. Wherever you put it, you can use ternary operators to shorten those either-or choices. The following replaces 20 lines in your procedure.
var points = (mn == 'm')?(opn == 'O'?10:2):(opn == 'O'?2:1);
The assignment starting IF( #remarque = null ) can be done with a null coalescing operator ISNULL() in sql, ?? in C#.
And if you're still still reading, grab QueryFirst. You'll get a v.clean separation between SQL and C# and all your parameter creation will be done for you.
Because you said you wanted to use stored procedures
using (SqlConnection cnn = new SqlConnection(/*Connection String*/))
{
using (SqlCommand cmd = new SqlCommand("MyStoredProcedure", cnn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#param1", "Value 1");
cmd.Parameters.AddWithValue("#param2", "xxxxxx");
cnn.Open();
}
}
I have a SQL table where column is set to null. Now I am passing the null value from code via stored procedure and it throws an error which is, Procedure or function 'MyStoredProcedure' expects parameter '#Parameter', which was not supplied.
Now Confusing part for me is, if I add null condition and than pass DBNull.Value, it works fine but if I pass direct parameter which could be null than it throws error. I am trying to understand why? Am I missing something here?
Note: Just for information, passing parameter could be null based on the requirement.
Code:
string connString = _dbContext.Database.Connection.ConnectionString;
using (SqlConnection connection = new SqlConnection(connString))
{
connection.Open();
SqlCommand cmd = new SqlCommand("MyStoredProcedure", connection);
cmd.CommandType = CommandType.StoredProcedure;
scheduledEndDateTime = !string.IsNullOrEmpty(bulkUpdateSchedule.EndDate) && !string.IsNullOrEmpty(bulkUpdateSchedule.EndTime)
? (DateTime?)
Convert.ToDateTime(bulkUpdateSchedule.EndDate + " " + bulkUpdateSchedule.EndTime)
: null;
//This Fails
cmd.Parameters.AddWithValue("#scheduledEndDateTime", scheduledEndDateTime);
//This Works
if (scheduledEndDateTime != null)
{
cmd.Parameters.AddWithValue("#scheduledEndDateTime", scheduledEndDateTime);
}
else
{
cmd.Parameters.AddWithValue("#scheduledEndDateTime", DBNull.Value);
}
cmd.ExecuteReader();
connection.Close();
}
Stored Procedure:
ALTER PROCEDURE [dbo].[InsertScheduledBulkUpdateRecords]
#scheduledEndDateTime DATETIME,
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO ScheduledBulkUpdate (ScheduledEndDateTime)
VALUES (#scheduledEndDateTime)
END
Table Structure screen shot:
You've pretty much answered your own question null and DBNull.Value are different things. Where DBNull.Value means something to the database provider than null doesn't.
From MSDN on DBNull:
Do not confuse the notion of null in an object-oriented programming
language with a DBNull object. In an object-oriented programming
language, null means the absence of a reference to an object. DBNull
represents an uninitialized variant or nonexistent database column.
I know that in Oracle I can get the generated id (or any other column) from an inserted row as an output parameter.
Ex:
insert into foo values('foo','bar') returning id into :myOutputParameter
Is there a way to do the same, but using ExecuteScalar instead of ExecuteNonQuery?
I don't want to use output parameters or stored procedures.
ps: I'm using Oracle, not sql server!!!
If you are on oracle, you have to use ExecuteNonQuery and ResultParameter. There is no way to write this as query.
using (OracleCommand cmd = con.CreateCommand()) {
cmd.CommandText = "insert into foo values('foo','bar') returning id into :myOutputParameter";
cmd.Parameters.Add(new OracleParameter("myOutputParameter", OracleDbType.Decimal), ParameterDirection.ReturnValue);
cmd.ExecuteNonQuery(); // an INSERT is always a Non Query
return Convert.ToDecimal(cmd.Parameters["myOutputParameter"].Value);
}
Oracle uses sequences as for his identity columns, if we may say so.
If you have set a sequence for your table primary key, you also have to write a trigger that will insert the Sequence.NextValue or so into your primary key field.
Assuming that you are already familiar with this concept, simply query your sequence, then you will get your answer. What is very practiced in Oracle is to make yourself a function which will return an INT, then within your function, you perform your INSERT. Assuming that you have setup your trigger correctly, you will then be able to return the value of your sequence by querying it.
Here's an instance:
CREATE TABLE my_table (
id_my_table INT PRIMARY KEY
description VARCHAR2(100) NOT NULL
)
CREATE SEQUENCE my_table_seq
MINVALUE 1
MAXVALUE 1000
START WITH 1
INCREMENT BY 2
CACHE 5;
If you want to manage the auto-increment yourself, here's how:
INSERT INTO my_table (
id_my_table,
description
) VALUES (my_table_seq.NEXTVAL, "Some description");
COMMIT;
On the other hand, if you wish not to care about the PRIMARY KEY increment, you may proceed with a trigger.
CREATE OR REPLACE TRIGGER my_table_insert_trg
BEFORE INSERT ON my_table FOR EACH ROW
BEGIN
SELECT my_table_seq.NEXTVAL INTO :NEW.id_my_table FROM DUAL;
END;
Then, when you're inserting, you simply type the INSERT statement as follows:
INSERT INTO my_table (description) VALUES ("Some other description");
COMMIT;
After an INSERT, I guess you'll want to
SELECT my_table_seq.CURRVAL
or something like this to select the actual value of your sequence.
Here are some links to help:
http://www.orafaq.com/wiki/Sequence
http://www.orafaq.com/wiki/AutoNumber_and_Identity_columns
Hope this helps!
You can use below code.
using (OracleCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"INSERT INTO my_table(name, address)
VALUES ('Girish','Gurgaon India')
RETURNING my_id INTO :my_id_param";
OracleParameter outputParameter = new OracleParameter("my_id_param", OracleDbType.Decimal);
outputParameter.Direction = ParameterDirection.Output;
cmd.Parameters.Add(outputParameter);
cmd.ExecuteNonQuery();
return Convert.ToDecimal(outputParameter.Value);
}
one possible way if one can add one column named "guid" to the table :
when inserting one record from c#, generate a guid and write it to the guid column.
then perform a select with the generated guid, and you have got the id of inserted record :)
Select t.userid_pk From Crm_User_Info T
Where T.Rowid = (select max(t.rowid) from crm_user_info t)
this will return your required id