Passing an array of objects to SQL stored procedure - c#

I have 2 tables
payment (payment_id, otherCosts, GarageCosts)
spareparts (payment_id, sparepartId, sparePartQty)
In payment table payment_id is autogenerated. Apart from otherCosts and garagecosts values, in my C# asp.net application there is an array of objects with
{ sparepartId : 'Somevalue', sparePartQty : 'somevalue' }
What I need to do is in a stored procedure first enter the record into payment table with garage costs and others costs value. Then return the last generated payment ID and enter it to spareParts table as paymentId for each of the value pairs in the array.
What is the way to achieve this? Please help.

Based on your tags I am going to assume we are talking about SQL Server / T-SQL.
You could do all this in one stored procedure:
CREATE PROCEDURE dbo.Foo ... /* input parameters */
AS
BEGIN
DECLARE #PaymentId int
INSERT INTO payment(otherCosts, GarageCosts) VALUES (...)
SET #PaymentId = SCOPE_IDENTITY()
INSERT INTO spareparts(payment_id, sparepartId, sparePartQty) VALUES(#PaymentId, ...)
END
GO
You may want to also look into ##IDENTITY but make sure you read about ##IDENTITY and SCOPE_IDENTITY and understand the risks associated with the first one.
If you need to have two separate sprocs you can do that too and here is how the first sproc would look like. Note that the #PaymentId is an output parameter which means that the caller can retrieve it and pass it to the second procedure.
CREATE PROCEDURE dbo.Foo
/* input parameters */
#PaymentId int OUT
AS
BEGIN
INSERT INTO payment(otherCosts, GarageCosts) VALUES (...)
SET #PaymentId = SCOPE_IDENTITY()
END
GO
Edit - after the scope of the question was clarified:
If you need to call the second stored procedure and pass it an array of parameters, with SQL Server 2008 or newer you can use TVP (Table Value Parameters). To see how you can use them in stored procedures and how you can pass them from C# code see Table Value Parameters in SQL Server 2008 and .NET (C#) or Table-Valued Parameters.
You can also use TVPs with the solution where you only have one sproc.

To solve your problem try this
First insert your data in payment table with otherCosts and GarageCosts.
Then create a procedure to get the latest stored payment_id from payment table
create procedure select_last_payment_id
as
begin
select top 1 payment_id
from payment
order by payment_id desc
end
Lastly get that payment_id by running stored procedure and assigning it to payment_id of spareparts table and storing spareparts data.
Hope it works for you.

Related

Stored procedure with parameters to return specific multiple columns, not all (*)

Currently, I have a stored procedure that returns all the columns (Select *). I am using this stored procedure to retrieve data from multiple tables, each table with different number of columns and obviously different column names, so Select * is convenient. However, now I need to retrieve only specific columns from each table so I want to pass the parameters something like this:
SELECT #param1, #param2, #param3, etc.
FROM #tableName
WHERE columnName = #tableId
The problem here is the number of parameters to be passed for the columns isn't set, as the table can have any number of columns. Is there a way to use some kind of loop or dynamic assignment so that I can pass any number of parameters as column names?
I know that I can filter out only the columns I want to use, and just leave out the rest, but that doesn't work in my case. I need the stored procedure to NOT return some specific columns with sensitive data.
I am using SQL Server 2008, ASP.NET MVC 4, and C# in my application.
If you are able to modify your stored procedure, you can easily put the required columns definitions as a parameter and use an auto-created temporary table:
CREATE PROCEDURE sp_GetDiffDataExample
#columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = N'SELECT ' + #columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
EXEC sp_executeSql #query
SELECT * FROM ##TempTable
DROP TABLE ##TempTable
END
In this case you don't need to create a temp table manually - it is created automatically.
Hope this helps.
You can just pass one parameter that is a comma-delimited string of the columns you want to select and build a dynamic sql string.
#sql = 'SELECT ' + #param + ' FROM MyTable...';
EXECUTE (#sql);
If you use a dynamic sql solution, you should take care to guard against sql injection attacks.
You might also consider continuing to get all columns from the stored procedure, and showing only the columns the user wants in the front end.
Although I really do not like the stored procedure approach for this problem (I agree with #Gusman that building the query in C# is a better approach) you can make a stored procedure work without opening yourself to SQL Injection attacks. The following example is one simple way to do this:
Let's say that the table in question has the columns named COL1, COL2, and COL3. The stored procedure would accept a varchar(max) parameter named #IncludeCols and have code like:
SELECT CASE WHEN #IncludeCols LIKE '%#COL1#%' THEN COL1 ELSE '' END AS COL1,
CASE WHEN #IncludeCols LIKE '%#COL2#%' THEN COL2 ELSE '' END AS COL2,
CASE WHEN #IncludeCols LIKE '%#COL3#%' THEN COL3 ELSE '' END AS COL3
FROM <Table name>
WHERE <Where clause>
Yes, a column of every name will be returned, but data will only come from columns whose names are in the parameter. For example, if you wanted COL1 and COL3, the parameter value would be #COL1#COL3#. The # is important and must be on each side of every column name or any LIKE clause could get a false positive match.
If this is to export data to Excel then the best approach is likely to have a stored procedure that returns all of your columns minus the ones that should never be exported (passwords, other protected data, timestamps perhaps) and then filter out any additional unwanted columns in your front end.
You should never be using SELECT * for several reasons. Define what your application needs and program to that.

Cannot get a stored procedure table output into my .NET Application

I have a stored procedure that looks like this:
DECLARE #A TABLE ( TabYear int, Std decimal(18,2))
DECLARE #B TABLE ( TabYear int, Std decimal(18,2))
BEGIN
DECLARE #FinalTable TABLE (TabYear int, HoldayA decimal(18,2), HolidayB decimal(18,2))
BEGIN
INSERT INTO #A(TabYear, std)
Select ... from ...
INSERT INTO #B(TabYear, Std)
Select ... from ...
END
INSERT INTO #FinalTable(TabYear, HoldayA, HoldayB)
SELECT
A.TabYear, a.Std, U.Std
FROM
#A A
LEFT JOIN
#B U ON a.TabYear = U.TabYear
END
Select * from #FinalTable
Now I want to get that into a DATASET within my .NET application using the TableAdapter Wizard. I choose :"Use existing stored procedure" and choose the procedure (above) but as Data Column I get only Column1. It looks like the DataTableAdapter does not recognize the table. If I let the SP run on the server everything is fine. I get the table as I wish. I also checked if the wizard recognizes other SP's I am working with on the table and that works fine. But I have to admit that all the other SP's I use are straigth Select commands nothing with querying #Tables like in this SP. Could anyone help me get the table into my application. I do not have go go with the DataSet necessarily a code solution getting the data into an array or something would help me too.
You cannot achieve this using the TableAdapter Wizard because your stored procedure has no defined output because its dynamically created. You must manually construct the TableAdapter in your code.
Probably because you SELECT * from a table that's defined within the sproc, so the analyzer can't determine the schema. Either define the columns outside of the wizard or change your sproc to select specific columns (although the wizard won't be able to determine the type, so you'll still have to edit the data table schema).
You could also turn the sproc into one SELECT that uses subqueries instead of filling a table variable, but that may have been done for performance reasons...
I do not have go go with the DataSet necessarily
That's fine, but you'll still hove to map the columns of the result set to a structure in C# code at some point.
Wizards only take you so far; at some point you need to start casting your own spells...

Insert multiple sql rows via stored proc

I have looked a some related topics but my question isn't quite answered:
C# - Inserting multiple rows using a stored procedure
Insert Update stored proc on SQL Server
Efficient Multiple SQL insertion
I have the following kind of setup when running my stored procedure in the code behind for my web application. The thing is I am now faced with the possibility of inserting multiple products and I would like to do it all in one ExecuteNonQuery rather than do a foreach loop and run it n number of times.
I am not sure how to do this, or if it can be, with my current setup.
The code should be somewhat self explanatory but if clarification is needed let me know. Thanks.
SqlDatabase database = new SqlDatabase(transMangr.ConnectionString);
DbCommand commandWrapper = StoredProcedureProvider.GetCommandWrapper(database, "proc_name", useStoredProc);
database.AddInParameter(commandWrapper, "#ProductID", DbType.Int32, entity._productID);
database.AddInParameter(commandWrapper, "#ProductDesc", DbType.String, entity._desc);
...more parameters...
Utility.ExecuteNonQuery(transMangr, commandWrapper);
Proc
ALTER PROCEDURE [dbo].[Products_Insert]
-- Add the parameters for the stored procedure here
#ProductID int,
#Link varchar(max)
#ProductDesc varchar(max)
#Date DateTime
AS BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[Prodcuts]
(
[CategoryID],
[Link],
[Desc],
[Date]
)
VALUES
(
#ProductID,
#Link,
#ProductDesc,
#Date
)
END
You should be fine running your stored procedure in a loop. Just make sure that you commit rarely, not after every insert.
For alternatives, you have already found the discussion about loading data.
Personally, I like SQL bulk insert of the form insert into myTable (select *, literalValue from someOtherTable);
But that will probably not do in your case.
You could pass all your data as a table value parameter - MSDN has a pretty good write up about it here
Something along the lines of the following should work
CREATE TABLE dbo.tSegments
(
SegmentID BIGINT NOT NULL CONSTRAINT pkSegment PRIMARY KEY CLUSTERED,
SegCount BIGINT NOT NULL
);
CREATE TYPE dbo.SegmentTableType AS TABLE
(
SegmentID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.sp_addSegments
#Segments dbo.SegmentTableType READONLY
AS
BEGIN
MERGE INTO dbo.tSegments AS tSeg
USING #Segments AS S
ON tSeg.SegmentID = S.SegmentID
WHEN MATCHED THEN UPDATE SET T.SegCount = T.SegCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(tSeg.SegmentID, 1);
END
Define the commandWrapper and parameters for the command outside of the loop and then with in the loop you just assign parameter values and execute the proc.
SqlDatabase database = new SqlDatabase(transMangr.ConnectionString);
DbCommand commandWrapper = StoredProcedureProvider.GetCommandWrapper(database, "proc_name", useStoredProc);
database.AddInParameter(commandWrapper, "#ProductID", DbType.Int32 );
database.AddInParameter(commandWrapper, "#ProductDesc", DbType.String);
...more parameters...
foreach (var entity in entitties)
{
database.SetParameterValue(commandWrapper, "#ProductID",entity._productID);
database.SetParameterValue(commandWrapper, "#ProductDesc",entity._desc);
//..more parameters...
Utility.ExecuteNonQuery(transMangr, commandWrapper);
}
Not ideal from a purist way of doing things, but sometimes one is limited by frameworks and libraries, and that you are forced to call stored procedures in a certain way, bind parameters in a certain way, and that connections are managed by pools as part of your framework.
In such circumstances, a method we have found to work is to simply write your stored procedure with a lot of parameters, usually a name followed by a number, e.g. #ProductId1, #ProductDesc1, #ProductId2, #ProductDesc2 up to a number you decide, possibly say 32.
You can use some form of scripting language to produce the lines for this.
You can get the stored procedure to insert all the values first into a table parameter that allows nulls, then do bulk inserts / merges on this data in a way similar to Johnv2020's answer. You might remove the null rows first.
It will usually be more efficient than doing it one at a time (partly because of the database operations itself, and partly because of your framework's overheads in getting the connection to call the procedure etc.)

Cannot alter table with LINQ TO SQL and stored procedure

I have to alter the table Statistic when I add a new metric in the table Metric I add a column in table Statistic.
I used a stored procedure that allows me to alter the table Statistic so the code :
CREATE PROCEDURE dbo.addnewmetricInstat
(
#MetricName varchar(254),
#TypeMetric varchar(254)
)
AS
IF (#TypeMetric='int')
Begin
alter table Statistic
add #MetricName int null
end
ELSE if (#TypeMetric='string')
begin
alter table Statistic
add #MetricName varchar(254) null
end
Then I successfully called the stored procedure but the columns is not added. The code I used in C# for calling this stored procedure is:
using (DataClassesDataContext db = new DataClassesDataContext("Data Source=EMEA-TUN-WS0367\\SQLEXPRESS;Initial Catalog=Perfgas;Integrated Security=True"))
{
db.addnewmetricInstat(metric.MetricName, metric.Type);
db.SubmitChanges();
}
First you shoud not be trying to call the sp from your application until you have tested it in SSMS. That way you know if the problem is your call or the sp. It will save you much debugging time if you do this.
Your proc is the problem. You will need to use dynamic SQl for this. Right now you are trying to add a column called MetericName because you didn't use the variable. This would work once, but of course the second time you run it, you will get an error becasue teh column already exists. However you can;t just throw a variable into an alter table statement, you must use dynamic SQL.

MySql Batching Stored Procedure Calls with .Net / Connector?

Is there a way to batch stored procedure calls in MySql with the .Net / Connector to increase performance?
Here's the scenario... I'm using a stored procedure that accepts a few parameters as input. This procedure basically checks to see whether an existing record should be updated or a new one inserted (I'm not using INSERT INTO .. ON DUPLICATE KEY UPDATE because the check involves date ranges, so I can't really make a primary key out of the criteria).
I want to call this procedure a lot of times (let's say batches of 1000 or so). I can of course, use one MySqlConnection and one MySqlCommand instance and keep changing the parameter values, and calling .ExecuteNonQuery().
I'm wondering if there's a better way to batch these calls?
The only thought that comes to mind is to manually construct a string like 'call sp_myprocedure(#parama_1,#paramb_1);call sp_myprocedure(#parama_2,#paramb2);...', and then create all the appropriate parameters. I'm not convinced this will be any better than calling .ExecuteNonQuery() a bunch of times.
Any advice? Thanks!
EDIT: More info
I'm actually trying to store data from an external data source, on a regular basis. Basically I'm taking rss feeds of Domain auctions (from various sources like godaddy, pool, etc.), and updating a table with the auction info using this stored procedure (let's call it sp_storeSale). Now, in this table that the sale info gets stored, I want to keep historical records for sales for a given domain, so I have a domain table, and a sale table. The sale table has a many to one relationship with the domain table.
Here's the stored procedure:
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE PROCEDURE `DomainFace`.`sp_storeSale`
(
middle VARCHAR(63),
extension VARCHAR(10),
brokerId INT,
endDate DATETIME,
url VARCHAR(500),
category INT,
saleType INT,
priceOrBid DECIMAL(10, 2),
currency VARCHAR(3)
)
BEGIN
DECLARE existingId BIGINT DEFAULT NULL;
DECLARE domainId BIGINT DEFAULT 0;
SET #domainId = fn_getDomainId(#middle, #extensions);
SET #existingId = (
SELECT id FROM sale
WHERE
domainId = #domainId
AND brokerId = #brokerId
AND UTC_TIMESTAMP() BETWEEN startDate AND endDate
);
IF #existingId IS NOT NULL THEN
UPDATE sale SET
endDate = #endDate,
url = #url,
category = #category,
saleType = #saleType,
priceOrBid = #priceOrBid,
currency = #currency
WHERE
id = #existingId;
ELSE
INSERT INTO sale (domainId, brokerId, startDate, endDate, url,
category, saleType, priceOrBid, currency)
VALUES (#domainId, #brokerId, UTC_TIMESTAMP(), #endDate, #url,
#category, #saleType, #priceOrBid, #currency);
END IF;
END
As you can see, I'm basically looking for an existing record that is not 'expired', but has the same domain, and broker, in which case I assume the auction is not over yet, and the data is an update to the existing auction. Otherwise, I assume the auction is over, it is a historical record, and the data I've got is for a new auction, so I create a new record.
Hope that clears up what I'm trying to achieve :)
I'm not entirely sure what you're trying to do but it sounds kinda house-keeping or maintenance related so I won't be too ashamed at posting the following suggestion.
Why dont you move all of your logic into the database and process it all server side ?
The following example uses a cursor (shock/horror) but it's perfectly acceptable to use them in such circumstances.
If you can avoid using cursors at all - great, but the main point of my suggestion is about moving the logic from your application tier back into the data tier to save on the round trips. You'd call the following sproc once and it would process the entire range of data in single call.
call house_keeping(curdate() - interval 1 month, curdate());
Also, if you can provide just a bit more information about what you're trying to do we might be able to suggest other approaches.
Example stored procedure
drop procedure if exists house_keeping;
delimiter #
create procedure house_keeping
(
in p_start_date date,
in p_end_date date
)
begin
declare v_done tinyint default 0;
declare v_id int unsigned;
declare v_expired_date date;
declare v_cur cursor for
select id, expired_date from foo where
expired_date between p_start_date and p_end_date;
declare continue handler for not found set v_done = 1;
open v_cur;
repeat
fetch v_cur into v_id, v_expired_date;
/*
if <some condition> then
insert ...
else
update ...
end if;
*/
until v_done end repeat;
close v_cur;
end #
delimiter ;
Just incase you think I'm completely mad in suggesting cursors you might want to read this
Optimal MySQL settings for queries that deliver large amounts of data?
Hope this helps :)

Categories