Can I pass a list to stored procedure? - asp.net

I have the following list
ID | DESC | PRICE
10 | SHOE | 5000
11 | LACE | 3000
12 | GAME | 2000
13 | TOAD | 3000
I am now passing individual rows in a foreach loop, and establishing a new connection all the time, which looks unconventional but I am hoping there is a faster way.
This is the code I have.
foreach(var item in tempList)
{
using (connection)
{
SqlCommand command = new SqlComman("StoredProc", connection);
command.Parameters.Add(new SqlParameter("id", item.id));
command.Parameters.Add(new SqlParameter("desc", item.desc));
command.Parameters.Add(new SqlParameter("price", item.price));
...
}
}
So how do I pass a list to a stored procedure?

To give a practical example of TVPs, in addition to the links (which are definitely worthwhile reading). Assuming SQL Server 2008 or better.
First, in SQL Server:
CREATE TYPE dbo.Items AS TABLE
(
ID INT,
Description VARCHAR(32),
Price INT
);
GO
CREATE PROCEDURE dbo.StoredProcedure
#Items AS dbo.Items READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.DestinationTable(ID, [DESC], Price)
SELECT ID, Description, Price FROM #Items;
END
GO
Now in C#:
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID"));
tvp.Columns.Add(new DataColumn("Description"));
tvp.Columns.Add(new DataColumn("Price"));
foreach(var item in tempList)
{
tvp.Rows.Add(item.id, item.desc, item.price);
}
using (connection)
{
SqlCommand cmd = new SqlCommand("StoredProcedure", connection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Items", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
connection.Open();
cmd.ExecuteNonQuery();
}

You could declare a custom table data type in SQL Server, use that as a parameter to your stored procedure, use a DataTable in your code, and fill it with the rows.
Read more on MSDN: Table-Valued Parameters

You could take a look at using Table-Valued Parameters to pass all the rows in one call as a single parameter:
Table-valued parameters provide an easy way to marshal multiple rows
of data from a client application to SQL Server without requiring
multiple round trips or special server-side logic for processing the
data. You can use table-valued parameters to encapsulate rows of data
in a client application and send the data to the server in a single
parameterized command. The incoming data rows are stored in a table
variable that can then be operated on by using Transact-SQL.

Related

Stored Procedure for inserting text field values that is created dynamically to the same id using asp.net C#

Im new to ASP.net webforms.Im having a event page,in which i have a field to add sales channel heads mail id.when i click on the plus button i will be able to add more than one sales channels head.
For inserting the form values into the database im using Stored procedure.and its inserting the records with one sales channel head email id.
I want to know how i can write a stored procedure for inserting dynamic textbox values into sql server for the same record(That is the id and event name should be same).
This is my stored procedure
CREATE PROCEDURE SPInsertEvent
#eventName varchar(200),
#eventDate date,
#costPerHead varchar(200),
#totalCost varchar(200),
#salesChannelHeads varchar(200),
#salesManagers varchar(200),
#eventCreationTime datetime
AS
BEGIN
SET NOCOUNT ON
-- Insert statements for procedure here
INSERT INTO dbo.hp_event
(event_name, event_date, cost_per_head, total_cost, sales_channel_head, sales_manager, event_creation_time)
VALUES
(#eventName, #eventDate, #costPerHead, #totalCost, #salesChannelHeads, #salesManagers, #eventCreationTime)
END
This is my ASP.net function
SqlCommand cmd = new SqlCommand("SPInsertEvent", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("EventName", txtEventName.Text);
cmd.Parameters.AddWithValue("EventDate", Convert.ToDateTime(txtEventDate.Text));
cmd.Parameters.AddWithValue("CostPerHead", txtTotCostPerHead.Text);
cmd.Parameters.AddWithValue("TotalCost", txtTotalCostEvent.Text);
cmd.Parameters.AddWithValue("SalesChannelHead", txtSalesChannelHead.Text);
cmd.Parameters.AddWithValue("SalesManager", txtSalesManagers.Text);
cmd.Parameters.AddWithValue("EventCreationTime", DateTime.Now);
conn.Open();
int k = cmd.ExecuteNonQuery();
if (k != 0)
{
string message = "Event added successfully.";
string script = "window.onload = function(){ alert('";
script += message;
script += "')};";
ClientScript.RegisterStartupScript(this.GetType(), "SuccessMessage", script, true);
}
conn.Close();
Instead of storing all the list of email ids for the given event in one table, I would suggest you to store them in separate table and you can reference them from the hp_event table whenever you need. So your database design should be some thing like below where eventid of hp_eventSalesManagers references eventId of hp_event -
To make this design work you can make use of Table Valued Parameters in ADO.NET and follow the below steps:
Create a User Defined Type -
CREATE TYPE [dbo].[ChannelHeads] As Table
(
EmailIds VARCHAR(50)
)
Whenever you click button populate a new Data Table(I am using Session to keep track
of the previous data), below is the sample code:
protected void btnAdd_Click(object sender, EventArgs e)
{
if (Session["DataTable"] == null)
{
dataTable = new DataTable();
dataTable.Columns.Add("EmailIds", typeof(string));
Session.Add("DataTable", dataTable);
}
else
{
//If yes then get it from current session
dataTable = (DataTable)Session["DataTable"];
}
DataRow dt_row;
dt_row = dataTable.NewRow();
dt_row["EmailIds"] = name.Text;
dataTable.Rows.Add(dt_row);
}
When submitting to data base add the below parameter(See the way I am passing the data table to DB):
SqlParameter parameterSalesChannelHeads = new SqlParameter();
parameterSalesChannelHeads.ParameterName = "#salesChannelHeads";
parameterSalesChannelHeads.SqlDbType = System.Data.SqlDbType.Structured;
parameterSalesChannelHeads.Value = (DataTable)Session["DataTable"];
parameterSalesChannelHeads.TypeName = "dbo.ChannelHeads";
cmd.Parameters.Add(parameterSalesChannelHeads);
Change all your parameters in above format just to make sure you are using
Parameters.Add instead of Parameters.AddWithValue as mentioned here
Finally change the procedure as below to populate the tables, below is one of the way,
you can enable error handling and improve the code:
ALTER PROCEDURE SPInsertEvent
#eventName varchar(200),
#eventDate datetime,
#costPerHead varchar(200),
#totalCost varchar(200),
#salesChannelHeads As [dbo].[ChannelHeads] Readonly,
#salesManagers varchar(200),
#eventCreationTime datetime
AS
BEGIN
SET NOCOUNT ON
DECLARE #eventID INT
-- Insert statements for procedure here
INSERT INTO dbo.hp_event
(event_name, eventDate, costPerHead, totalCost, eventCreationTime,
salesManagers)
VALUES
(#eventName, #eventDate, #costPerHead, #totalCost,#eventCreationTime,
#salesManagers)
SET #eventID = SCOPE_IDENTITY()
INSERT INTO dbo.hp_eventSalesManagers
(eventid,event_name,salesChannelHeads)
SELECT #eventID, #eventName, EmailIds
FROM
#salesChannelHeads
END
Finally change the data types of the fields accordingly as mentioned in the comment section for better clarity and usages.
You said in the comments "What i need is a stored procedure for inserting saleschannel heads email id(txtSalesChannelHead,txtSalesChannelHead1,txtSalesChannelHead2) into the sql server table with same id,that is there will be duplicate rows in the table". Handling a dynamic number of inputs like that is not best done in a stored procedure, from what i can see of your scenario. The easier way is to run the insert procedure from your .NET code once for each textbox. Now I don't know how your textboxes are being added, so I can't tell you how to get the set of textbox values, but once you have it, a simple foreach loop will let you run the stored procedure once for each of them.

Update 2000 records with one query

I have a Database table:
Item
ID (uniqueidentifier)
Index (int)
I have a list of 2000 key-value pairs items where the key is ID and value is Index, which i need to update it. How can i update all the 2000 items from database using one single sql query?
Right now i have something like this:
// this dictionary has 2000 values
Dictionary<Guid, int> values = new Dictionary<Guid,int>();
foreach(KeyValuePair<Guid, int> item in values)
{
_db.Database.ExecuteSqlCommand("UPDATE [Item] SET [Index] = #p0 WHERE [Id] = #p1", item.Value, item.Key);
}
However, i am making too many requests to the SQL Server, and i want to improve this.
Use table value parameters to send those values to SQL Server and update Items table in one shot:
CREATE TYPE KeyValueType AS TABLE
(
[Key] GUID,
[Value] INT
);
CREATE PROCEDURE dbo.usp_UpdateItems
#pairs KeyValueType READONLY
AS
BEGIN
UPDATE I
SET [Index] = P.Value
FROM
[Item] I
INNER JOIN #pairs P ON P.Id = I.Id
END;
GO
If you really need to update in that manner and have no other alternative - the main way around it could be this rather "ugly" technique (and therefore rarely used, but still works pretty well);
Make all 2000 statements in one string, and execute that one string. That makes one call to the database with the 2000 updates in it.
So basically something like this (code not made to actually run, it's an example so t
Dictionary<Guid, int> values = new Dictionary<Guid, int>();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (KeyValuePair<Guid, int> item in values)
{
sb.Append(String.Format("UPDATE [Item] SET [Index] = {0} WHERE [Id] = '{1}';", item.Value, item.Key));
}
_db.Database.ExecuteSqlCommand(sb.ToString);

SqlCommand slow when executing a query with parameters

DbDataAdapter.Fill() is extremly slow when performing parameters!
I have a query with 2 parameters inside, and when I put those parameters hardcoded in the query it takes 1 second to execute (in a 470k table rows, returning only 20 rows).
I found many posts similars here and I tried all those solutions (set arithabort, option recompile, option optimize for, ...) with no luck.
I just perform a query (sql server 2008) and not a stored procedure, so the query with arithabort is like this:
string strSql = #"set ARITHABORT ON;
select TOP 20 ....
Also I tried to call set arithabort in the same transaction but performing that query first..
I don't know if I'm doing something wrong, but the sensation is the ado.net is performing a very bad execution plan in ado.net when I have defined parameters on it.
As a result of this bad choice, the execution time in SSMS is 1 second (after being cached) but in asp is like 9 seconds!
The query is something like this:
strSQL #="
select *
from Table1
where Name like #name";
And then:
DbProviderFactory factory = DbProviderFactories.GetFactory(mProvider);
DbCommand dbcmd = factory.CreateCommand();
if (CommandTimeout != null)
dbcmd.CommandTimeout = CommandTimeout.Value;
if(this.transaccion != null)
dbcmd.Transaction = this.transaccion;
dbcmd.Connection = dbc;
dbcmd.CommandText = strSQL;
if (parametros != null)
dbcmd.Parameters.AddRange(parametros);
DbDataAdapter dbda = factory.CreateDataAdapter();
dbda.SelectCommand = dbcmd;
DataTable dt = new DataTable();
dbda.Fill(dt);
return dt;
EDIT 14/01/2013 (18:44)
I'm not longer retrieve the connection from DbProviderFactory, insted I'm using directly SqlConnection and SqlCommand. I know DbCommand and DbProvider are a base clase... but I think there is something more in there.. because the performance drasticaly increase like 300%!
It's not the fill method, because I already tried in the code shown before..
Anyway, I don't know the reason why but using a SqlConnection is much faster! Any idea? Maybe isn't making that bad execution plan made before?
SqlCommand objCmd = new SqlCommand(strSQL, sqlConn);
if (CommandTimeout != null)
objCmd.CommandTimeout = CommandTimeout.Value;
if (this.transaccion != null)
objCmd.Transaction = SQLtransaccion;
if (parametros != null)
objCmd.Parameters.AddRange(parametros);
DbDataReader dbReader = objCmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dbReader);
dbReader.Close();
return dt;
Any help will be greatly appreciated,
Thanks,
I found the solution!
It was parameters!
I was using a wrong type in the the List!
Parametross.Add(bd.MakeParameter("#val", "%" + txtFind.Text + "%",
DbType.String));
DbType.String vs. DbType.AnsiString
Although both DbType.String and DbType.AnsiString deal with character data, these datatypes are processed differently, and using the wrong data type can have a negative effect on the application’s performance. DbType.String identifies the parameter as a 2-byte Unicode value and is sent to the server as such.DbType.AnsiString causes the parameter to be sent as a multibyte character string. To avoid excessive string conversions, use:
DbType.AnsiString for char or varchar columns and parameters.
DbType.String for unichar and univarchar columns and parameters.
Source:
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc20066.0115/html/adonet/adonet49.htm
In my query there is a:
....
where Table.Col1 like #val
But the column type was varchar and I should use DbType.AnsiString, instead of DbType.String
Parametross.Add(bd.MakeParameter("#val", "%" + txtFind.Text + "%",
DbType.AnsiString));
In my huge table I was making a lot of unnecesary casts and this is the reason why the performance drastically fall down!
Hope this will help someone,

Passing an object collection as a parameter into SQL Server stored procedure

I have a general question on whether something can be done - and whether it will be the most efficient way of doing it !
To summarise: can I pass an object collection as a parameter to a stored procedure?
Let's say that I have a SQL Server table called Users [UserID, Forename, Surname]
and another table called Hobbies [HobbyID, UserID, HobbyName, HobbyTypeID]
This set up is to record multiple hobbies against a user.
In my application, I want to update the user record.
Normally - I would update the user table and then in code, loop through each hobby and update the hobbies table record by record.
If I'm updating the user forename and 2 of their hobbies, this would require 3 calls to the database.
(1 call to a stored procedure to update the forename/surname, and 2 calls to a stored procedure to update the 2 hobby records)
My question is:
Can I make just 1 call to the database by passing all the parameters to just 1 stored procedure.
eg.
intUserID = 1
strForename = "Edward"
strSurname = "ScissorHands"
dim objHobbyCollection as New List(Of Hobby)
'Assume that I have 2 hobby objects, each with their hobbyID, UserID, HobbyName & HobbyTypeID
Dim params As SqlParameter()
params = New SqlParameter() {
New SqlParameter("#UserID", intUserID),
New SqlParameter("#Forename", strForename),
New SqlParameter("#Surname", strSurname),
New SqlParameter("#Hobbies", objHobbyCollection)
}
Can I do this ? (and which way would be more efficient?)
What would the Stored Procedure look like ?
ALTER PROCEDURE [dbo].[User_Update]
#UserID INT
,#Forename NVARCHAR(50) = NULL
,#Surname NVARCHAR(50) = NULL
,#Hobbies ??????????????
Assuming SQL Server 2008+, you can do this using a table-valued parameter. First in SQL Server create a table type:
CREATE TYPE dbo.HobbiesTVP AS TABLE
(
HobbyID INT PRIMARY KEY,
HobbyName NVARCHAR(50),
HobbyTypeID INT
);
Then your stored procedure would say:
#Hobbies dbo.HobbiesTVP READONLY
In C# (sorry I don't know vb.net equivalent) it would be as follows (but if you just have one UserID, this doesn't need to be part of the collection, does it?):
// as Steve pointed out, you may need to have your hobbies in a DataTable.
DataTable HobbyDataTable = new DataTable();
HobbyDataTable.Columns.Add(new DataColumn("HobbyID"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyName"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyTypeID"));
// loop through objHobbyCollection and add the values to the DataTable,
// or just populate this DataTable in the first place
using (connObject)
{
SqlCommand cmd = new SqlCommand("dbo.User_Update", connObject);
cmd.CommandType = CommandType.StoredProcedure;
// other params, e.g. #UserID
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Hobbies", HobbyDataTable);
tvparam.SqlDbType = SqlDbType.Structured;
// ...presumably ExecuteNonQuery()
}

Inserting into two tables and Identity_Scope()

I am building a forum and I have two tables:
Threads
-------
ThreadID
UsersID
Date
ThreadTitle
ThreadParagraph
ThreadClosed
Topics
-----
TopicsID
Theme
Topics
Date
The ThreadID is connected to the users table with a primary key:
Topics.TopicsID(PK)==Threads.TopicID(FK)
First i insert into the Topics table and then to the Threads table. My goal is to obtain the ID of Topics.TopicID with Identity_Scope() and pass it to the second insert which is Threads.TopicID
Here is what i have done, but i am not sure if it is correct:
StringBuilder insertCommand = new StringBuilder();
insertCommand.Append("DECLARE #TopicsID int");
insertCommand.Append("INSERT INTO Topics(Theme,Topics,Date)");
insertCommand.Append("VALUES('#topic,#subTopic,GETDATE()')");
insertCommand.Append("SET #TopicsID = SCOPE_IDENTITY()");
insertCommand.Append("INSERT INTO Threads(UsersID,TopicsID,Date,ThreadTitle,ThreadParagraph,ThreadClosed)");
insertCommand.Append("VALUES('#uniqueIdentifier,#TopicsID,GETDATE(),#questionTitle,#questionParagraph,0')");
I have got all the otehr parameters obtained from the controls the users presses or feeds information into, so dont worry about them. All i am worried about is passing the same TopicID from the Topic table to Thread table (Column name: TopicID).
Both Magnus & Damien_The_Unbeliever are right - you have few syntax errors (or typos). Correct insert command should be something like
insertCommand.Append(#"
DECLARE #TopicSID int
INSERT INTO Topics(Theme,Topics,Date)
VALUES(#topic,#subTopic,GETDATE())
SET #TopicSID = SCOPE_IDENTITY()
INSERT INTO Threads(UsersID,TopicsID,Date,ThreadTitle,ThreadParagraph,ThreadClosed)
VALUES(#uniqueIdentifier,#TopicSID ,GETDATE(),#questionTitle,#questionParagraph,0)
");

Resources