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
Related
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
This question already has answers here:
How to pass an array into a SQL Server stored procedure
(13 answers)
Closed 3 years ago.
Is it possible to create something like this in C# code using SqlDbCommand and SqlParameter?
DECLARE #Users TABLE (ID INT)
INSERT INTO #Users
VALUES (10),(20),(30)
SELECT COUNT(*) FROM #Users
I tried something like this:
1) DataTable creator:
private static DataTable CreateDataTable(IEnumerable<int> ids)
{
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(int));
foreach (int id in ids)
{
table.Rows.Add(id);
}
return table;
2) Add SqlParameter:
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table
});
3) Execute command (command is SELECT COUNT(*) FROM #Users), parameters is list of parameters from step 2:
using (SqlCommand cmd = new SqlCommand(command, connection))
{
if (parameters != null)
cmd.Parameters.AddRange(parameters.ToArray());
SqlDataReader reader = cmd.ExecuteReader();
What I get is:
The table type parameter '#Users' must have a valid type name.
And I don't really have a real table so no type available, just want it to be:
DECLARE #Users TABLE (ID INT)
Is that doable? What I want to achieve is just pass list of values, in this case list of ints, obviously.
IN REGARD TO MARKED AS DUPLICATE:
Provided link doesn't solve the problem since it's not lack of typename problem but rather lack of typename to use. The problem is that I can't create any table and can't use any existing one to pass TypeName in SqlParameter.
ONE WORKING SOLUTION:
CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)
and then SqlParameter:
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table,
TypeName = "dbo.IntList"
});
Nevertheless, another step would be to use built-in type like GarethD suggested. I'm not sure if they are available in SQL Server 2016.
You need to add TypeName in sqlParameter , the same name with you created your table type in DB.
sqlParameters.Add(new SqlParameter()
{
ParameterName = $"#paramname",
SqlDbType = SqlDbType.Structured,
Value = table,
TypeName = "dbo.MyType";
});
If you do not have table type in database then first you need to create it in SQL
CREATE TYPE [dbo].[IntegerList] AS TABLE(
[Data] [int] NOT NULL,
)
GO
And then give that name in your code. That will work and you do need to create table in DB for that.
You need to use table type parameter. first you need to create table type in SQL. then pass parameter from C#
Take Datatable as SP Pararmeter
#dt customdatatable READONLY
Write Following C# Code
DataTable dt = new DataTable();
dt.Columns.Add("customcolumnname");
DataRow dr = dt.NewRow();
dr["customcolumnname"] = "columnvalue";
dt.Rows.Add(dr);
SqlParameter[] parCollection = new SqlParameter[1];
parCollection[0] = new SqlParameter("#yourdt", dt);
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.....
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();
Team,
I am getting list of expenses as input parameter after that i created datatable and stored all the records and passing to the stored procedure as Datatable
using User Defined Table types
User input value like expense List
Create DataTable and store all values
Pass into stored procedure as User Defined Table type
Code:
public ReturnTimeSheetExpSaveStatus SaveExpense(SaveExpenseInformation SaveExpense)
{
ReturnTimeSheetExpSaveStatus RTSExpStatus = new ReturnTimeSheetExpSaveStatus();
DataTable DtExpenseList = new DataTable();
DtExpenseList.Columns.Add("TimeSheetID", typeof(Int32));
DtExpenseList.Columns.Add("ExpenseBillableType",typeof(string));
DtExpenseList.Columns.Add("ExpenseType", typeof(string));
DtExpenseList.Columns.Add("ExpenseDays", typeof(string));
DtExpenseList.Columns.Add("CurrencyConvertAmount", typeof(string));
DtExpenseList.Columns.Add("CurrencyConvertBy", typeof(string));
DtExpenseList.Columns.Add("UserInputExpAmount", typeof(string));
DtExpenseList.Columns.Add("UserInputExpCurrency", typeof(string));
foreach (var ExpenseSaveList in SaveExpense.ExpenseList)
{
DataRow DtRow = DtExpenseList.NewRow();
DtRow["TimeSheetID"] = SaveExpense.TimeSheetID;
DtRow["ExpenseBillableType"] = ExpenseSaveList.ExpenseBillableType;
DtRow["ExpenseType"] = ExpenseSaveList.ExpenseType;
DtRow["ExpenseDays"] = ExpenseSaveList.ExpenseDays;
DtRow["CurrencyConvertAmount"] = ExpenseSaveList.CurrencyConvertAmount;
DtRow["CurrencyConvertBy"] = ExpenseSaveList.CurrencyConvertBy;
DtRow["UserInputExpAmount"] = ExpenseSaveList.UserInputExpAmount;
DtRow["UserInputExpCurrency"] = ExpenseSaveList.UserInputExpCurrency;
DtExpenseList.Rows.Add(DtRow);
DtExpenseList.AcceptChanges();
}
using (SqlConnection con = new SqlConnection(cs))
{
try
{
SqlCommand cmd = new SqlCommand("[sp_PostSaveExpense]", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Domain_Id", SaveExpense.DomainID);
cmd.Parameters.AddWithValue("#TimeSheetID", SaveExpense.TimeSheetID);
//ProductCat List table
cmd.Parameters.AddWithValue("#SaveExpenseTable", DtExpenseList);
SqlParameter ConsultantIDNameparameter = new SqlParameter();
ConsultantIDNameparameter.ParameterName = "#ReturnTimeSheetExpSaveStatus";
ConsultantIDNameparameter.SqlDbType = System.Data.SqlDbType.BigInt;
ConsultantIDNameparameter.Direction = System.Data.ParameterDirection.Output;
cmd.Parameters.Add(ConsultantIDNameparameter);
con.Open();
cmd.ExecuteNonQuery();
RTSExpStatus.TimeSheetSaveExpStatus = Convert.ToInt32(ConsultantIDNameparameter.Value);
}
catch (Exception Ex)
{
RTSExpStatus.ErrorMessage = Ex.ToString();
}
finally
{
con.Close();
}
}
return RTSExpStatus;
}
User defined table Type:
CREATE TYPE [dbo].[spCPMapp_SaveExpense] AS TABLE(
[TimeSheetID] [int] NULL,
[ExpenseBillableType] [varchar](30) NULL,
[ExpenseType] [varchar](30) NULL,
[ExpenseDays] [varchar](30) NULL,
[CurrencyConvertAmount] [varchar](20) NULL,
[CurrencyConvertBy] [varchar](10) NULL,
[UserInputExpAmount] [varchar](20) NULL,
[UserInputExpCurrency] [varchar](10) NULL
)
GO
Stored procedure:
CREATE PROCEDURE [dbo].[sp_PostSaveExpense]
(
#Domain_Id varchar(30),
#TimeSheetID int,
#SaveExpenseTable spCPMapp_SaveExpense READONLY,
#ReturnTimeSheetExpSaveStatus int output
)
AS
--SET NOCOUNT ON added to prevent extra result sets from
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN
IF EXISTS (SELECT 1 FROM table WHERE ID=#TimeSheetID )
BEGIN
INSERT INTO tabke(Contractor_TESheets_ID,billableType,Expense_Type,Day_of_the_Week,currency,Amount)
SELECT [TimeSheetID] , ExpenseBillableType, ExpenseType , ExpenseDays , UserInputExpCurrency, UserInputExpAmount
FROM #SaveExpenseTable WHERE ExpenseType <> 'Distance'
END
END
When I execute this program, everything is working fine.
I raised the request to DBA team for moving these two objects into Production environment.
DBA team did not accept to run "User Defined Table type", they said, we never used User defined table type in Production around the company.
I explained to them, this is the feature to update list of values into single variables.
I need to explain them clearly to DBA team.
Can you help me?
If we use "User Defined table types " in Production, it would affect any perforamnce?
Each time if I execute this UDT , the value can store any of the place?
Features of User defined table type?
If any one of you using this kind of scenarios in your project?
Please share your suggestions
or
Is there any other simple way to achieve this solution?
I do this in my application I've found that setting the SqlDbType seems to work.
//ProductCat List table
cmd.Parameters.AddWithValue("#SaveExpenseTable", DtExpenseList).SqlDbType = SqlDbType.Structured;
Okay as far as I can understand your issue is:
- You need the user information into C# datatables which is later stored into SQL tables using a stored procedure.
- However, the DB team is not allowing you to manually define the columns and datatypes for the datatable.
If this is the case, there are 2 things you can try:
1.) No need for a datatable. You can simply run a foreach loop on the user inputs and passs them as SQLParameters.
2.) If you need to store the data inside datatables for future computations, you can get the schema from the SQL table into C# datatables by the following code:
DataTable dt = new DataTable();
using (SqlConnection conn = new SqlConnection("Your SQLConnectionString")) {
conn.Open();
using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT TOP 0 * FROM tabke", conn))
{
adapter.Fill(dt);
};
};
You can debug the datatable (dt) and check its structure just to make sure that you have the correct results (columns).
I hope this helps. Good luck brother.