LINQ not causing insert trigger - c#

Me again with another LINQ question.
Our database is set up so that there is an event trigger when an SQL insert occurs.
Now, this trigger is not being called when I am using LINQ. Is there something in particular I need to do to make the database see the LINQ command as an insert? (Code below). I should say that the data is being entered into the database correctly, but the trigger is not happening.
Thanks in advance.
LINQ code:
private void SaveToWebsure()
{
using (MagTestDataContext context = new MagTestDataContext())
{
//create new instance of tblPolicy object
tblPolicy policy = new tblPolicy();
//generate PolicyID number
policyNo = context.ExecuteQuery<int>("DECLARE #ret INT; EXEC spNextPolicyID #ret OUTPUT; SELECT #ret").Single();
//add values to field
policy.PolicyID = policyNo;
policy.RecordType = "New Business";
policy.SchemeID = 17;
policy.Status = "Quote";
policy.Title = ddTitle.Text + ' ' + tbSurname.Text;
policy.PolicyHolder = ddTitle.Text + ' ' + tbFirstName.Text + ' ' + tbSurname.Text;
policy.SearchKey = tbSurname.Text;
policy.EMail = tbEmail.Text;
policy.Telephone = tbTelephone.Text;
policy.Address1 = tbAddressLine1.Text;
policy.Address2 = tbAddressLine2.Text;
policy.Address3 = tbAddressLine3.Text;
policy.Address4 = tbAddressLine4.Text;
policy.PostCode = tbPostcode.Text;
policy.rowguid = System.Guid.NewGuid();
policy.Comments = "Current/Previous Insurer: " + tbInsurer.Text + "; " + "Reason for refused insurance: " + ddReason.SelectedItem.Text + "; " + "Further reasons specified: " + tbOther.Text;
//insert new contact_detail object
context.tblPolicies.InsertOnSubmit(policy);
//submit changes to database
context.SubmitChanges();
}
STORED PROCEDURE:
ALTER PROCEDURE spNextPolicyID #PolicyID int output AS
begin tran
/* update tblIdNumbers set ApplicantId = ApplicantId
set #Policyid = (select ApplicantID from tblIdNumbers)
update tblIdNumbers set ApplicantId = ApplicantId + 1 */
set Rowcount 0
SET NOCOUNT ON
exec dbo.spNextIdNumber #PolicyID Output,'PolicyId'
commit tran

LINQ is not doing anything special in the background. A couple of things you should check
use SQL Profiler to see what query is being generated and what runs against the DB
check and see if the trigger is enabled just before the query runs
tip: if you're moving large amounts of data avoid triggers if you can (every case is different, just sayin) we usually disable/enable triggers from code when working with big data

Related

How to use IF EXISTS in SQL (ifexists update, else insert in SQL)

string searched = TextBox1.Text; // for example, text is 4477
...
sorgu.CommandText = "IF EXISTS(SELECT * FROM [Pins] WHERE Pin =' " + searched.ToString() + " ') BEGIN UPDATE [Pins] SET SAY= SAY+1, Pin = ' " + searched.ToString() + " ' END ELSE BEGIN INSERT INTO Pins(SAY,Pin) VALUES (+1,' " + searched.ToString() + " ') END";
...
I am using SAY for counting the number of searches.
This code is changing all records on column (Pins) to searched text.
Where/What is my fault?
In pseudo code, you're saying
IF EXISTS(SELECT Statement) UPDATE ALL ROWS
The correct way to do this with EXISTS is
UPDATE TABLE
WHERE EXISTS(Correlated SELECT Statement)
You need to have a where to not update all records. This would fix it.... but is a horrible query.
IF EXISTS(SELECT * FROM [Pins] WHERE Pin = 'searched.ToString()')
BEGIN
UPDATE [Pins] SET SAY = SAY + 1
WHERE [Pin] = 'searched.ToString() '
END
ELSE
BEGIN
INSERT INTO Pins(SAY, Pin) VALUES (1, 'searched.ToString()')
END
You missed the WHERE clause.
I would also prefer using Parameters instead of string concatenated query (which makes your code open to SQL injection attacks)
string searched = TextBox1.Text; // for example, text is 4477
sorgu.CommandText = "IF EXISTS(SELECT * FROM [Pins] WHERE Pin = #searched) BEGIN UPDATE [Pins] SET SAY=SAY+1 WHERE Pin = #searched END ELSE BEGIN INSERT INTO Pins(SAY,Pin) VALUES (1, #searched) END";
sorgu.Parameters.AddWithValue("#searched", TextBox1.Text);

How to create a database trigger with ADO.NET

I am trying to create a trigger with SqlCommand and I am getting the error:
Incorrect syntax near the keyword 'trigger'
When I copy the same query in SQL Server it is executing successfully.
Here is how the SQL command looks like.
command.CommandText = "CREATE TRIGGER tr_Korisnik" + korisnik.KorisnikID + "_FakturaStavka_ForInsert " +
"on Korisnik"+korisnik.KorisnikID+"_FakturaStavka " +
"FOR INSERT " +
"AS " +
"BEGIN " +
"DECLARE #ID int " +
"DECLARE #FakturaID int " +
"DECLARE #StavkaBr int " +
"SET #ID = (SELECT DokumentID from inserted) " +
"SET #FakturaID = (SELECT FakturaID from inserted) " +
"UPDATE Korisnik"+korisnik.KorisnikID+"_Fakturi SET BrStavki = BrStavki+1 WHERE DokumentID = #FakturaID " +
"SET #StavkaBr = (SELECT Korisnik"+korisnik.KorisnikID+"_Fakturi.BrStavki FROM Korisnik"+korisnik.KorisnikID+"_Fakturi WHERE DokumentID = #FakturaID) " +
"UPDATE Korisnik"+korisnik.KorisnikID+"_FakturaStavka SET StavkaBroj = #StavkaBr WHERE DokumentID = #ID END";
command.ExecuteNonQuery();
Also, above that I have SQLCommands for CREATE TABLE and they work properly.
I tried USE [databasename] before CREATE TRIGGER, still nothing.
I removed the concatenations +"korisnik.KorisnikID" and made clean names, still can't execute it.
The documentation for ExecuteNonQuery states that
You can use the ExecuteNonQuery to perform catalog operations (for example, querying the structure of a database or creating database objects such as tables), or to change the data in a database without using a DataSet by executing UPDATE, INSERT, or DELETE statements.
Which sort of confirms my suspicions: you can't create a trigger this way.
If you want to create a trigger in code, use a CLR Trigger.
You can create a stored procedure in your database to create a trigger then pass the correct parameters. Call the stored procedure with ado.net and pass params.
Like already mentioned, a trigger should be created at design time.
Nevertheless, as follows it is possible with ExecuteNonQuery.
Use the EXEC statement and the stored procedure sp_executesql:
cmd.CommandText = "EXEC sp_executeSQL N'CREATE TRIGGER myTrigger ON myTable...'";
cmd.ExecuteNonQuery();

How to pass absolute database name,server name as parameter?

I have a requirement where I need to select data from database DB1 and insert into database DB2.
update [Server1\SQLEXPRESS].[DB1].dbo.table1
set CName = (select CName
from [Server2\SQLEXPRESS].[DB2].dbo.table1
where CID = 3)
So above script is working fine.
Now I want to pass
[Server1\SQLEXPRESS].[DB1]
[Server2\SQLEXPRESS].[DB2]
as parameters, because the server name and database can be different in real time environment.
Can you tell me how to achieve below goal?
create procedure
#CID numeric,
#ServerName1 serverDataType,
#ServerName2 serverDataType,
#DBName1 dbDataType,
#DbName2 dbDataType
as
update #ServerName1.#DBName1.dbo.table1
set CName = (select CName
from #ServerName2.#DBName2.dbo.table1
where CID = #CID)
Try this something in this fashion:
DECLARE #ServerName1 varchar(max) = 'MyServer'
DECLARE #DB1 varchar(max) = 'MyDB'
EXEC('update ' + #ServerName1 + '.' + DB1 + '.dbo.table1
set CName= (select CName from ' + #ServerName1 + '.' + DB1 +
'.dbo.table1' where CID ='+ #CID)

Reduce the number of SqlCommands required to send information to three tables

I have the following code:
command = new SqlCommand("SELECT UserId from Users WHERE Username = N'" + userName + " AND " + userPassword + "= N'" + userPassword + "AND AccountStatus = 0");
command.CommandType = System.Data.CommandType.Text;
command.Connection = conn;
int uid = (int)command.ExecuteScalar();
if(uid > 0)
{
command = new SqlCommand("UPDATE IsOnline =" + true + " WHERE UserId = 'N" + uid);
command.ExecuteNonQuery();
command = new SqlCommand("INSERT INTO LogonHistory OUTPUT LogonHistoryId VALUES(NULL," + uid + "," + DateTime.Now + ")");
int id = (int) command.ExecuteNonQuery();
command = new SqlCommand("INSERT INTO UsersLogOnHistory VALUES(NULL," + uid + "," + id + ")");
command.ExecuteNonQuery();
IsAuthorised = true;
SendMessage(ID, ServerCommands.Success, IsAuthorised.ToString());
}
else
{
// User does not exist
SendMessage(ID, ServerCommands.Failed, IsAuthorised.ToString());
}
The first SQLCommand executed checks to see if the Username and password are correct and if their account is not suspended. It then (should) return the Row ID.
If the RowID > 0 then we have a valid logon.
Next SQLCommand updates the IsOnline status within the same table
The next SQLCommand Inserts into the LogonHistory the Users ID, and the DateTime. Now setting id with the Row Id
finally the SQLCommand is executed to insert the RowId we got from the last command and the User's Id into UserLogOnHistory. (which allows us to do a quick look up) - theoretically for updating the LogonHistory for when this user logs off.
I now realise that this is a mess!
So to the Questions:
How do I get the RowId of the table affected by the last command.
How can I optimise the queries to reduce the number of SQLCommands executed - or does this seem reasonable.
You can issue multiple TSQL commands in one batch by simply... including multiple TSQL commands. To be thorough, you should delimit them with ;, but in most (not all) cases, that is optional and it will work without.
To get the most recently inserted identity value; SCOPE_IDENTITY(). This only works for INSERT, and only with there is an IDENTITY column. In all other cases: OUTPUT.
Note; you should parameterize, but consider:
UPDATE IsOnline = 1 WHERE UserId = #uid;
DECLARE #lhid int
INSERT INTO LogonHistory (explict columns here)
VALUES(NULL,#uid, GETUTCDATE());
SET #lhid = SCOPE_IDENTITY();
INSERT INTO UsersLogOnHistory (explicit columns here)
VALUES(NULL,#uid, #lhid);
Note that you could also do the last bits with an INSERT trigger on LogonHistory, or via OUTPUT.
The number of round trips here: 1
If it was me i would put all that logic in a stored procedure, it's more easy to test and makes for better decoupling.
CREATE PROC logon
#username NVARCHAR(MAX)
, #password NVARCHAR(MAX)
, #IsAuthorized BIT OUTPUT
AS
BEGIN
SELECT #UID = UserId
FROM Users
WHERE Username = #username
AND userPasswordHash = CHECKSUM(#password);
UPDATE Users
SET IsOnline = 1
WHERE UserId = #UID;
INSERT INTO LogonHistory
VALUES(NULL,#UID,GETDATE());
INSERT INTO UsersLogOnHistory
VALUES(NULL,#UID,SCOPE_IDENTITY());
IF #UID IS NOT NULL
SET #IsAuthorized = 1;
ELSE
SET #IsAuthorized = 0;
END;
PS: please be considered for you colleagues privacy and hash their passwords.

SQL Server stored procedure executes in query analyzer, but does not function properly from C#

I have a stored procedure that updates my database.
It runs great in query analyzer, but when I try to run it from my C# web app, the table is not updated.
I am receiving the following error, so I set ARITHABORT to "ON", but I still receive the error.
UPDATE failed because the following SET options have incorrect settings: 'ARITHABORT'. Verify that SET options are correct for use
with indexed views and/or indexes on computed columns and/or query
notifications and/or xml data type methods.
C# Code:
p = new SqlParameter("#userAnswers", SqlDbType.VarChar, 1000);
p.Value = "";
p.Value = exam.ExamQuestions.Rows[0].ItemArray[0] + ":" + exam.UserAnswerChoices[0];
for (int x = 1; x < exam.NumQuestions; x++)
{
p.Value+= ", " + exam.ExamQuestions.Rows[x].ItemArray[0] + ":" + exam.UserAnswerChoices[x];
}
conn.query("insertAnswers", p);
return p.Value.ToString();
Stored procedure code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ARITHABORT ON
GO
ALTER PROCEDURE [dbo].[insertAnswers]
#userAnswers VarChar(1000)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #input XML
Set #input = '<Questions><Question id="' + Replace(Replace(#userAnswers, ':', '">'), ', ', '</Question><Question id="') + '</Question>' + '</Questions>'
;WITH ParsedXML AS
(
SELECT
ID = C.value('(#id)[1]', 'int'),
ColumnName = C.value('(.)[1]', 'varchar(10)')
FROM #input.nodes('/Questions/Question') AS T(C)
)
UPDATE CourseQuestions
SET a = CASE WHEN p.ColumnName = 'a' THEN t.a + 1 ELSE t.a END,
b = CASE WHEN p.ColumnName = 'b' THEN t.b + 1 ELSE t.b END,
c = CASE WHEN p.ColumnName = 'c' THEN t.c + 1 ELSE t.c END,
d = CASE WHEN p.ColumnName = 'd' THEN t.d + 1 ELSE t.d END,
e = CASE WHEN p.ColumnName = 'e' THEN t.e + 1 ELSE t.e END,
f = CASE WHEN p.ColumnName = 'f' THEN t.f + 1 ELSE t.f END
FROM CourseQuestions t
INNER JOIN ParsedXml p ON t.QuestionID = p.ID
END
#userAnswers is basically a comma-separated string (since I can't send an array to SQL Server). It looks like: '1:d, 2:a, 3:b'
A few things to check;
Execution/General + Advanced options in Management Studio, ensure that you don't have some strange things set in there. The usual suspects which are on by default are:
QUOTED_IDENTIFIER
ANSI_NULL_DFLT_ON
ANSI_PADDING
ANSI_WARNINGS
ANSI_NULLS
CONCAT_NULL_YIELDS_NULL
ARITHABORT (which you already checked)
Is there a chance you could actually post the Query being executed, otherwise we're flying blind a bit.
SET ARITHABORT ON is not within Stored Procedure
ALTER PROCEDURE [dbo].[insertAnswers]
#userAnswers VarChar(1000)
AS
BEGIN
SET NOCOUNT ON
SET ARITHABORT ON
.....
Haven't tested. If that doesn't work try
strSQL = "SET ARITHABORT ON" & chr(13) & chr(10) & "EXEC MySPRoc ..."
conn.Execute strSQL
Wild stab here, but could the C# code be handling text that already has : or ' within it? Or is the concatenated string that you are passing greater than 1000 characters?
On another note you should be using StringBuilder for the concatenations as it provides better performance in most instances, especially when you are doing many concatenations.

Categories