How can I pass an array variable to a SQL Server stored procedure using C# and insert array values into a whole row?
Thanks in advance.
SQL Server table:
ID | Product | Description
-------------------------------
8A3H | Soda | 600ml bottle
C# array:
string[] info = new string[] {"7J9P", "Soda", "2000ml bottle"};
SQL Server stored procedure:
ALTER PROC INSERT
(#INFO_ARRAY ARRAY)
AS
BEGIN
INSERT INTO Products VALUES (#INFO_ARRAY)
END
In SQL Server 2008 and later
Create a type in SQL Server like so:
CREATE TYPE dbo.ProductArray
AS TABLE
(
ID INT,
Product NVARCHAR(50),
Description NVARCHAR(255)
);
Alter your procedure in SQL Server:
ALTER PROC INSERT_SP
#INFO_ARRAY AS dbo.ProductArray READONLY
AS
BEGIN
INSERT INTO Products SELECT * FROM #INFO_ARRAY
END
Then you'll need to create a DataTable object with values to pass in C#:
DataTable dt = new DataTable();
//Add Columns
dt.Columns.Add("ID");
dt.Columns.Add("Product");
dt.Columns.Add("Description");
//Add rows
dt.Rows.Add("7J9P", "Soda", "2000ml bottle");
using (conn)
{
SqlCommand cmd = new SqlCommand("dbo.INSERT_SP", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter dtparam = cmd.Parameters.AddWithValue("#INFO_ARRAY", dt);
dtparam.SqlDbType = SqlDbType.Structured;
}
here is a way simpler example:
I've been searching through all the examples and answers of how to pass any array to sql server,till i found this linK, below is how I applied it to my project:
--The following code is going to get an Array as Parameter and insert the values of that
--array into another table
Create Procedure Proc1
#INFO_ARRAY ARRAY nvarchar(max) //this is the array your going to pass from C# code
AS
declare #xml xml
set #xml = N'<root><r>' + replace(#INFO_ARRAY,',','</r><r>') + '</r></root>'
Insert into Products
select
t.value('.','varchar(max)')
from #xml.nodes('//root/r') as a(t)
END
Hope you enjoy it
Related
I am trying to upload an Excel file in C# to SQL Server database table.
The table looks like this:
Companies
ID(PK) Int AutoIncrement
CompanyName Varchar(256)
Logo Varchar(256)
WebsiteURL Varchar(256)
Description Varchar(256)
I have read the Excel into a DataTable object and passed it to a stored procedure. The stored procedure uses MERGE INTO to insert new records and update existing.
I need to know how many records are inserted and how many are updated.
For this, I used OUTPUT like this:
CREATE PROCEDURE [dbo].[Update_Companies]
#tblCompanies CompanyType READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #summary CompanySummaryType;
MERGE INTO companies c1
USING #tblCompanies c2 ON c1.CompanyName = c2.CompanyName
WHEN MATCHED THEN
UPDATE
SET c1.Logo = c2.Logo,
c1.WebsiteURL = c2.WebsiteURL,
c1.CompanyDescription = c2.CompanyDescription,
WHEN NOT MATCHED THEN
INSERT (CompanyName, Logo, WebsiteURL, Description)
VALUES (c2.CompanyName, c2.Logo, c2.WebsiteURL, c2.Description)
OUTPUT
$action as ActionType,
INSERTED.CompanyName as CompanyName INTO #summary;
END
CompanyType is a user-defined table type containing table columns
CompanySummaryType is a user-defined table type containing two columns:
ActionType Varchar(256),
CompanyName Varchar(256)
The code runs fine and insert or update working perfectly.
I want to read the #summary variable back in my C# code.
Right now, I am using ExecuteNonQuery to execute stored procedure like this:
private void AddRecords(DataTable dataTable)
{
string constr = ConfigurationManager.ConnectionStrings["CMSConnectionString"].ConnectionString;
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand("Update_Companies"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.AddWithValue("#tblCompanies", dataTable);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
}
Also, is there a way by which I can find how many records failed to insert or update?
You can make your #Summary variable an OUTPUT parameter of the procedure.
In your C# code, you can then compare the returned OUTPUT parameter, with the original input parameter, to see which rows were not inserted or updated.
I'm looking to do something simulair toward here: How do I insert multiple rows WITHOUT repeating the "INSERT INTO dbo.Blah" part of the statement?
except that in addition towards doing this in one query (faster then several dozen) I also want to do this parameterized as the input comes from the web.
Currently I have
foreach(string data in Scraper){
SqlConnection conn = new SqlConnection(WebConfigurationManager.AppSettings["ConnectionInfo"].ToString());
string query = "INSERT INTO DATABASE('web',#data)";
SqlCommand sqlCommand= new SqlCommand(query, conn);
sqlCommand.Parameters.AddWithValue("#data", data);
Command.executeNonQuery();
conn.close();
}
Which is a bit slugish (note the real example has a lot more colums but that would make things more confusing).
Since you are using c# and sql server 2008, you can use a table valued parameter to insert multiple rows to your database.
Here is a short description on how to do this:
First, you need to create a user defined table type:
CREATE TYPE MyTableType AS TABLE
(
Col1 int,
Col2 varchar(20)
)
GO
Then, you need to create a stored procedure that will accept this table type as a parameter
CREATE PROCEDURE MyProcedure
(
#MyTable dbo.MyTableType READONLY -- NOTE: table valued parameters must be Readonly!
)
AS
INSERT INTO MyTable (Col1, Col2)
SELECT Col1, Col2
FROM #MyTable
GO
Finally, execute this stored procedure from your c# code:
DataTable dt = new DataTable();
dt.Columns.Add("Col1", typeof(int));
dt.Columns.Add("Col2", typeof(string));
// Fill your data table here
using (var con = new SqlConnection("ConnectionString"))
{
using(var cmd = new SqlCommand("MyProcedure", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#MyTable", SqlDbType.Structured).Value = dt;
con.Open();
cmd.ExecuteNonQuery();
}
}
You can make use of the SQL syntax:
INSERT INTO YOUR_TABLE (dataColumn) VALUES (data1),(data2),(data3)
So loop over your rows you wanna insert and append ",(datax)" to your query and also add the corresponding parameter.
Perhaps it helps.
I have a SQL statement that I need to run in C# and would need to get parameters from C# code. I know stored procedures are preferred to avoid SQL injection but I am just looking to do this in C#.
I am translating this SQL to C# but I encountered an error even though the query works in SQL Server Management Studio. It uses temporary stored procedure and temp table below:
-- 1.) Declare a criteria table which can be any number of rows
BEGIN TRY
DROP TABLE #CriteriaTable
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE #CriteriaTable (ParameterCode VARCHAR(64), Value VARCHAR(64))
-- 2.) Declare a procedure to add criteria table
BEGIN TRY
DROP PROCEDURE #AddCriteriaTable
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #AddCriteriaTable
(#ParameterCode VARCHAR(64), #Value VARCHAR(64))
AS
INSERT #CriteriaTable
VALUES(#ParameterCode, #Value)
GO
-- 3.) Do a computation which accesses the criteria
BEGIN TRY
DROP PROCEDURE #ComputeBasedOnCriteria
END TRY
BEGIN CATCH
END CATCH
go
CREATE PROCEDURE #ComputeBasedOnCriteria
(#product VARCHAR(36) = 'ABC',
#currency VARCHAR(3) = 'USD',
#zScore FLOAT = .845)
AS
-- Code inside this procedure is largely dynamic sql.
-- This is just a quick mock up
SELECT
#Product ProductCode,
#currency Currency,
950 ExpectedRevenue,
*
FROM
#CriteriaTable c
PIVOT
(min (Value) FOR ParameterCode IN
([MyParam1], MyParam2, MyParam3)
) AS pvt
GO
--End of code for Configuration table
-- Samples: Execute this to add criteria to the temporary table that will be used by #ComputeBasedOnCriteria
EXEC #AddCriteriaTable 'MyParam1', 'MyValue1'
EXEC #AddCriteriaTable 'MyParam2', 'MyValue3'
EXEC #AddCriteriaTable 'MyParam3', 'MyValue3'
--Execute the procedure that will return the results for the screen
EXEC #ComputeBasedOnCriteria
Now trying this in C# I encounter an error when I try to run the #AddCriteriaTable procedure. When I try to run the ExecuteQuery on the second to the last line it throws:
Exception: System.Data.SqlClient.SqlException, Incorrect syntax near the keyword 'PROC'.
Why does it work in SQL Server but not in C# code? Is there another way to do this in C#? Let me know if there are c# guidelines I should follow as I am still learning this c# - db work.
EDIT:
I know I could do this as a normal stored proc and pass in a DataTable however there are team issues I cannot say and it forces me to use the sp as a text.
The reason that it is failing is you are passing parameters to the CREATE PROC section here:
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
It does not make sense to pass the values here, since you are just creating the procedure, you only need to pass them when executing the procedure. If you run a trace you will see something like this being executed on the server:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)',
N'#ParameterCode VARCHAR(64),#Value VARCHAR(64)',
#ParameterCode = 'MyParam1',
#Value = 'MyValue1'
Which will throw the same incorrect syntax error when run in SSMS. All you need is:
EXEC sp_executesql
N'CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)';
So in c# you would need:
//First Create the procedure
cmd.CommandText = #"CREATE PROC #AddCriteriaTable (#ParameterCode VARCHAR(64), #Value VARCHAR(64)) AS INSERT #CriteriaTable VALUES (#ParameterCode, #Value)";
cmd.ExecuteNoneQuery();
//Update the command text to execute it, then add parameters
cmd.CommandText = "EXECUTE #AddCriteriaTable #ParameterCode, #Value;";
cmd.Parameters.AddWithValue("#ParameterCode", request.Criteria.First().Key;
cmd.Parameters.AddWithValue("#Value", request.Criteria.First().Value;
var reader2 = cmd.ExecuteReader();
I think you are over complicating everything, a temporary stored procedure to add data to a temporary table seems over kill.
If you are executing from code it seems likely that you need to reuse everything, so why not just have a permanent procedure for your computation,
and then use a defined type to manage instances of the execution.
So first create your type:
CREATE TYPE dbo.CriteriaTableType AS TABLE (ParameterCode VARCHAR(64), Value VARCHAR(64));
Then create your procdure:
CREATE PROC dbo.ComputeBasedOnCriteria
(
#product VARCHAR(36)='ABC',
#currency VARCHAR(3)='USD',
#zScore FLOAT = .845,
#CriteriaTable dbo.CriteriaTableType READONLY
)
AS
--Code inside this proc is largely dynamic sql. This is just a quick mock up
SELECT
#Product ProductCode
,#currency Currency
,950 ExpectedRevenue
,*
FROM #CriteriaTable c
PIVOT (MIN (Value) FOR ParameterCode IN (MyParam1, MyParam2,MyParam3)) AS pvt;
GO
Then finally to run:
DECLARE #Criteria dbo.CriteriaTableType;
INSERT #Criteria
VALUES
('MyParam1', 'MyValue1'),
('MyParam2', 'MyValue2'),
('MyParam3', 'MyValue3');
EXECUTE dbo.ComputeBasedOnCriteria #CriteriaTable = #Criteria;
You can even populate the criteria table in c#, and just pass this from c# to the procedure.
var table = new DataTable();
table.Columns.Add("ParameterCode", typeof(string)).MaxLength = 64;
table.Columns.Add("Value", typeof(string)).MaxLength = 64;
foreach (var criterion in request.Criteria)
{
var newRow = table.NewRow();
newRow[0] = criterion.Key;
newRow[1] = criterion.Value;
table.Rows.Add(newRow);
}
using (var connection = new SqlConnection("connectionString"))
using (var command = new SqlCommand("dbo.ComputeBasedOnCriteria", connection))
{
var tvp = command.Parameters.Add("#CriteriaTable", SqlDbType.Structured);
tvp.TypeName = "dbo.CriteriaTableType";
tvp.Value = table;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
//Do Something with your results
}
}
}
If you are executing SQL to create a stored procedure via C# then you might as well just execute your SQL via C# and forget about the Procedure.
The point of using a stored procedure to avoid SQL Injection only applies when the stored procedure already exists on the server and you are not creating it via the code.
You can avoid SQL injection here by using a Parameterised query.
Parameters prevent sql injection by validating the data type. So if you insert a integer in your code then someone attempting injection cannot supply a string with special characters which changes your expected result.
BUT apart from all that, you're getting an error because you have CREATE PROC in your SQL in C# instead of CREATE PROCEDURE
Scenario: Just consider i have a table having 3 column Id, QuestionId and optedAnswer. I have a form which consists n no of question and some options for answer. On save button i want to save data in database(in my table).
Problem: I want to save all the answers in one connection.
Step taken by me: I made a string having structure questionId:optedAnswwer | questionId : optedAnswer | and so on....
I wrote a procedure. started a loop. split the data based on ':' and '|' and saved data in one connection. But it is a rigorous task. Is there any way to save the data directly without using loop and split.
Save your each questionId and its OptedAnswer in Datatable and then insert your datatable to SQL table as below :
DataTable dataTable = null; // your data needs to be here
try
{
ConnectionStringSettings mConString = ConfigurationManager.ConnectionStrings["SiteSqlServer"];
// Optional truncating old table
using (SqlConnection connection = new SqlConnection(mConString.ConnectionString))
{
connection.Open();
// Delete old entries
SqlCommand truncate = new SqlCommand("TRUNCATE TABLE MYTABLE", connection);
truncate.ExecuteNonQuery();
}
SqlBulkCopy bulkCopy = new SqlBulkCopy(mConString.ConnectionString, SqlBulkCopyOptions.TableLock)
{
DestinationTableName = "dbo.MYTABLE",
BatchSize = 100, //set your required size
BulkCopyTimeout = 360
};
bulkCopy.WriteToServer(dataTable);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Create a XML string of all your queastion and ans and pass these xml string to sql and use the
sp_xml_preparedocument procedure these sql inbuilt proc that reads the XML.
you can get more information on Bulk INsert
I would also go for an xml-centric solution, prepare you data in an xmldocument, post that inte the database and use a SELECT from the xml as source for your insert eg:
declare #xml XML
DECLARE #docid int
set #xml = N'<?xml version="1.0" ?>
<Custs>
<Cust>
<name>Erik</name>
<lastname>Stark</lastname>
</Cust>
<Cust>
<name>Donald</name>
<lastname>Duck</lastname>
</Cust>
<Cust>
<name>Johnny</name>
<lastname>Walker</lastname>
</Cust>
</Custs>'
EXEC sp_xml_preparedocument #docid OUTPUT, #xml
SELECT *
FROM OPENXML (#docid, '/Custs/Cust',2)
WITH (name varchar(50), lastname varchar(50))
exec sp_xml_removedocument #docid
There is another way to save multiple data in one connection.
Create a type of table like this
CREATE TYPE [dbo].[YourTypeName] AS TABLE(
[Col1] [bigint] NULL,
[Col2] [datetime] NULL,
[Col3] [bigint] NULL,
[Col4] [bigint] NULL,
[Col5] [datetime] NULL
)
and then write a procedure like this....
CREATE PROCEDURE [dbo].YOURPROCEDURENAME]
(
#yourDataTableName YourTypeName READONLY
)
AS
INSERT INTO TableName
(Col1,
Col2,
Col3,
Col4,
Col5)
SELECT CP.Val1,
CP.Val2,
CP.Val3,
CP.Val4,
CP.Val15)
FROM #yourDataTableName CP
GO
And then create a datatable in code behind and pass that datatable in prosedure like this...
SqlCommand cmd = new SqlCommand("YOURPROCEDURENAME");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#yourDataTableName", SqlDbType.Structured).Value = datatable;
cmd.Parameters[0].TypeName = "YourTypeName ";
Just a few years behind, but I discovered table valued parameters for stored procedures/UDFs today. They are the ideal solution to a problem I'm having, but I can't get them to work from C#.
I have a UDF:
CREATE FUNCTION GetSurveyScores
#Survey bigint,
#Question nvarchar(max),
#Area StringList READONLY,
#JobCode StringList READONLY
AS BEGIN
SELECT * FROM SurveyResults WHERE RespondentArea IN (Select val from #Area) AND RespondentJobCode IN (select val from #JobCodes)
END
(StringList is the type I created, it's just a table valued type with a single column called val, defined as nvarchar(256))
Then from SQL Server Management Studio, I can do this:
declare #area StringList;
insert into #area(val) values('NW'),('NE'),('SW');
// Get all survey respondent job codes from the SurveyRespondent table.
declare #jobcodes StringList;
insert into #jobcodes select distinct jobcode from dbo.SurveyRespondent;
select * from dbo.GetSurveyScores(3, 'Q3', #area, #jobcodes)
That works brilliantly.
From C#, I get no results (no exceptions), using this code: (I'm using DataTables because the actual code I intend to drop this into uses DataTables already)
DataTable areas = new DataTable("StringList");
areas.Columns.Add("val");
areas.Rows.Add("NW"); areas .Rows.Add("NE"); areas .Rows.Add("SW");
DataTable jobcodes = new DataTable("StringList");
jobcodes.Columns.Add("val");
jobcodes.Rows.Add("JC1"); jobcodes .Rows.Add("JC2");
SqlCommand cmd = new SqlCommand("SELECT * FROM dbo.GetSurveyScores(3, 'Q3', #area, #jc)", connection);
cmd.Parameters.AddWithValue("#area", areas);
cmd.Parameters["#area"].SqlDbType = SqlDbType.Structured;
cmd.Parameters["#area"].TypeName = "StringList";
cmd.Parameters.AddWithValue("#jc", jobcodes);
cmd.Parameters["#jc"].SqlDbType = SqlDbType.Structured;
cmd.Parameters["#jc"].TypeName = "StringList";
DataTable results = new DataTable();
using (SqlDataAdapter da = new SqlDataAdapter(cmd)) {
da.Fill(results);
}
Console.WriteLine(results.Rows.Count);
The final line prints 0. I'm sure I must be missing something simple, but after 6 hours of trying, I think I need a new set of eyes to look at it.
I believe that the issue is that you are not returning anything from your function, although to be honest I'm not sure how it was able to run in MGT studio because it looks like a syntax error. The function should look something like this
CREATE FUNCTION GetSurveyScores (
#Survey bigint,
#Question nvarchar(max),
#Area StringList READONLY,
#JobCodes StringList READONLY )
RETURNS #Results TABLE (RespondentArea NVARCHAR(256), RespondentJobCode NVARCHAR(256))
AS
BEGIN
INSERT #Results
SELECT * FROM SurveyResults WHERE RespondentArea IN (Select val from #Area) AND RespondentJobCode IN (select val from #JobCodes)
RETURN
END
go
Id' image that the syntax you actually used was slightly different, and the select statement is getting executed but the return value of the function has no results.