Table valued parameter has wrong data - c#

I have ASP .NET Web Form application with SignalR handling online notifications. Somewhere down the road of simplifying the code, I ran into an issue when stored procedure accepting whole table of connected clients as one of the input parameters for some reason inserts into result table less rows than when I just put a breakline into c# code before the query runs and launch it SQL-side myself.
It's really complicated code now spanning over multiple c# methods and sql queries, so I just want to know, why could this be an issue. I'm providing with small piece of code, that will hopefuly give you guys some ideas, cause I fresh ran out of mine, I feel like I tried everything by now to no avail.
Here is the SQL function dbo.UpdateAffectedClients I'm using in procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
RETURNS
#Output TABLE
(
connectionId varchar(512),
clientId int,
numberOfNotifications int,
isPatient bit
)
AS
BEGIN
--ADDING AFFECTED CLIENTS TO OUTPUT
--med/Instrument/Order
IF(#typeId = 1)
BEGIN
--Adding doctors and patients
INSERT INTO #Output (connectionId, clientId, numberOfNotifications, isPatient)
SELECT cc.connectionId, cc.clientId, COALESCE(COUNT(n.id), 0), cc.isPatient
FROM MedOrders mo
LEFT JOIN User2ExamRoom uer on mo.examRoomId = uer.examRoomId
JOIN #ConnectedClients cc on (uer.userId = cc.clientId AND cc.isPatient = 0) OR (mo.patientId = cc.clientId AND cc.isPatient = 1)
LEFT JOIN Notifications n on n.clientId = cc.clientId AND n.isPatient = cc.isPatient
WHERE mo.id = #tableId
GROUP BY cc.isPatient, cc.clientId, cc.connectionId
END
RETURN
END
and here is the procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[HandleNotificationsDBUpdate]
#ConnectedClients as SignalRConnectedClients READONLY,
#tableId int,
#typeId int
AS
BEGIN
SET NOCOUNT ON;
--QUERY
DECLARE
#affectedClients SignalRConnectedClients,
#orderId int = -1,
#meetingId int = -1,
#messageId int = -1,
#processed bit,
#cancelledByPatient bit,
#examRoomId int,
#affectedPatientId int,
#errorMessage varchar(MAX) = '',
#written bit,
#tracing varchar(2048) = ''
--Whatever happened, the row got deleted from the table in the process and info is no longer available.
IF NOT EXISTS(SELECT TOP 1* FROM MedOrders WHERE id = #tableId)
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Row no longer exists in the original table. '
--Insert to affected clients - prior to deleting from notifications, I'd lose the reference afterwards
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT cc.connectionId, cc.clientId, COUNT(n.id), cc.isPatient
FROM #ConnectedClients cc
JOIN Notifications n on cc.clientId = n.clientId AND cc.isPatient = n.isPatient
WHERE n.notificationTypeId = #typeId AND n.tableId = #tableId
GROUP BY cc.isPatient, cc.clientId, cc.connectionId
--Then delete notifications linked to it
DELETE FROM Notifications WHERE tableId = #tableId AND notificationTypeId = #typeId
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, "Whatever happened, the row got deleted from the table in the process and info is no longer available" section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--row is still there
ELSE
BEGIN
SET #tracing = #tracing + 'Row still exists in the original table. '
--MED ORDER
IF (#typeId = 1)
BEGIN
SET #tracing = #tracing + 'Its a medOrder, '
SET #orderId = #tableId
SET #examRoomId = (SELECT examRoomId FROM MedOrders WHERE id = #orderId)
SET #processed = (SELECT CASE WHEN processed IS NULL THEN 0 ELSE 1 END FROM MedOrders WHERE id = #orderId)
SET #cancelledByPatient = (SELECT CASE WHEN cancelledByPatient IS NULL THEN 0 ELSE 1 END FROM MedOrders WHERE id = #orderId)
--not processed
IF (#processed = 0)
--PATIENT POSTED OR UPDATED ORDER
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'not processed. '
--it's not already there (meaning patient didn't just update something)
IF NOT EXISTS (SELECT TOP 1 id FROM Notifications WHERE notificationTypeId = #typeId AND tableId = #tableId)
BEGIN
SET #tracing = #tracing + 'Patient is creating an order. '
--Insert row to notifications for each doctor (user) having this examRoom assigned
INSERT INTO Notifications (tableId, notificationTypeId, clientId, isPatient)
SELECT #tableId, #typeId, uer.userId, 0
FROM User2ExamRoom uer
WHERE uer.examRoomId = #examRoomId
--FUNCTION - insert into affectedClients table affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, PATIENT POSTED OR UPDATED ORDER section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--processed
ELSE
BEGIN
SET #tracing = #tracing + 'processed. '
--deleting from notifications cause its been processed
DELETE FROM Notifications
WHERE tableId = #tableId AND notificationTypeId = #typeId
--cancelled by patient
IF (#cancelledByPatient = 1)
--PATIENT REMOVED IT BEFORE APPROVAL
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Patient cancelled the order. '
--FUNCTION - insert into affectedCLients table affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, PATIENT REMOVED IT BEFORE APPROVAL section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
--not cancelled by patient
ELSE
BEGIN
--DOCTOR PROCESSED ORDER
BEGIN TRY
BEGIN
SET #tracing = #tracing + 'Doctor processed the order. '
SET #affectedPatientId = (SELECT patientId FROM MedOrders WHERE id = #orderId)
--Insert row to notifications table for the patient that placed this order
INSERT INTO Notifications (tableId, notificationTypeId, clientId, isPatient)
VALUES (#tableId, #typeId, #affectedPatientId, 1)
--FUNCTION - Add affected clientIds and number of their notifications
INSERT INTO #affectedClients (connectionId, clientId, numberOfNotifications, isPatient)
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM dbo.UpdateAffectedClients(#ConnectedClients, #typeId, #tableId)
END
END TRY
BEGIN CATCH
SET #errorMessage = #errorMessage + 'Error in HandleNotificationsDBUpdate procedure, MedOrder, DOCTOR PROCESSED ORDER section, see line ' + CAST(ERROR_LINE() as varchar(4)) + '. Error message: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(10)
END CATCH
END
END
END
END
--Final selects
SET #written = CASE WHEN LEN(#errorMessage) = 0 THEN 1 ELSE 0 END
SELECT connectionId, clientId, numberOfNotifications, isPatient FROM #affectedClients
SELECT #written as written, #errorMessage as errorMessage, #tracing as tracing
END
Patient posted order section is just fine, Doctor processed order doesn't return any patients, it only returns doctors in affectedClients result. As if it somehow failed JOIN or something (I used to run it in two insert statements, but managed to turn it into single one to see if that could be the issue).
C# method:
/// <summary>
/// Handles any changes in notifications SQL side provided typeId (see NotificationTypes Table in SQL or comment in header of this function) and id of the updated row in respective table (tableId - could stand for orderId, messageId, taskId, etc.)
/// </summary>
/// <param name="typeId"></param>
/// <param name="tableId"></param>
public static Obj_NotificationDBUpdateResult HandleNotificationsDBUpdate(int typeId, int tableId)
{
//typeIds:
//1: medOrder
//2: instrumentOrder
//3: meeting
//4: message
//5: task
Obj_NotificationDBUpdateResult output = new Obj_NotificationDBUpdateResult();
List<Obj_SignalRClientsListEntry> ConnectedClients = NotificationsHub.ConnectedClients;
//preparing ConnectedClients as parametr to pass into stored procedure so I can easily link connectionId with clientId
DataTable ConnectedClientsDt = new DataTable();
ConnectedClientsDt.Columns.Add("connectionId", typeof(string));
ConnectedClientsDt.Columns.Add("clientId", typeof(Int32));
ConnectedClientsDt.Columns.Add("numberOfNotifications", typeof(Int32));
ConnectedClientsDt.Columns.Add("isPatient", typeof(bool));
foreach(Obj_SignalRClientsListEntry item in ConnectedClients)
{
DataRow dr = ConnectedClientsDt.NewRow();
dr["connectionId"] = item.connectionId;
dr["clientId"] = item.clientId;
dr["numberOfNotifications"] = item.numberOfNotifications;
dr["isPatient"] = item.isPatient;
ConnectedClientsDt.Rows.Add(dr);
}
//Query
SqlConnection conn = new SqlConnection(Data.connStr);
try
{
conn.Open();
using (SqlCommand comm = new SqlCommand("HandleNotificationsDBUpdate", conn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ConnectedClients", ConnectedClientsDt).SqlDbType = SqlDbType.Structured;
comm.Parameters.AddWithValue("#typeId", typeId);
comm.Parameters.AddWithValue("#tableId", tableId);
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
output.affectedClients.Add(new Obj_SignalRClientsListEntry()
{
connectionId = reader["connectionId"].ToString(),
clientId = Convert.ToInt32(reader["clientId"]),
isPatient = Convert.ToBoolean(reader["isPatient"]),
numberOfNotifications = Convert.ToInt32(reader["numberOfNotifications"]),
});
}
reader.NextResult();
while (reader.Read())
{
output.written = Convert.ToBoolean(reader["written"]);
output.errorMessage = reader["errorMessage"].ToString();
output.tracing = reader["tracing"].ToString();
}
}
}
}
catch (Exception ex)
{
throw (ex);
}
finally
{
conn.Close();
}
//joining the hub, providing context
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>();
//looping through each client whom we should notify
foreach (Obj_SignalRClientsListEntry item in output.affectedClients)
{
//calling clients refresh function providng this number as an input
context.Clients.Client(item.connectionId).clientRefreshNotificationsCount(item.numberOfNotifications);
}
return output;
}
When I check (right before hubContext line) the output.affectedClients, it only shows doctors. But when I put a breakline before the SQLConnection line and run the body of the procedure provided same input as came to the c# method (see debug section at the beginning in the procedure), it returns doctors and patients just fine.
I tried switching some inserts in the procedure, putting stuff in the function directly into procedure, omitting function completely, tried to comment everything but the part with inserting into affectedClients, fiddle with ansi_nulls and those up there, tried to put them before query when I run it solo, tried to remove them from the procedure before altering, nothing.
I even put the tracing output to see exactly where it went in the procedure and it all seems legit, I have no idea why would it return just doctors.
Other than that - it's pretty neatly commented, so it should give you some idea of what's going on behind the curtain, but If you need to ask, ask, I'll try my best to answer. Thanks in advance.
UPDATE
So I narrowed the problem down to this
SELECT cc.connectionId, cc.clientId, 100, cc.isPatient
FROM #ConnectedClients cc
JOIN MedOrders mo on (mo.patientId = cc.clientId AND cc.isPatient = 1)
WHERE mo.id = #tableId
that cc.isPatient = 1 just doesn't work. For some unknown reason it doesn't recognize that 1 in #ConnectedClients (bit) as bit value. Any ideas?

Ok, so I figured it out... apparently - order of columns in the SQL User defined Table Type matters. I had last two columns switched in SQL table type and when the DataTable object arrived to the procedure, it disregarded column names from C# and just took those columns from left to right and started to fill the SQL table in the same order, therefore inserting numberOfNotifications to isPatient and vice versa. Now since you cannot have value bigger than 1 in a bit column, it treated it as 0 and the query kept failing JOINs and other stuff...
I would have never guessed this to be an issue. Why do I even bother naming columns in DataTable then?

Related

Alternative of PRINT output from SQL

I am working on an MVC app with a database where I have a lot of empty columns in each table. Now if a user dynamically adds a field on form I have to check that which column is completely empty(available) and then assign it to the user added field.
For that matter I searched and found the following SQL code and it works fine.
declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name as colum FROM sys.tables t JOIN sys.columns c ON t.Object_ID = c.Object_ID
WHERE t.Name = 'MyTableName'
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF NOT EXISTS (SELECT * FROM MyTableName WHERE [' + #col + '] IS NOT NULL) BEGIN print ''' + #col + ''' end'
EXEC(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
It prints all columns in the given table having all empty rows. Now I want to get that list in my MVC Controller. Since it is printing the results so I cannot use SqlDataReader. So I found a method in answers of this question. It gives me results one by one.
public ActionResult getList()
{
SqlConnection con = DBHelperADO.GetConnection();
SqlCommand cmd = new SqlCommand(query, con); // query = sql code mentioned above
con.Open();
using (con)
{
con.InfoMessage += connection_InfoMessage;
SqlDataReader dr = cmd.ExecuteReader();
}
}
void connection_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
var outputFromStoredProcedure = e.Message;
}
How to populate a List of all column names from the handler? (can't use session or static list)
I don't want to use Event Handler in MVC app so is there any better option? (may be any other SQL code to give me the required result or getting the PRINT output by some other method)
UPDATED according to #AwaisMahmood suggestion
You can change the print sentence by a temp table and then return it
(I don't have checked the correct sintax for the quotes in the set #cmd... line, but you can get the idea)
declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name as colum FROM sys.tables t JOIN sys.columns c ON t.Object_ID = c.Object_ID
WHERE t.Name = 'MyTableName'
CREATE TABLE #EmptyFields {
FieldName nvarchar(60)
}
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF NOT EXISTS (SELECT * FROM MyTableName WHERE [' + #col + '] IS NOT NULL)
BEGIN
insert into #EmptyFields (FieldName) values (''' + #col +
'')
' end'
EXEC(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
select * from #EmptyTables
Then, in the client, you can use a datareader as usual to fill a List<string> or simmilar.

Why is my output parameter in stored procedure called from Entity Null?

I am calling a stored procedure and declare an output paramters:
CREATE PROCEDURE dbo.usp_getIsReadyForProcess
#VideoId INT ,
#val INT OUTPUT
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
-- LOCK ROW UNTIL END OF TRANSACTION
SELECT * FROM dbo.ProcessStatus WITH (ROWLOCK, HOLDLOCK) WHERE VideoId = #VideoId
And then setting the value throughout the transaction
--If there is no row count
IF ##ROWCOUNT = 0
BEGIN
SET #val = 0
END
-- If video is already in process
ELSE IF #statusCode > 1
BEGIN
SET #val = 0
END
...... more if blocks
-- RELEASE LOCK
COMMIT TRANSACTION
END TRY
Here is the c# code for getting the output parameter:
using (var db = EntityFactory.GetInstance())
{
ObjectParameter objParam = new ObjectParameter("val", typeof(int));
db.usp_getIsReadyForProcess(videoId, objParam);
return (int)objParam.Value == 1;
}
... But then objParam.Value is null no matter what I do.
So I dig a little deeper and uncover an exception that was handled already,
Message:
Method may only be called on a Type for which Type.IsGenericParameter is true.
What am i doing wrong? I tried type(bool, int32, string) .. nothing works
Your stored procedure returns a resultset, due to this line:
SELECT * FROM dbo.ProcessStatus WITH (ROWLOCK, HOLDLOCK) WHERE VideoId = #VideoId
You can either change the SQL to something like:
DECLARE #RowCount INT
SELECT #RowCount = COUNT(*) FROM dbo.ProcessStatus WITH (ROWLOCK, HOLDLOCK) WHERE VideoId = #VideoId
Or you can capture the resultset in your code:
var resultSet = db.usp_getIsReadyForProcess(videoId, objParam);

Getting specific column name of a table

I am working on a project and I got stuck with a query in which I want to get the specific column name of a table whose value is "no."
I have a table with name subscription and it has four fields; id(integer), email(varchar), sms(varchar), phone(varchar) and my email, sms and phone field have values either 'yes' or 'no'.
Now I want to retrieve the name of only those columns which has a value 'no'.
I want to execute this query in mysql.
I have tried the following query which is returning me the complete row wherever there is even one occurence of 'no':
SELECT * FROM test.subscription WHERE 'no' IN (email,sms,phone);
Like i have these two rows now i want to select the column name where value='no' and id=1
How about something cheap and cheerful using a case statement like:
SELECT ID, 'ColName' = CASE
WHEN email = 'no' THEN 'email'
END,
'ColName2' = CASE
WHEN sms = 'no' THEN 'sms'
END,
'ColName3' = CASE
WHEN phone = 'no' THEN 'phone'
END
FROM subscription
where ID = 2
I cant put this as a comment because of less privileges, but if you are looking to search a value which is held by a table (could be any column) - you might check this SO post
Using the code from the same post -
DECLARE #Results TABLE(ColumnName nvarchar(370), ColumnValue nvarchar(3630))
DECLARE #SearchStr nvarchar(100)
DECLARE #TableName nvarchar(256), #ColumnName nvarchar(128), #SearchStr2 nvarchar(110)
SET #TableName = 'no'
SET #SearchStr2 = QUOTENAME('%' + 'no' + '%','''')
SET #ColumnName = ''
SET #TableName = '[test].[dbo].[subscription]'
WHILE (#TableName IS NOT NULL) AND (#ColumnName IS NOT NULL)
BEGIN
SET #ColumnName =
(
SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(#TableName, 2)
AND TABLE_NAME = PARSENAME(#TableName, 1)
AND DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
AND QUOTENAME(COLUMN_NAME) > #ColumnName
)
IF #ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
(
'SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2
)
END
END
SELECT ColumnName, ColumnValue FROM #Results
If i got the Question right, i think the following Query might work
SELECT *
FROM test.subscription
WHERE 1=1
and
(email = 'no'
or
email = 'no'
or
sms = 'no'
)
and id=(your values)

Retrieve multiple data from stored procedure using Linq-to-SQL

I am using Silverlight and Linq-to-SQL to communicate with the database.
I have a stored procedure which receives 2 parameters (PFOID and Quantity) and Userid and returns a product name.
If we send multiple values like multiple pfoid's and quantity's it will return multiple product names shown as below
The stored procedure looks like this..
ALTER PROCEDURE [PFO].[PFOValidateUpdateData]
#PfoIDs xml, -- list of PFO ID's
#UserID uniqueidentifier --The Identity of the User making the call.
AS
BEGIN
-- SET DEFAULT BEHAVIOR
SET NOCOUNT ON -- Performance: stops rows affected messages
SET DEADLOCK_PRIORITY LOW -- This SP to be the Deadlock victim
-- Initialise Lock-Timeout and Deadlock vars for Insert
DECLARE #iLockTimeoutRetries as int
DECLARE #iDeadLockRetries as int
DECLARE #dtLockTimeoutSleepInterval as datetime
DECLARE #dtDeadlockSleepInterval as datetime
DECLARE #iErrorNumber as int
SET #iLockTimeoutRetries = 0
SET #iDeadLockRetries = 0
SET #dtLockTimeoutSleepInterval = sCommon.fnLockTimeoutSleepInterval()
SET #dtDeadlockSleepInterval= sCommon.fnDeadlockSleepInterval()
SET #iErrorNumber = 0
-- procedure specific
DECLARE #idoc as int
DECLARE #IsBrightstarUser as bit
RETRY:
BEGIN TRY
--Create Temp table to store stores!
CREATE TABLE [#PFOList]
(
[PFOId] nvarchar(50),
[Quantity] INT
)
--Create Temp table to store User stores!
CREATE TABLE [#UserStoreList]
(
[StoreID_XRef] nvarchar(50)
)
print CONVERT(varchar(1000), #PfoIDs)
--Create Document
EXEC sp_xml_preparedocument #idoc OUTPUT, #PfoIDs
-- Append to new list of Store records
INSERT INTO [#PFOList] (
[PFOId],
[Quantity]
)
SELECT [PFOID],[Quantity]
FROM OPENXML (#idoc, 'ArrayOfString/string',2)
WITH( [PFOID] nvarchar(50),[Quantity] [INT]) Stores
--WHERE [PFOId] Is Not NULL
-- Clean UP
exec sp_xml_removedocument #iDoc
-- are we dealing with a brightstar user?
SET #IsBrightstarUser = CASE WHEN exists
(SELECT *
FROM dbo.aspnet_UsersInRoles AS uir inner join
dbo.aspnet_Roles AS roles ON uir.RoleId = roles.roleid
WHERE roles.rolename = 'Brightstar Employee' and uir.userid = #userid)
THEN 1 ELSE 0 END
--Get User Storelist
INSERT INTO [#UserStoreList] (
[StoreID_XRef]
)
SELECT s.StoreId_XRef
FROM PFO.UserStoreLink us(nolock)
INNER JOIN PFO.Store s(nolock)
ON us.StoreId=s.StoreId
where UserId=#UserID
--Select * from [#PFOList]
--SELECT #IsBrightstarUser AS ISBrightstaruser
--SELECT * from [#UserStoreList]
--If BrightstarCustomer Update all the Quantities.
IF #IsBrightstarUser=1
BEGIN
UPDATE
PFO.PFO
SET
IsBrightstarReviewComplete = 1
,[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
,[BrightstarReviewedQty]=pfol.[Quantity]
FROM
PFO.PFO as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN [#PFOList] as pfol on pfo.PFOId = pfol.PFOId
WHERE #IsBrightstarUser = 1
END
ELSE BEGIN
--Update Non Contrained Orders
UPDATE
PFO.PFO
SET
[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
FROM
PFO.PFO (nolock) as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN [#PFOList] as pfol on pfo.PFOId = pfol.PFOId
WHERE pfo.IsBrightstarReviewComplete=1 AND IsConstraint=0
--SELECT * from PFO.PFO (nolock) where PFOId='04676723-2afb-49ff-9fa1-0131cabb407c'
--Update Contrained Orders
--Get Existing quantities for the User
CREATE TABLE #ExistingProductQuantity
(
[PfoID] nvarchar(100)
,[Product] nvarchar(255)
,[PlannedQty] INT
,[BrightstarReviewedQty] INT
)
CREATE TABLE #CustProductQuantity
(
[Product] nvarchar(255)
,[IsUpdatable] BIT
)
INSERT INTO #ExistingProductQuantity
( [PfoID],[Product],[PlannedQty],[BrightstarReviewedQty])
SELECT PFOId,InventoryId,PlannedQty,BrightstarReviewedQty
FROM PFO.PFO as pfo
INNER JOIN [#UserStoreList] as stores on pfo.StoreId_XRef=stores.StoreID_XRef
WHERE pfo.IsBrightstarReviewComplete=1 AND IsConstraint=1
UPDATE
#ExistingProductQuantity
SET [PlannedQty]=pfol.[Quantity]
FROM #ExistingProductQuantity eoq
INNER JOIN [#PFOList] as pfol on eoq.PFOId = pfol.PFOId
INSERT INTO #CustProductQuantity
( [Product],[IsUpdatable] )
SELECT
[Product],
CASE WHEN SUM(PlannedQty)<=SUM(BrightstarReviewedQty) THEN 1 ELSE 0 END
FROM #ExistingProductQuantity
GROUP BY [Product]
--SELECT * from #ExistingProductQuantity
--SELECT * from #CustProductQuantity
--Update the products that can be updatable
UPDATE
PFO.PFO
SET
[ModifyingUsersID] = #UserID
,[ModifiedDate] = getdate()
,[PlannedQty] = pfol.[Quantity]
FROM
PFO.PFO as pfo
INNER JOIN #UserStoreList as stores on pfo.StoreId_XRef=stores.StoreID_XRef
INNER JOIN #PFOList as pfol on pfo.PFOId = pfol.PFOId
INNER JOIN #CustProductQuantity as pr on pr.Product=pfo.InventoryId
WHERE pfo.IsBrightstarReviewComplete=1 AND pr.IsUpdatable=1 AND IsConstraint=1
--Return the products that are not updatabele
select [Product]
FROM #CustProductQuantity
where [IsUpdatable]=0
END
END TRY
BEGIN CATCH
-- Get the ErrorNumber
Set #iErrorNumber = ERROR_NUMBER()
--Handle Deadlock situation (Deletes, Inserts & Updates)
IF #iErrorNumber = 1205
BEGIN
-- If we have not made enough attempts to break the lock
IF #iDeadLockRetries < sCommon.fnMaxDeadlockRetries()
BEGIN
-- Increment the Attempt count
SET #iDeadLockRetries = #iDeadLockRetries + 1
-- Pause to allow the deadlock contention to clear
WAITFOR DELAY #dtDeadlockSleepInterval
GOTO RETRY
END
END
-- Handle Lock Timeout situation (Deletes, Inserts & Updates)
IF #iErrorNumber = 1222
BEGIN
-- If we have not made enough attempts to break the Deadlock
IF #iLockTimeoutRetries < sCommon.fnMaxLockTimeoutRetries()
BEGIN
-- Increment the Attempt count
SET #iLockTimeoutRetries = #iLockTimeoutRetries + 1
-- Pause to allow the lock contention to clear
WAITFOR DELAY #dtLockTimeoutSleepInterval
GOTO RETRY
END
END
exec Common.RethrowError
END CATCH
END
The result is as follows..
Product
6435LVWK-360-CD819E3
NSCHI535C1097I360-4C
NSCHU485C1819I360-0C
Return Value
0
My Linq-to-SQL connection is like this
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="PFO.PFOValidateUpdateData")]
public int PFOValidateUpdateData([global::System.Data.Linq.Mapping.ParameterAttribute(Name = "PfoIDs", DbType = "Xml")] System.Xml.Linq.XElement pfoIDs, [global::System.Data.Linq.Mapping.ParameterAttribute(Name = "UserID", DbType = "UniqueIdentifier")] System.Nullable<System.Guid> userID)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), pfoIDs, userID);
return ((int)(result.ReturnValue));
}
I am trying to retrieve all the data from the stored procedure but the when I debugging it the return value is "o"..
I would be grateful to you if you could help me retrieve all the data returned by the stored procedure... thank you very much...
If your stored procedure returns a collection of nvarchar's, then the signature of your Linq2Sql method is not correct. It should not return an int, but an ISingleResult.
So the correct signature will be:
public ISingleResult<string> PFOValidateUpdateData(...)
{
IExecuteResult result = this....;
return (ISingleResult<string>)result.ReturnValue;
}
var products = PFOValidateUpdateData(...).ToList();
If you want to return the results from multiple SELECT's in your stored procedure, you'll have to use IMultipleResults.
Well I know this is not the right way...for time being,its working for me...
I created an other table with two columns one ProductId and ID, I am inserting the values returned by the stored procedure,
in the designer.cs I am returning the table,
[global::System.Data.Linq.Mapping.FunctionAttribute(Name="PFO.PFOValidateUpdateData")]
public ISingleResult<PFOValidData> PFOValidateUpdateData([global::System.Data.Linq.Mapping.ParameterAttribute(Name = "PfoIDs", DbType = "Xml")] System.Xml.Linq.XElement pfoIDs, [global::System.Data.Linq.Mapping.ParameterAttribute(Name = "UserID", DbType = "UniqueIdentifier")] System.Nullable<System.Guid> userID)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), pfoIDs, userID);
return ((ISingleResult<PFOValidData>)(result.ReturnValue));
}
And in the Domainservice
List<string> PFOValidateUpdateData(string pfoIds, Guid userID)
{
List<string> productIdList = new List<string>();
// Acquire the int
result = this.DataContext.PFOValidateUpdateData(element, userID);
foreach (var item in result)
{
productIdList.Add(item.ProductID);
}
return productIdList;
To get the multiple values returned by the stored procedure....
Please let me know if there is a better way to solve this... thank you

SQL Server: Check if Child Rows Exist

I am working on a web application where there are many tables but two will suffice to illustrate my problem:
User
Order
Let us say that the User table has a primary key "UserID", which is a foreign key in the Order table called "CreatedBy_UserID".
Before deleting a User, I would like to check if the Order table has a record created by the soon-to-be deleted user.
I know that a SqlException occurs if I try to delete the user but let us say that I want to check beforehand that the Order table does not have any records created by this user? Is there any SQL code which I could run which will check all foreign keys of a table if that row is being referenced?
This for me is generally useful code as I could remove the option for deletion altogether if it can be detected that the user exists in these other tables.
I don't want a simple query (SELECT COUNT(*) FROM Order WHERE CreatedBy_UserID == #userID) because this will not work if I create another foreign key to the Order table. Instead I want something that will traverse all foreign keys.
Can this be done?
Below is code for an sp that I've used in the past to perform this task (please excuse the indenting):
create proc dbo.usp_ForeignKeyCheck(
#tableName varchar(100),
#columnName varchar(100),
#idValue int
) as begin
set nocount on
declare fksCursor cursor fast_forward for
select tc.table_name, ccu.column_name
from
information_schema.table_constraints tc join
information_schema.constraint_column_usage ccu on tc.constraint_name = ccu.constraint_name join
information_schema.referential_constraints rc on tc.constraint_name = rc.constraint_name join
information_schema.table_constraints tc2 on rc.unique_constraint_name = tc2.constraint_name join
information_schema.constraint_column_usage ccu2 on tc2.constraint_name = ccu2.constraint_name
where tc.constraint_type = 'Foreign Key' and tc2.table_name = #tableName and ccu2.column_name = #columnName
order by tc.table_name
declare
#fkTableName varchar(100),
#fkColumnName varchar(100),
#fkFound bit,
#params nvarchar(100),
#sql nvarchar(500)
open fksCursor
fetch next from fksCursor
into #fkTableName, #fkColumnName
set #fkFound = 0
set #params=N'#fkFound bit output'
while ##fetch_status = 0 and coalesce(#fkFound,0) <> 1 begin
select #sql = 'set #fkFound = (select top 1 1 from [' + #fkTableName + '] where [' + #fkColumnName + '] = ' + cast(#idValue as varchar(10)) + ')'
print #sql
exec sp_executesql #sql,#params,#fkFound output
fetch next from fksCursor
into #fkTableName, #fkColumnName
end
close fksCursor
deallocate fksCursor
select coalesce(#fkFound,0)
return 0
end
This will select a value of 1 if a row has any foreign key references.
The call you would need would be:
exec usp_ForeignKeyCheck('User','UserID',23)
There is no clean way to iterate through all FK columns where multiple exist. You'd have to build some dynamic SQL to query the system tables and test each in turn.
Personally, I wouldn't do this. I know what FKs I have: I'll test each in turn
...
IF EXISTS (SELECT * FROM Order WHERE CreatedBy_UserID == #userID)
RAISERROR ('User created Orders ', 16, 1)
IF EXISTS (SELECT * FROM Order WHERE PackedBy_UserID == #userID)
RAISERROR ('User packed Orders', 16, 1)
...
You wouldn't dynamically iterate through each property of some user object and generically test each one would you? You'd have code for each property
This code will give you a list of the foreign keys which are defined for a specifit table:
select distinct name from sys.objects
where object_id in ( select constraint_object_id from sys.foreign_key_columns as fk
where fk.Parent_object_id = (select object_id from sys.tables
where name = 'tablename') )
You can use transaction to check it.
I know it seems like stone ax, but it working fast and stable.
private bool TestUser(string connectionString, int userID)
{
var result = true;
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
var command = connection.CreateCommand();
var transaction = connection.BeginTransaction();
command.Connection = connection;
command.Transaction = transaction;
try
{
command.CommandText = "DELETE User WHERE UserID = " + userID.ToString();
command.ExecuteNonQuery();
transaction.Rollback();
}
catch
{
result = false;
}
}
return result;
}

Categories