SQLException was unhandled, Dataset -Table Adapter - vb.net

I have the following stored procedure and I'm calling it from my Windows Forms application DataSet like this:
Me.TransactionTableAdapter.spPaymentApply(130, iAmount, Now)
Although I provide the CustomerID, (and stepping in the code to see if it's actually there - and it is) I get the following error during execution:
Procedure or function 'PaymentApply' expects parameter '#CustomerID', which was not supplied.
Here is my SP:
USE [dbPB]
GO
/****** Object: StoredProcedure [dbo].[PaymentApply] Script Date: 05/30/2013 18:34:01 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[PaymentApply]
#CustomerID int,
#Amount int,
#AsOfDate datetime
AS
WITH Totals AS (
SELECT
T.*,
RunningTotal =
Coalesce (
(SELECT Sum(S.Buyin - Coalesce(S.CreditPaid, 0))
FROM [Transaction] S
WHERE
T.CustomerID = S.CustomerID
AND S.Type = 'Credit'
AND S.Buyin > Coalesce(T.CreditPaid, 0)
AND (
T.Starttime > S.Starttime
OR (
T.Starttime = S.Starttime
AND T.TransactionID > S.TransactionID
)
)
),
0)
FROM
[Transaction] T
WHERE
CustomerID = #CustomerID
AND T.Type = 'Credit'
AND T.Buyin > Coalesce(T.CreditPaid, 0)
)
UPDATE T
SET
T.EndTime = P.EndTime,
T.CreditPaid = Coalesce(T.CreditPaid, 0) + P.CreditPaid
FROM
Totals T
CROSS APPLY (
SELECT TOP 1
V.*
FROM
(VALUES
(T.Buyin - Coalesce(T.CreditPaid, 0), #AsOfDate),
(#Amount - RunningTotal, NULL)
) V (CreditPaid, EndTime)
ORDER BY
V.CreditPaid,
V.EndTime DESC
) P
WHERE
T.RunningTotal <= #Amount
AND #Amount > 0;
;
VB Code:
Private Sub btnTransProcess_Click(sender As Object, e As EventArgs) Handles btnTransProcess.Click
Dim iAmount As Integer
Dim drv As DataRowView = CType(Me.cboCustomerName.SelectedItem, DataRowView)
Dim SelCustId As Integer
SelCustId = drv.Item("CustomerID")
Try
iAmount = CType(txtCreditPayment.Text, Integer)
Catch ex As Exception
MessageBox.Show("Enter a valid Credit Payment Amount!", "Invalid Amount", MessageBoxButtons.OK, MessageBoxIcon.Warning)
End Try
MsgBox(SelCustId)
Me.TransactionTableAdapter.spPaymentApply(130, iAmount, Now)
'
'Dim command As New SqlCommand(queryString, connection)
'command.Parameters.AddWithValue("#CustomerID", 123)
End Sub

What happened ?
You didn't provide the parameter.
But.. I double-checked...
Maybe you're not calling it the right way ?
Have you tried something like this :
Dim queryString As String = "exec PaymentApply"
Using connection As New SqlConnection(ConnStrg)
connection.Open()
Dim command As New SqlCommand(queryString, connection)
command.Parameters.AddWithValue("#CustomerID", 123)
// ... same for other non-nullable parameters
Dim reader As SqlDataReader = command.ExecuteReader()
End Using
End Try
Why should I do this ?
When you call your Stored Procedure from you code, you don't provide the #CustomerID int parameter.
Since the parameter is not nullable it expects a value and will not go any further until you give it one.
If you want it to be nullable you can set a default value to it. This way he will take this value if you don't provide a new one. Exemple :
From your code, you pass a parameter like this :
someCommand.Parameters.AddWithValue("#CustomerID", 123)
If you want your parameter to be nullable, do something like this in SQL
#CustomerID int = 123

It sounds like you're using a Table Adapter instead of a Command Object.
see: Table Adapter
and TableAdapter Query Configuration Wizard
Your call to Me.TransactionTableAdapter.spPaymentApply(130, iAmount, Now) looks like it should provide 130 as the CustomerID- but if the code does not compile/work properly, perhaps you should use the configuration wizard.
Or, consider the use of a command object instead. The use of both is outlined here:
How to: Create and Execute an SQL Statement that Returns No Value

Related

How to create web Api execute stored procedure

I work on SQL server 2012 and web API entity framework .NET core 2.2
so I face issue I can't implement web API execute stored Procedure below
Create proc ItemCalculateStock
#ItemId int = NULL,
#InventoryLocation int=NULL
as
begin
SELECT i.itemName,l.InventoryName, SUM(case when QTY > 0 then QTY else 0 end) as PurchasedItem,SUM(case when QTY < 0 then -QTY else 0 end) as ConsumItems,SUM(case when QTY > 0 then QTY else 0 end) + SUM(case when QTY < 0 then QTY else 0 end) as remaining
FROM [dbo].[Invenroty] n with(nolock)
inner join [dbo].[InventoryLocations] l with(nolock) on l.id=n.InventoryLocID
inner join [dbo].[Items] i with(nolock) on n.itemid=i.id
inner join [dbo].[TransactionTypes] t with(nolock) on n.transactionTypeId=t.ID and InventoryLocID=case when #InventoryLocation is null then n.InventoryLocID else #InventoryLocation end
and i.id=case when #ItemId is null then n.itemid else #ItemId end
GROUP BY i.itemName,l.InventoryName
end
so How to get result of stored procedure on web API using Entity Framework .NET core 2.2
[HttpGet("CalculateInventoryData")]
public IActionResult CalculateInventoryData([FromQuery]int optionId, [FromQuery] int ItemId, [FromQuery] int InventoryLocation)
{
// here how to get stored procedure result here
// so i ask question to know how to get result of stored procedure above
}
to call API I use the link below :
https://localhost:44374/api/Inventory/getInventoryData?optionId=1&ItemId=2&InventoryLocation=1
updated Post
I try below
context.Database.ExecuteSqlCommand("ItemCalculateStock #OptionId ,#ItemId,#InventoryLocation ", parameters: new[] {optionId,ItemId,InventoryLocation});
i get error cannot implicitly convert type int into to Microsoft .aspnetcore.mvc.action result
so how to solve issue
The code to execute a stored procedure without parameters is as follows:
SqlConnection conn=new SqlConnection(“connectionString”);
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand();
da.SelectCommand.Connection = conn;
da.SelectCommand.CommandText = "NameOfProcedure";
da.SelectCommand.CommandType = CommandType.StoredProcedure;
The code to execute a stored procedure with parameters is as follows (we can declare the function that calls the stored procedure as ExeProcedure(string inputdate)):
param = new SqlParameter("#ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.Input;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);
This adds an input parameter. If you need to add output parameters:
param = new SqlParameter("#ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.Output;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);
To get the return value of the stored procedure:
param = new SqlParameter("#ParameterName", SqlDbType.DateTime);
param.Direction = ParameterDirection.ReturnValue;
param.Value = Convert.ToDateTime(inputdate);
da.SelectCommand.Parameters.Add(param);
For more information, please refer to this post:How to run stored procedures in Entity Framework Core?

How do I create a single query for multiple optional search parameters in a parameterized query?

I'm working on a Report form where a user inputs multiple values on textboxes and I take those values and I perform parameterized query search.
My current issue is a report form that has 8 total parameters but has a minimum requirement of three values being: Store_ID, From_date and To_Date. What I want to do is to have a single query that performs a search with the given values. So that I wont have to create cases or if else statement for each possible combination of those 8 parameters.
I would appreciate if someone can provide an example of on how to do that.
For additional context is for a report form Web application build using Visual Studio .NET with Visual basic.
EDIT: I made the modifications to the query as suggested in the comments.
My current query works but only if all the values are provided. But if I don't add one of the optional query parameters I get the following error:
System.FormatException: 'Input string was not in a correct format.'
I suspect this error is because I didn't assign a value to a parameter.
Or that perhaps the AND H.Rgst_ID IS NULL statement might be causing a SQL error and doesn't perform the search.
How can I fix this error so that my query accepts blank/null inputs?
This is my current code:
SQLControl
Imports System.Data.SqlClient
Public Class SQLControl
Private ReadOnly ConStr As String = "String"
Private DBCmd As SqlCommand
'Query Parameters
Public Params As New List(Of SqlParameter)
'This generates a blank sqlclient class with the deafult connection string
Public Sub New()
End Sub
'Allow connection string override
Public Sub New(connectionString As String)
ConStr = connectionString
End Sub
'Execute Query Sub
Public Function ExecQuery(query As String) As DataTable
Dim DBDT = New DataTable
Using DBCon As New SqlConnection(ConStr),
DBCmd As New SqlCommand(query, DBCon)
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
Params.Clear()
DBCon.Open()
DBDT.Load(DBCmd.ExecuteReader)
End Using
Return DBDT
End Function
'Add Params
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
Web Form code:
Protected Sub ExecuteButton_Click(sender As Object, e As EventArgs) Handles ExecuteButton.Click
Dim StoreID As Integer
Dim TransID As Integer
Dim RgstID As Integer
Dim FromDate As DateTime
Dim ToDate As DateTime
Dim DiscCD As Integer 'This is selected from the item list
Dim DiscPercent As Double
Dim DiscAmount As Double
'The minimum required search paraeters are Store_ID, From_Date and To_Date. The rest of the parameters are optional
'StoreID Validation
If Not Integer.TryParse(StoreIDTextbox.Text, StoreID) Then
MsgBox("Invalid input. Please enter Store ID.")
Exit Sub
End If
'FromDateValidation
If Not DateTime.TryParse(FromDateTextbox.Text, FromDate) Then
MsgBox("Invalid input. Please enter from date")
Exit Sub
End If
'ToDateValidation
If Not DateTime.TryParse(ToDateTextbox.Text, ToDate) Then
MsgBox("Invalid input. Please enter to date.")
Exit Sub
End If
Integer.Parse(RegisterIDTextbox.Text, RgstID)
Integer.Parse(TransactionIDTextbox.Text, TransID)
Integer.Parse(ListBox1.SelectedValue, DiscCD)
Double.Parse(DiscountPercentTextbox.Text, DiscPercent)
Double.Parse(DiscountAmtTextbox.Text, DiscAmount)
Dim pct As Double
pct = DiscPercent / 100
Dim amt As Double
amt = DiscAmount * -1
'Adds parameter to the query
SQL.AddParam("#Str_ID", StoreID)
SQL.AddParam("#FromDate", FromDate)
SQL.AddParam("#ToDate", ToDate)
SQL.AddParam("#Rgst_ID", RgstID)
SQL.AddParam("#Trans_ID", TransID)
SQL.AddParam("#DiscType", DiscCD)
SQL.AddParam("#DisPct", pct)
SQL.AddParam("#DisAmt", amt)
Dim dt As DataTable
Try
dt = SQL.ExecQuery("SELECT H.Str_ID, H.Rgst_ID, H.Tran_ID, L.Tran_LN_Num, H.Bus_DT, H.Emp_ID, H.Cust_ID, LD.Auth_Emp_ID, L.Ext_Prc, LD.Disc_Amt, D.Descr, LD.Disc_Pct, LD.DisC_CD
FROM twOLTP.dbo.Transaction_Header H
INNER JOIN twOLTP.dbo.LN_Detail L ON (H.Str_ID = L.Str_ID AND H.Rgst_ID = L.Rgst_ID AND H.Tran_ID = L.Tran_ID)
INNER JOIN twOLTP.dbo.LN_Discount LD ON (L.Str_ID = LD.Str_ID AND L.Rgst_ID = LD.Rgst_ID AND L.Tran_ID = LD.Tran_ID AND L.Tran_Ln_Num = LD.Tran_Ln_Num)
LEFT JOIN twOLTP.dbo.Discount D ON (LD.Disc_CD = D.Disc_CD)
WHERE (H.Str_ID = #Str_ID)
AND (H.Bus_DT >= #FromDate)
AND (H.Bus_DT <= #ToDate)
AND (H.Rgst_ID IS NULL OR H.Rgst_ID = #Rgst_ID)
AND (H.Tran_ID IS NULL OR H.Tran_ID = #Trans_ID)
AND (LD.DisC_CD IS NULL OR LD.DisC_CD = #DiscType)
AND (LD.Disc_Pct IS NULL OR LD.Disc_Pct = #DisPct)
AND (LD.Disc_Amt IS NULL OR LD.Disc_Amt = #DisAmt) ")
Catch ex As Exception
MsgBox(ex.Message)
Exit Sub
End Try
GridView1.DataSource = dt
GridView1.DataBind()
EDIT I was able to solve the problem
For my query to accept blank/null inputs i had t do the following.
WHERE ((H.Rgst_ID = #Rgst_ID) Or (#Rgst_ID Is NULL Or #Rgst_ID = ''))
Thanks for the help
Your where clause might look like this... the first three params are required, the others are optional
WHERE
Store_ID = #Store_ID
AND
From_date = #From_date
AND
To_Date = #To_Date
AND
ISNULL(#param4,param_name_4) = param_name_4
AND
ISNULL(#param5,param_name_5) = param_name_5
AND
ISNULL(#param6,param_name_6) = param_name_6
AND
ISNULL(#param7,param_name_7) = param_name_7
AND
ISNULL(#param8,param_name_8) = param_name_8
As Dan mentioned in the comments under the question, you want to be able to maintain SARGability here. For a simple query, this means you can use an OR with an IS NULL on the parameter like this below:
SELECT ...
FROM dbo.YourTable YT
WHERE StoreID = #StoreID
AND FromDate = #FromDate
AND ToDate = #ToDate
AND (OtherColumn = #OtherParameter OR #OtherParameter IS NULL)
AND (AnotherColumn = #AnotherParameter OR #AnotherParameter IS NULL)
AND (SomeColumn = #SomeParameter OR #SomeParameter IS NULL)
AND (YetAnotherColumn = #YetAnotherParameter OR #YetAnotherParameter IS NULL)
AND (FinalColumn = #FinalParameter OR #FinalParameter IS NULL)
OPTION (RECOMPILE);
You need to, however, ensure you have OPTION (RECOMPILE) as SQL Server would generate a single plan based on this query. This is bad as the query has many different ways of running, and so a plan where 1 parameter has a non-NULL value would be vastly different to where none of them do. OPTION (RECOMPILE) forces the data engine to regenerate the plan as a result.
This, however, can come at a cost, and sometimes it better to stick to the "older" method us using dynamic SQL to generate the statement. This means that different statements are run, depending on what parameters have non-NULL values.
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'SELECT ...' + #CRLF +
N'FROM dbo.YourTable YT' + #CRLF +
N'WHERE YT.StoreID = #StoreID' + #CRLF +
N' AND YT.FromDate = #FromDate' + #CRLF +
N' AND YT.ToDate = #ToDate' + #CRLF +
CASE WHEN #OtherParameter IS NOT NULL THEN N' AND YT.OtherColumn = #OtherParameter' + #CRLF + END +
CASE WHEN #AnotherParameter IS NOT NULL THEN N' AND YT.AnotherColumn = #AnotherParameter' + #CRLF + END +
CASE WHEN #SomeParameter IS NOT NULL THEN N' AND YT.SomeColumn = #SomeParameter' + #CRLF + END +
CASE WHEN #YetAnotherParameter IS NOT NULL THEN N' AND YT.YetAnotherColumn = #YetAnotherParameter' + #CRLF + END +
CASE WHEN #FinalParameter IS NOT NULL THEN N' AND YT.FinalColumn = #FinalParameter' + #CRLF + END + N';';
EXEC sys.sp_executesql #SQL,
N'#OtherParameter int, #AnotherParameter varchar(30), #SomeParameter date, #YetAnotherParameter decimal(12,2), #FinalParameter tinyint',
#OtherParameter,
#AnotherParameter,
#SomeParameter,
#YetAnotherParameter,
#FinalParameter;
Notice you need to declare all the parameters in the 2nd parameter for sys.sp_executesql and still pass them all, but if they have the value NULL then the executed query won't use them in the WHERE.

Returning Unique IDs from Oracle Update statement in VB.NET

I am trying to update a selection of rows in an Oracle table we are using to handle messages. Because this table is busy, it would be best if the update could return the UniqueIDs of the rows it updated in an atomic transaction. I modified a code sample I found on StackOverflow to look like the following, but when I examine the parameter "p", I don't find any information coming back from the update statement, as I expected.
Any suggestions for modifying either the .NET code that is setting up the Oracle call, or modifying the Oracle SQL statement itself?
Dim connectString As String = data source=ORA1;user id=MESSAGEBOX;password=MESSAGEBOX
Dim conn As New OracleConnection(connectString)
If conn.State <> ConnectionState.Open Then
conn.Open()
End If
Dim transaction As OracleTransaction = conn.BeginTransaction()
Dim cmd As New OracleCommand()
cmd.Connection = conn
cmd.CommandText = "BEGIN UPDATE MESSAGE_TABLE SET C_WAS_PROCESSED = 2 WHERE C_ID IN (SELECT * FROM(SELECT C_ID FROM MESSAGE_TABLE WHERE C_WAS_PROCESSED = 0 AND C_CREATED_DATE_TIME < CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE) ORDER BY C_MESSAGE_PRIORITY, C_ID) WHERE ROWNUM < 16) RETURNING C_ID BULK COLLECT INTO :C_ID; END;"
cmd.CommandType = CommandType.Text
cmd.BindByName = True
cmd.ArrayBindCount = 15
Dim p As New OracleParameter()
p.ParameterName = "C_ID"
p.Direction = ParameterDirection.Output
p.OracleDbType = OracleDbType.Int64
p.Size = 15
p.ArrayBindSize = New Integer() {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}
p.CollectionType = OracleCollectionType.PLSQLAssociativeArray
cmd.Parameters.Add(p)
Dim nRowsAffected As Integer = cmd.ExecuteNonQuery()
transaction.Commit()
conn.Close()
conn.Dispose()
I believe that problem here is that SQL variable being RETURNED BULK COLLECT INTO needs to be of a TABLE type, not a simple number type.
Alter your SQL to include a declaration block like follows:
cmd.CommandText = "DECLARE TYPE IDS IS TABLE OF MESSAGE_TABLE.C_ID%TYPE; C_ID IDS;
BEGIN UPDATE MESSAGE_TABLE SET C_WAS_PROCESSED = 2 WHERE C_ID IN (SELECT * FROM(SELECT C_ID FROM MESSAGE_TABLE WHERE C_WAS_PROCESSED = 0 AND C_CREATED_DATE_TIME < CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE) ORDER BY C_MESSAGE_PRIORITY, C_ID) WHERE ROWNUM < 16) RETURNING C_ID BULK COLLECT INTO :C_ID; END;"
I got the table declaration syntax from this oracle-base.com link.

How to determine if sql INSERTED or UPDATED?

Say I have a function like this:
Private Function addNicheMerge(ByVal tyNicheMergePOLE As typeNicheMergePOLE) As Integer Implements IGenie.addNicheMerge
Dim objParameterValues As New clsParameterValues
Dim objCon As DbConnection
Dim paramValues() As DbParameter
Dim iConnectionBLL As DataAccessLayer.Connection.iConnectionBLL
iConnectionBLL = New clsConnectionBLL()
Dim intCount As Integer
Try
tyAcornCollision = New typeAcornCollision
objParameterValues = New clsParameterValues
objCon = iConnectionBLL.getDatabaseTypeByDescription("GENIE2")
Using objCon
Dim strSQL As String
strSQL = "If NOT Exists (SELECT * FROM dbNicheMergeLog WHERE MasterID=#MasterID AND ChildID = #ChildID) "
strSQL = strSQL & "INSERT INTO dbNicheMergeLog (MasterID, ChildID, DateAdded, GenieUpdated, done, CheckedByUser, MergeTypeID, Usercode, LastUpdated) VALUES (#MasterID, #ChildID, getDate(), 0, 0, #CheckedByUser, #MergeTypeID, '', " & _ "GetDate()) Else Update dbNicheMergeLog SET LastUpdated = getdate() WHERE MasterID=#MasterID AND ChildID = #ChildID"
objParameterValues.AssignParameterValues("#MasterID", tyNicheMergePOLE.MasterID, 1)
objParameterValues.AssignParameterValues("#ChildID", tyNicheMergePOLE.ChildID, 1)
objParameterValues.AssignParameterValues("#MergeTypeID", tyNicheMergePOLE.MergeTypeID, 1)
objParameterValues.AssignParameterValues("#CheckedByUser", 0, 1)
paramValues = objParameterValues.getParameterValues
intCount = clsDatabaseHelper.ExecuteNonQuery(objCon, CommandType.Text, strSQL, paramValues)
Return intCount
End Using
Catch ex As Exception
Return -2
Finally
' If objCon.State = ConnectionState.Open Then
' objCon.Close()
' End If
objCon = Nothing
End Try
End Function
Is there a way to determine whether an insertion or update took place?
if you are using sql server, you could use an output clause to determine if an update happened
Update dbNicheMergeLog SET LastUpdated =
getdate() WHERE MasterID=#MasterID AND
ChildID = #ChildID
OUTPUT count(*)
Then in vb use
dim updateCount as int = Convert.ToInt32(clsDatabaseHelper.ExecuteScalar())
Try this SQL:
WITH new (
MasterID
,ChildID
,DateAdded
,GenieUpdated
,done
,CheckedByUser
,MergeTypeID
,Usercode
,LastUpdated
)
AS(
SELECT
#MasterID
,#ChildID
,getDate()
,0
,0
,#CheckedByUser
,#MergeTypeID
,''
,getdate()
)
MERGE INTO dbNicheMergeLog t
USING new
ON t.MasterID = new.MasterID
AND t.ChildID = new.ChildID
WHEN NOT MATCHED BY TARGET THEN
INSERT (
MasterID
,ChildID
,DateAdded
,GenieUpdated
,done
,CheckedByUser
,MergeTypeID
,Usercode
,LastUpdated
)
VALUES (
new.MasterID
,new.ChildID
,new.DateAdded
,new.GenieUpdated
,new.done
,new.CheckedByUser
,new.MergeTypeID
,new.Usercode
,new.LastUpdated
)
OUTPUT $action as Action
;
You can use
Output deleted.*
in your update query to get the results that were lost on update, something like this:
Create Table #abctest(
Id int identity,Name varchar(200))
Insert into #abctest(Name)
Values('abcd'),('bcde'),('cdef'),('defg'),('efgh'),('fghi')
Update #abctest Set Name='xyz' OUTPUT Deleted.* Where Id=2
The above query will give you an output
Id Name
2 bcde

Exception from Stored Procedure not caught in .NET using SqlDataAdapter.Fill(DataTable)

I have a Stored Procedure that I am executing from VB.NET. The SP should insert records into a table and return a set to the calling app. The set returned are the records that were inserted.
If the INSERT fails, the exception is caught and re-thrown in the SP, but I never see the exception in my application. The severity level is 14, so I should see it.
Here is the stored procedure:
BEGIN TRY
BEGIN TRANSACTION
-- Declare local variables
DECLARE #DefaultCategoryID AS BIGINT = 1 -- 1 = 'Default Category' (which means no category)
DECLARE #DefaultWeight AS DECIMAL(18,6) = 0
DECLARE #InsertionDate AS DATETIME2(7) = GETDATE()
DECLARE #SendToWebsite AS BIT = 0 -- 0 = 'NO'
DECLARE #MagentoPartTypeID AS BIGINT = 1 -- For now, this is the only part type we are importing from COPICS ('simple' part type)
DECLARE #NotUploaded_PartStatusID AS TINYINT = 0 -- 0 = 'Not Uploaded'
DECLARE #Enabled_PartStatusID AS TINYINT = 1 -- 1 = 'Enabled'
DECLARE #Disabled_PartStatusID AS TINYINT = 2 -- 2 = 'Disabled'
-- Get the part numbers that will be inserted (this set will be returned to calling procedure).
SELECT c.PartNumber
FROM
COPICSPartFile c
LEFT JOIN Part p on c.PartNumber = p.PartNumber
WHERE
p.PartNumber IS NULL
-- Insert new records from COPICSPartFile (records that don't exist - by PartNumber - in Part table)
INSERT INTO Part
([PartNumber]
,[ReplacementPartNumber]
,[ShortDescription]
,[ListPrice]
,[PartStatusTypeID]
,[Weight]
,[CategoryID]
,[DateInserted]
,[SendToWebsite]
,[FileName]
,[MagentoPartTypeID]
,[PrintNumber])
SELECT
c.PartNumber
,c.ReplacementPartNumber
,c.ShortDescription
,c.ListPrice
,CASE WHEN c.PartStatusTypeID = #Enabled_PartStatusID THEN #NotUploaded_PartStatusID ELSE #Disabled_PartStatusID END
,#DefaultWeight
,#DefaultCategoryID
,#InsertionDate
,#SendToWebsite
,#FileName
,#MagentoPartTypeID
,c.PrintNumber
FROM
COPICSPartFile c
LEFT JOIN Part p on c.PartNumber = p.PartNumber
WHERE
p.PartNumber IS NULL
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH
And here is the .net code:
Try
'Create command
Dim command As New SqlCommand
command.CommandType = CommandType.StoredProcedure
conn = New SqlConnection(m_ConnectionString)
command.Connection = conn
command.CommandText = "trxInsertPartFromCOPICSPartFile"
With command.Parameters
.AddWithValue("#FileName", fileName)
End With
Dim da As New SqlDataAdapter(command)
Dim dt As New DataTable
da.Fill(dt)
If dt.Rows.Count > 0 Then
Return dt
Else
Return Nothing
End If
Catch ex As SqlException
Dim myMessage As String = ex.Message
Finally
If conn.State <> ConnectionState.Closed Then
conn.Close()
End If
End Try
As I was trying to figure out why the exception (duplicate key) wasn't being caught in my application, I tried commenting out the SELECT statement in the SP just before the INSERT and voila. The exception from the INSERT is caught in the application.
Can someone explain to me why the SELECT statement causes this? I know I can break out the SELECT into another SP, but I'd like to keep it all one atomic transaction if possible. Is this expected behavior? Is there a way around it?
Thanks.
The exception is being swallowed by the Fill method. Instead of using that method, create a SqlDataReader, do a command.ExecuteReader(), and then use the reader to populate the DataTable via Load(). This way the error should occur in the ExecuteReader() method and should be catchable. And then you shouldn't need the SqlDataAdapter.
Try
'Create command
Dim command As New SqlCommand
command.CommandType = CommandType.StoredProcedure
conn = New SqlConnection(m_ConnectionString)
command.Connection = conn
command.CommandText = "trxInsertPartFromCOPICSPartFile"
With command.Parameters
.AddWithValue("#FileName", fileName)
End With
Dim dt As New DataTable
conn.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
dt.Load(reader)
If dt.Rows.Count > 0 Then
Return dt
Else
Return Nothing
End If
Catch ex As SqlException
Dim myMessage As String = ex.Message
Finally
If conn.State <> ConnectionState.Closed Then
conn.Close()
End If
End Try
Also, you might be better off on several levels if you combine the SELECT and the INSERT into a single statement. You can do this via the OUTPUT clause, as follows:
INSERT INTO Part
([PartNumber]
,[ReplacementPartNumber]
,[ShortDescription]
,[ListPrice]
,[PartStatusTypeID]
,[Weight]
,[CategoryID]
,[DateInserted]
,[SendToWebsite]
,[FileName]
,[MagentoPartTypeID]
,[PrintNumber])
OUTPUT INSERTED.[PartNumber] -- return the inserted values to the app code
SELECT
c.PartNumber
,c.ReplacementPartNumber
,c.ShortDescription
,c.ListPrice
,CASE WHEN c.PartStatusTypeID = #Enabled_PartStatusID
THEN #NotUploaded_PartStatusID
ELSE #Disabled_PartStatusID END
,#DefaultWeight
,#DefaultCategoryID
,#InsertionDate
,#SendToWebsite
,#FileName
,#MagentoPartTypeID
,c.PrintNumber
FROM
COPICSPartFile c
LEFT JOIN Part p on c.PartNumber = p.PartNumber
WHERE
p.PartNumber IS NULL