How to pass DataTable as Paramter tp stored procedure in C#? - c#

I have created User-Defined Table Types and have written stored procedure as below:
//Create the data type
CREATE TYPE [dbo].tbl_admintype AS TABLE
(
[code] [varchar](50) NULL,
[name] [varchar](100) NULL,
[branch] [varchar](100) NULL default '',
[location] [varchar](100) NULL default '',
[usertype] [varchar](50) NULL,
[password] [varchar](max) NULL,
[saltkey] [varchar](100) NULL
)
GO
//Stored Procedure
create PROCEDURE [dbo].[proc_tbl_admin_InsertItem]
#tbl_admintype tbl_admintype READONLY
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
MERGE INTO tbl_admin a
USING #tbl_admintype at
ON a.code=at.code and a.usertype=at.usertype
--WHEN MATCHED THEN
--UPDATE SET a.Name = at.Name,a.Country = at.Country
WHEN NOT MATCHED THEN
INSERT VALUES(at.name, at.code, at.password, at.saltkey,at.branch,at.location,at.usertype,1,getdate(),getdate());
select ''
END
The following piece of code works fine:
using (SqlConnection con1 = new SqlConnection(connectionstring))
{
using (SqlCommand cmd1 = new SqlCommand("proc_tbl_admin_InsertItem"))
{
cmd1.CommandType = CommandType.StoredProcedure;
cmd1.Connection = con1;
cmd1.Parameters.AddWithValue("#tbl_admintype", dt);
con1.Open();
cmd1.ExecuteNonQuery();
con1.Close();
}
}
But when I used the following function:
public int ExecuteNonQuery(string spName, params object[] parameterValues)
{
try
{
return db.ExecuteNonQuery(spName, parameterValues);
}
catch (DALException ex)
{
throw ex;
}
}
it gives the error:
"The incoming tabular data stream (TDS) remote procedure call (RPC)
protocol stream is incorrect. Table-valued parameter 1
("#tbl_admintype"), row 0, column 0: Data type 0xF3 (user-defined
table type) has a non-zero length database name specified. Database
name is not allowed with a table-valued parameter, only schema name
and type name are valid.".

Don't use AddWithValue. Instead, do this:
cmd1.Parameters.Add("#tbl_admintype", SqlDbType.Structured).Value = dt;
If you insist on using AddWithValue you can do it this way:
SqlParameter tvpParam = cmb1.Parameters.AddWithValue(
"#tbl_admintype", dt);
tvpParam.SqlDbType = SqlDbType.Structured;
This can be done since both the Add method and the AddWithValue method returns a reference to the parameter.
For more information, read this MSDN page.

You should create User Defined Table Type at SQL Server to send DataTable from C# to stored procedure.
Just open in SSMS:
Programmability -> Types -> UserDefined Table Types and right click New -> UserDefinedTableType
Then:
USE [YourDatabase]
GO
/****** Object: UserDefinedTableType [dbo].[tp_ParameterList]
Script Date: 18.10.2017 10:36:40 ******/
CREATE TYPE [dbo].[tp_ParameterList] AS TABLE(
[Name] [VARCHAR](255) NULL,
[Val] [VARCHAR](255) NULL
)
GO
Now you can execute your stored procedure with parameter which can accept DataTable:
ALTER PROCEDURE [dbo].[YourSPWithDataTable](
#AdditionalParams dbo.tp_ParameterList READONLY
)
AS
and you can call stored procedure from C# like that:
var cmd = new SqlCommand("YourSPWithDataTable", db.Database.Connection as SqlConnection,
db.Database.CurrentTransaction.UnderlyingTransaction as SqlTransaction);
cmd.CommandType = CommandType.StoredProcedure;
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Val");
dt.Rows.Add("id_Person", 1);
dt.Rows.Add("id_Dep", 1);
cmd.Parameters.Add(new SqlParameter("#AdditionalParams", dt));
cmd.ExecuteNonQuery();

Related

MSSQL SP Synonym Call from .Net Code error: Document Invalid, The request for procedure 'myProc' failed because 'myProc' is a synonym object

We have a procedure in our project. Which accepts some integer, string and user-defined table type as parameters. It was working fine with the following code snippet previously:
public void myFunc()
{
DataTable myDt = new DataTable();
myDt.Columns.Add("col1");
myDt.Columns.Add("col2");
myDt.Columns.Add("col3");
myDt.Rows.Add(new object[] { "R1Val1", "R1Val2","R1Val3"});
myDt.Rows.Add(new object[] { "R2Val1", "R2Val2","R2Val3"});
DataSet ds = new DataSet();
using (SqlConnection sqlConnection = new SqlConnection(dbConnstring))
{
using (SqlCommand sqlCommand = new SqlCommand("myProc", sqlConnection))
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.Parameters.Add(new SqlParameter("#param1", SqlDbType.NVarChar));
sqlCommand.Parameters["#param1"].Value = "";
sqlCommand.Parameters.Add(new SqlParameter("#tableParam", SqlDbType.Structured));
sqlCommand.Parameters["#tableParam"].Value = myDt;
sqlCommand.Parameters["#tableParam"].TypeName = "UserDefinedTableType";
using (SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand))
{
sqlDataAdapter.Fill(ds);
}
}
}
}
We have a new requirement for creating synonyms for SP`s in the DB in order to achieve some functionality.
Now the problem is that if the user-defined table type parameter is passed from the code for executing SP, it throws error on da.Fill(dt) line of code that:
"Document Invalid, The request for procedure 'MyProc' failed because 'MyProc' is a synonym object."
Other than these procedures having user defined types in parameters, other procedures synonyms executing fine from the code. Moreover the sql server does not throw any error when the query is executed directly from the SQL Management Studio.
As per my observation, it seems like this error is only caused from the code side.
Any idea or code to resolve this issue.
SQL Script for User Defined Data Type:
CREATE TYPE [dbo].UserDefinedTableType AS TABLE(
[col1] [nvarchar](50) NOT NULL,
[col2] [nvarchar](50) NOT NULL,
[col3] [nvarchar](50) NOT NULL,
)
GO
Procedure Script:
Create PROCEDURE [dbo].[myProc_V2] (#param1 nvarchar(30),
#tableParam UserDefinedTableType READONLY,
)
AS
BEGIN
--Logic of SP
END
Synonym Screenshot

Operand type clash: nvarchar is incompatible with user-defined table type

I'm new here and I'm facing a trouble currently, my scenario is that I wanted to insert and update data from Excel into a SQL Server table.
For the insert part it works perfectly but when it comes to update I have no idea how should I do that. I have search for few methods and I found this is the most comfortable for me by using stored procedure.
Here is my code that I'm using now. When I try it gave me this error:
Operand type clash: nvarchar is incompatible with user-defined table type
--- Stored procedure ---
CREATE PROCEDURE [dbo].[chkUpdate]
#Operator IC_CHK READONLY
AS
BEGIN
set nocount on;
MERGE INTO tb_Operator c1
USING #Operator c2 ON c1.IC = c2.IC
WHEN MATCHED THEN
UPDATE SET
c1.Name = c2.Name,
--c1.IC = c2.IC,
c1.Email = c2.Email,
c1.Status = c2.Status,
c1.Datetime = c2.Datetime
WHEN NOT MATCHED THEN
INSERT VALUES(c2.Name, c2.IC, c2.Email, c2.[Status], c2.[Datetime]);
end
--- User-defined table type ---
CREATE TYPE [dbo].[IC_CHK] as table
(
[Id] [int] NULL,
[Name] [nvarchar](50) NULL,
[IC] [bigint] NULL,
[Email] [nvarchar](MAX) NULL,
[Status] [nvarchar](50) NULL,
[Datetime] [datetime] NULL
)
VS 2010 code:
protected void btnImport_Click1(object sender, EventArgs e)
{
int i = 0;
try
{
string path = string.Concat(Server.MapPath("~/Excel/" + UploadExcel.FileName));
UploadExcel.SaveAs(path);
String strCon = string.Format("Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0}; Extended Properties=Excel 12.0;",path);
OleDbDataAdapter myda = new OleDbDataAdapter("SELECT * FROM [sheet1$]", strCon);
DataTable myds = new DataTable();
myda.Fill(myds);
for (i = 0; i <= myds.Rows.Count - 1; i++)
{
String constr = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
using (SqlConnection con = new SqlConnection(constr))
using (SqlCommand cmd = new SqlCommand("chkUpdate"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.AddWithValue("#Operator", path);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
MsgBox1.alert("Import success");
View.Visible = true;
vBinds();
}
catch (Exception ex)
{
MsgBox1.alert(ex.Message);
}
}
Do check for me and I'm appreciate it. Thank you
P/S: I double confirm that my user-defined table type has the same data type with my table.
In the INSERT in your MERGE statement, I would recommend to explicitly define the columns you're inserting into. Most likely, that's the cause of the error - you're inserting your columns - but you're not specifying which target columns those should be inserted into.
Since you're not specifying that, you must supply values for each column in the table, in the exact order in which they are defined - is that really the case?? E.g. what do you insert into your ID column in the table??
Assuming the ID column on your actual database table is an IDENTITY column, I would use (otherwise, you'd have to list ID in the list of columns to insert into as well and provide a value in the VALUES list of values):
WHEN NOT MATCHED THEN
INSERT(Name, IC, Email, [Status], [DateTime])
VALUES(c2.Name, c2.IC, c2.Email, c2.[Status], c2.[Datetime]);
and I would also recommend not to use T-SQL reserved keywords like status or datetime as your column names - you're just asking for trouble doing so. Use more expressive names - something that really relates to your business domain - not just datetime.....

Operand type clash: varchar is incompatible

I have created a user data table type as below:
CREATE TYPE [dbo].[UDTPASpecMYTest] AS TABLE(
[EmpName] [varchar](max) NULL,
[Empaddress] [varchar](max) NOT NULL,
[EmpCarname] [varchar](max) NULL
)
GO
and declare a procedure as below:
CREATE procedure [dbo].[test]
(
#tblPASpecs UDTPASpecMYTest READONLY
)
AS
BEGIN
select 1
END
While I am calling the procedure from application by passing a datatable it is showing an error:
"Operand type clash: nvarchar is incompatible with UDTPASpecMYTest".
Code in application:
DataColumn workCol = dtbl.Columns.Add("EmpName", typeof(String));
dtbl.Columns.Add("Empaddress", typeof(String));
dtbl.Columns.Add("EmpCarname", typeof(String));
dtbl.Rows.Add("Test", "Test", "Test");
strQuery = "EXEC dbo.test #tblPASpecs=" + dtbl + "";
//call the procedure
CMASConnectionProvider.DMLService.ExecSqlReturnDataSet(strQuery);
You are only passing the type name (as string) to your stored procedure.
Instead you must pass the table instance, by using SqlParameter object.
Something like this:
var connection = CMASConnectionProvider.Connection;
var command = new SqlCommand("dbo.test", connection);
command.CommandType = CommandType.StoredProcedure;
// Next 2 lines are the point:
var parameter = command.Parameters.AddWithValue("#tblPASpecs", dtbl);
parameter.SqlDbType = SqlDbType.Structured;
// Execute the command according your needs and existing helper classes
// var result = command.Execute();
This article explains exactly what you would like to do, please read here

Calling a stored procedure with a parameter that is a table

I'm trying to call the below stored procedure but I am unsure on what to pass through one of the parameters (#UnsubscribeTypes) I've tried passing in a list but got a compile error. I'm using c#, Visual Studio 2010, web forms. Any ideas on what I should pass in when calling the stored procedure in my c# code (ado.net)?
Here is my stored procedure:
ALTER PROCEDURE [czone].[SetAccountEmailPreference]
(
#EmailAddress VARCHAR(255),
#UnsubscribeTypes dbo.ListOfIDs READONLY,
#SentEmailID INT = NULL
)
AS
SET NOCOUNT ON;
EXEC dbo.LogObjectExecution ##PROCID;
DECLARE #UnsubscribeID INT = (SELECT TOP 1 UnsubscribeID
FROM Email.dbo.Unsubscribe
WHERE EmailAddress = #EmailAddress
ORDER BY UnsubscribeID DESC);
-- Unsubscribe
IF ((SELECT COUNT(*) FROM #UnsubscribeTypes) > 0)
BEGIN
IF(#UnsubscribeID IS NULL)
BEGIN
-- ADD UNSUBSCRIBE
INSERT INTO Email.dbo.Unsubscribe (EmailAddress, CreatedDate)
VALUES (#EmailAddress, CURRENT_TIMESTAMP)
SET #UnsubscribeID = ##IDENTITY;
END
-- Remove current mappings
DELETE FROM Email.dbo.UnsubscribeTypeMapping
WHERE UnsubscribeFK = #UnsubscribeID;
-- Add new mappings
INSERT INTO Email.dbo.UnsubscribeTypeMapping (UnsubscribeFK, UnsubscribeTypeFK, SentEmailFK)
SELECT
#UnsubscribeID, ID, #SentEmailID
FROM
#UnsubscribeTypes;
END
-- Subscribe
ELSE IF (#UnsubscribeID IS NOT NULL)
BEGIN
DELETE FROM Email.dbo.Unsubscribe
WHERE UnsubscribeID = #UnsubscribeID;
END
dbo.ListOfIDs is a table type. First, find out the type in your database, then check columns. generate a datatable with rows containing the UnsubscribeTypeFK ids.
The ADO.net code (not compiled)
Creating table
DataTable dt = new DataTable("Items");
dt.Columns.Add("ID", typeof(int));
dt.Rows.Add(4);
Calling proc
con = new SqlConnection(conStr);
con.Open();
using (con) {
// Configure the SqlCommand and SqlParameter.
SqlCommand sqlCmd = new SqlCommand("czone.SetAccountEmailPreference", con);
sqlCmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = sqlCmd.Parameters.AddWithValue("#UnsubscribeTypes", _dt); // TVP
tvpParam.SqlDbType = SqlDbType.Structured; //tells ADO.NET we are passing TVP
//pass other parameters
sqlCmd.ExecuteNonQuery();
}
con.Close();
You will find more about Table-Valued parameters here

Ado.net ExecuteScalar() returning null

I am executing a stored procedure in c# (through vs2008) using ado.net with an ExecuteScalar command. The stored proc returns the pkey of the new record entered, but ExecuteScalar is returning null. I look in the database and a record has indeed been added. I could use an output parameter to get the value, but then I won't know why this didn't work.
When I execute the sp in ssms, the pkey is returned.
What am I doing wrong?
Here is the C# code:
public int SaveNewPerson(EPerson ePerson)
{
int newPersonPkey;
SqlConnection cn = new SqlConnection(cnn.PersonData);
using (cn)
{
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = cn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "People.dbo.AddNewPerson";
cmd.Parameters.Add("#LastName", SqlDbType.VarChar, 150).Value = ePerson.LastName;
cmd.Parameters.Add("#FirstName", SqlDbType.VarChar, 150).Value = ePerson.FirstName;
cn.Open();
object result = cmd.ExecuteScalar();
newPersonPkey = int.Parse(result.ToString());
cn.Close();
}
catch (Exception e)
{
// call error method
throw new Exception(e.Message + " save new Person error ");
}
}
return newPersonPkey;
}
And this is the sp:
PROCEDURE [dbo].[AddNewPerson]
#FirstName varchar(50)
,#LastName varchar(50)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [People].[dbo].[Persons]
(
[FirstName]
,[LastName]
)
VALUES
(
#FirstName
,#LastName
)
declare #persons_PKey int
set #persons_PKey = ##IDENTITY
return #persons_PKey
end
The ExecuteScalar method returns the first field of the first record of the result, but as your query doesn't produce a result, it will return null.
You can either select the value instead of returning it from the stored procedure, or add a parameter with the direction set to ParameterDirection.ReturnValue to catch what the stored procedure returns.
Try changing the Stored Procedure to use a Select Statement to return the identity instead of using a return like this:
SELECT CAST(scope_identity() AS int)
Thus changing your procedure to:
PROCEDURE [dbo].[AddNewPerson]
#FirstName varchar(50)
,#LastName varchar(50)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [People].[dbo].[Persons]
(
[FirstName]
,[LastName]
)
VALUES
(
#FirstName
,#LastName
)
SELECT CAST(scope_identity() AS int)
end
From the documentation of the ExecuteScalar() on MSDN it says that it will return the first column of the first row in the result set or null otherwise if the result set is empty.

Categories