How to create a database trigger with ADO.NET - c#

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();

Related

SQL Create Trigger Command Runs Fine Through MySQL Workbench But Errors When Called Through Entity Framework

I have the following code to create SQL setup a trigger (not yet a prepared statement).
commandList.Add($"DROP TRIGGER if exists `{tableName}_after_insert`;");
commandList.Add($"delimiter $$" + Environment.NewLine +
$"CREATE TRIGGER `{tableName}_after_insert` AFTER INSERT" + Environment.NewLine +
$"ON `{tableName}`" + Environment.NewLine +
"FOR EACH ROW BEGIN" + Environment.NewLine +
"Insert into HashEntry(TableName, TableRowId, TokenValue)" + Environment.NewLine +
$"select '{tableName}', n.{tableKey}, 'tokenValue' from NEW n;" + Environment.NewLine +
"END;" + Environment.NewLine +
"$$" + Environment.NewLine +
"delimiter ;");
//then some unrelated code until we execute
foreach (var command in commandList)
{
Database.ExecuteSqlCommand(command);
}
This create the following SQL which works in Workbench and Entity Framework.
DROP TRIGGER if exists `MyTable_after_insert`;
And this which only works in Workbench directly.
delimiter $$
CREATE TRIGGER `MyTable_after_insert` AFTER INSERT
ON `MyTable`
FOR EACH ROW BEGIN
Insert into HashEntry(TableName, TableRowId, TokenValue)
select 'MyTable', n.Id, 'tokenValue' from NEW n;
END;
$$
delimiter ;
Which gives the following error.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'delimiter $$
CREATE TRIGGER `MyTable_after_insert` AFTER INSERT
I'm not sure why it would work directly in Workbench but not calling the same command through EF.
Any ideas?
DELIMITER $$ is special syntax only understood by MySQL Workbench. It's not necessary when executing commands against a server using MySqlCommand. Just execute the SQL directly:
commandList.Add($#"CREATE TRIGGER `{tableName}_after_insert` AFTER INSERT
ON `{tableName}`
FOR EACH ROW BEGIN
Insert into HashEntry(TableName, TableRowId, TokenValue)
select '{tableName}', n.{tableKey}, 'tokenValue' from NEW n;
END;");
(Note that I'm using verbatim strings to avoid all the concatenation with Environment.NewLine.)

How to generate strings that are not vulnerable to SQL injection

I have a utility that generates query strings, but the static code analyzers (and my coworkers) are complaining because of risk of "SQL Injection Attack".
Here is my C# code
public static string[] GenerateQueries(string TableName, string ColumnName)
{
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
};
}
In the code I always call it only with constant strings, such as
var queryList = GenerateQueries("Person", "Name");
Is there any way to rewrite this function so that it is "safe"? For example, if I were using C instead of C#, I could write a macro to generate the strings safely.
At the moment, the only choice I have is to copy/paste this block of SELECT statements for every single table, which is ugly and a maintenance burden.
Is copy/paste really my only option?
EDIT:
Thank you for the replies, esp William Leader. Now I see that my question is wrong-headed. It isn't just the fact that I am concatenating query strings, but also storing them in a variable. The only proper way to do this is to construct the SqlDataAdapter using a constant such as,
var adapter = new SqlDataAdapter("SELECT * FROM PERSON");
There is no other choice. So yes, there will be a lot of copy/paste. I'm starting to regret not using EF.
I was shocked at first, but on reflection this is no different than having an SQL statement already in your code that looks like this:
"SELECT * FROM Person"
We do that kind of thing all the time.
IF
There's an important caveat here. That only remains true if you can control how the function is called. So if this method is a private member of a data layer class somewhere, you might be okay. But I also wonder how useful this really is. It seems like you're not saving much over what you'd get from just writing the queries.
Additionally, it's not good to be in the habit of ignoring your static analysis tools. Sometimes they give you stuff you just know is wrong, but you change it anyway so that when they do find something important you're not conditioned to ignore it.
What your Code analyser is telling you is that you should most likely be calling a procedure with some parameters instead of sending SQL across the wire.
It does not mater a single bit whether or not you use a macro to generate your SQL statements, if you are sending raw SQL across the wire you are open to SQL Injection Attacks
Sending SQL commands to an endpoint making a non sanctioned call. If we fire up a network packet sniffer, we can see that you have a database configured to allow SQL commands to be sent, so we can inject illegal SQL into the system
You could still rely on a single procedure for calling your updates, but if you elect to move to procedures, why would you want to do that?
EDITED to provide an example
create PROC sp_CommonSelectFromTableProc #tableName varchar(32)
AS
-- code to check the tableName parameter does not contain SQL and/or is a valid tableName
-- your procedure code here will probable use
-- exec mydynamicSQLString
-- where mydynamicSQLString is constructed using #tableName
END;
or maybe a table specific procedure
create PROC sp_SelectFromSpecificTableProc
AS
SELECT * FROM SpecificTable
END;
What is important to remember is that SQL injection is independent of the technology used for the underlying application.
It is just overt when the application contains such constructs as
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
SQL Injection must be addressed at both ends of the data channel.
Here is a pretty good starting point for understanding how to mitigate for SQL Injection attacks

How do I make a foreign key in one of my databases copy the primary key that it is referencing to while I create a record? [duplicate]

I have a query to insert a row into a table, which has a field called ID, which is populated using an AUTO_INCREMENT on the column. I need to get this value for the next bit of functionality, but when I run the following, it always returns 0 even though the actual value is not 0:
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertInvoice;
comm.CommandText += "\'" + invoiceDate.ToString("yyyy:MM:dd hh:mm:ss") + "\', " + bookFee + ", " + adminFee + ", " + totalFee + ", " + customerID + ")";
int id = Convert.ToInt32(comm.ExecuteScalar());
According to my understanding, this should return the ID column, but it just returns 0 every time. Any ideas?
EDIT:
When I run:
"INSERT INTO INVOICE (INVOICE_DATE, BOOK_FEE, ADMIN_FEE, TOTAL_FEE, CUSTOMER_ID) VALUES ('2009:01:01 10:21:12', 50, 7, 57, 2134);last_insert_id();"
I get:
{"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'last_insert_id()' at line 1"}
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertStatement; // Set the insert statement
comm.ExecuteNonQuery(); // Execute the command
long id = comm.LastInsertedId; // Get the ID of the inserted item
[Edit: added "select" before references to last_insert_id()]
What about running "select last_insert_id();" after your insert?
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertInvoice;
comm.CommandText += "\'" + invoiceDate.ToString("yyyy:MM:dd hh:mm:ss") + "\', "
+ bookFee + ", " + adminFee + ", " + totalFee + ", " + customerID + ");";
+ "select last_insert_id();"
int id = Convert.ToInt32(comm.ExecuteScalar());
Edit: As duffymo mentioned, you really would be well served using parameterized queries like this.
Edit: Until you switch over to a parameterized version, you might find peace with string.Format:
comm.CommandText = string.Format("{0} '{1}', {2}, {3}, {4}, {5}); select last_insert_id();",
insertInvoice, invoiceDate.ToString(...), bookFee, adminFee, totalFee, customerID);
Use LastInsertedId.
View my suggestion with example here: http://livshitz.wordpress.com/2011/10/28/returning-last-inserted-id-in-c-using-mysql-db-provider/
It bothers me to see anybody taking a Date and storing it in a database as a String. Why not have the column type reflect reality?
I'm also surprised to see a SQL query being built up using string concatenation. I'm a Java developer, and I don't know C# at all, but I'd wonder if there wasn't a binding mechanism along the lines of java.sql.PreparedStatement somewhere in the library? It's recommended for guarding against SQL injection attacks. Another benefit is possible performance benefits, because the SQL can be parsed, verified, cached once, and reused.
Actually, the ExecuteScalar method returns the first column of the first row of the DataSet being returned. In your case, you're only doing an Insert, you're not actually querying any data. You need to query the scope_identity() after you're insert (that's the syntax for SQL Server) and then you'll have your answer. See here:
Linkage
EDIT: As Michael Haren pointed out, you mentioned in your tag you're using MySql, use last_insert_id(); instead of scope_identity();

I have inserted a row, I want to get it's ID and plus it with an int and insert in that row

I have inserted a row into my table, and I want to get it's ID and plus it with an int and inserted in that row.
But I don't know how to get it's ID.
Here is the insert code:
objCommand.Connection = objConnection;
objCommand.CommandText = "INSERT INTO Moin " +
" (Title, TotalID, Code ) " +
"VALUES (#Title , #TotalID, #Code )";
objCommand.Connection = objConnection;
objCommand.CommandText = "INSERT INTO Moin " +
" (Title, TotalID, Code ) " +
"VALUES (#Title , #TotalID, #Code ) SELECT SCOPE_IDENTITY()";
object id = objCommand.ExecuteScalar();
Try using the OUTPUT clause of SQL Server in your query - it can return any of the just inserted value (here I'm assuming your column is called ID - adapt as needed):
objCommand.Connection = objConnection;
objCommand.CommandText = "INSERT INTO Moin(Title, TotalID, Code ) " +
"OUTPUT Inserted.ID " +
"VALUES (#Title , #TotalID, #Code ); "
and then execute it like this:
int result = (int)objCommand.ExecuteScalar();
Since you're returning just one row and one column (just the INT), you can use .ExecuteScalar() to retrieve that value back from the INSERT statement.
With the OUTPUT clause, you can return any values just inserted - not just the identity column. So you could also return values that are filled by the database with default values, or whatever you need. If you return multiple values, you need to use a data reader to read them all - ExecuteScalar() only works for a single value.
But, as Anders correctly mentioned - using an ORM like Entity Framework would do all of this automatically for you and you wouldn't have to deal with those raw SQL commands anymore....
Building SQL commands in strings should be considered a legacy technique. If you use Entity Framework or linq-to-sql the retrieval of the id is handled automatically for you.
With pure SQL, use the SCOPE_IDENTITY() function to retrieve the id of the inserted element.

Cannot drop database because it is currently in use

I want to drop a database. I have used the following code, but to no avail.
public void DropDataBase(string DBName,SqlConnection scon)
{
try
{
SqlConnection.ClearAllPools();
SqlCommand cmd = new SqlCommand("ALTER DATABASE " + DBName + " SET SINGLE_USER WITH ROLLBACK IMMEDIATE", scon);
cmd.CommandType = CommandType.Text;
scon.Open();
cmd.ExecuteNonQuery();
scon.Close();
SqlCommand cmddrpdb = new SqlCommand("drop database " + DBName + "", scon);
cmddrpdb.CommandType = CommandType.Text;
scon.Open();
cmddrpdb.ExecuteNonQuery();
scon.Close();
}
catch (Exception ex)
{
MessageBox.Show("DropDataBase : " +ex.Message);
}
}
I am getting Error as cannot drop database because it is currently in use.
Please help me out in the above mentioned issue.
Before dropping a database, you will need to drop all the connections to the target database first.
I have found a solution at http://www.kodyaz.com/articles/kill-all-processes-of-a-database.aspx
DECLARE #DatabaseName nvarchar(50)
SET #DatabaseName = N'YOUR_DABASE_NAME'
DECLARE #SQL varchar(max)
SELECT #SQL = COALESCE(#SQL,'') + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DatabaseName) AND SPId <> ##SPId
--SELECT #SQL
EXEC(#SQL)
It's too late, but it may be useful for future users.
You can use the below query before dropping the database query:
use master go
alter database [MyDatbase] set single_user with rollback immediate
drop database [MyDatabase]
It will work. You can also refer to
How do I specify "close existing connections" in sql script
I hope it will help you :)
Someone connected to the database. Try to switch to another database and then, to drop it:
Try
SP_WHO to see who connected
and KILL if needed
For SQL server mgmt. studio:
Right click database: Properties -> Options -> Restrict Access : Set to "Single User" and perform the drop afterwards
In SQL Server Management Studio 2016, perform the following:
Right click on database
Click delete
Check close existing connections
Perform delete operation
select * from sys.sysprocesses where dbid = DB_ID('Test')
(Replace 'Test' with the name of the database you are trying to drop)
This will tell you which processes are using it.
If you still want to force drop then, the ultimate approach is:
USE master;
GO
ALTER DATABASE Test
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
GO
DROP DATABASE Test;
Hope this helps !
First make your data base offline after that detach it e.g.
Use Master
GO
ALTER DATABASE dbname SET OFFLINE
GO
EXEC sp_detach_db 'dbname', 'true'
If your dropping the database in SQL Management Studio and you get the message, don't forget that you use Master as selected database otherwise your query is also an connection to the database.
USE Master;
GO
DROP DATABASE AdventureWorks;
GO
First check the connected databases
SP_WHO
Second Disconnect your database
DECLARE #DatabaseName nvarchar(50)
SET #DatabaseName = N'your_database_name'
DECLARE #SQL varchar(max)
SELECT #SQL = COALESCE(#SQL,'') + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DatabaseName) AND SPId <> ##SPId
--SELECT #SQL
EXEC(#SQL)
FINALLY DROP IT
drop database your_database
A brute force workaround could be:
Stop the SQL Server Service.
Delete the corresponding .mdf and .ldf files.
Start the SQL Server Service.
Connect with SSMS and delete the database.
I wanted to call out that I used a script that is derived from two of the answers below.
Props to #Hitesh Mistry and #unruledboy
DECLARE #DatabaseName nvarchar(50)
SET #DatabaseName = N'[[[DatabaseName]]]'
DECLARE #SQL varchar(max)
SELECT #SQL = COALESCE(#SQL,'') + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DatabaseName) AND SPId <> ##SPId
EXEC(#SQL)
alter database [[[DatabaseName]]] set single_user with rollback immediate
DROP DATABASE [[[DatabaseName]]]
Using MS SQL Server 2008, in DELETE dialog with Close connection options, this is the generated script, I guess it is the best:
EXEC msdb.dbo.sp_delete_database_backuphistory #database_name = N'YOUR_DATABASE_NAME'
GO
USE [master]
GO
ALTER DATABASE [YOUR_DATABASE_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
/****** Object: Database [YOUR_DATABASE_NAME] Script Date: 01/08/2014 21:36:29 ******/
DROP DATABASE [YOUR_DATABASE_NAME]
GO
Just wanted to give a vb.net (as with c language if want to convert..) I was having similar problem for uninstal of one of my programs, dropping the DB was bit tricky, yes could get users to go into server drop it using Express, but thats not clean, after few looks around got a perfect little bit of code together...
Sub DropMyDatabase()
Dim Your_DB_To_Drop_Name As String = "YourDB"
Dim Your_Connection_String_Here As String = "SERVER=MyServer;Integrated Security=True"
Dim Conn As SqlConnection = New SqlConnection(Your_Connection_String_Here)
Dim AlterStr As String = "ALTER DATABASE " & Your_DB_To_Drop_Name & " SET OFFLINE WITH ROLLBACK IMMEDIATE"
Dim AlterCmd = New SqlCommand(AlterStr, Conn)
Dim DropStr As String = "DROP DATABASE " & Your_DB_To_Drop_Name
Dim DropCmd = New SqlCommand(DropStr, Conn)
Try
Conn.Open()
AlterCmd.ExecuteNonQuery()
DropCmd.ExecuteNonQuery()
Conn.Close()
Catch ex As Exception
If (Conn.State = ConnectionState.Open) Then
Conn.Close()
End If
MsgBox("Failed... Sorry!" & vbCrLf & vbCrLf & ex.Message)
End Try
End Sub
Hope this helps anyone looking
xChickenx
UPDATE
Using this converter here is the C# version :
public void DropMyDatabase()
{
var Your_DB_To_Drop_Name = "YourDB";
var Your_Connection_String_Here = "SERVER=MyServer;Integrated Security=True";
var Conn = new SqlConnection(Your_Connection_String_Here);
var AlterStr = "ALTER DATABASE " + Your_DB_To_Drop_Name + " SET OFFLINE WITH ROLLBACK IMMEDIATE";
var AlterCmd = new SqlCommand(AlterStr, Conn);
var DropStr = "DROP DATABASE " + Your_DB_To_Drop_Name;
var DropCmd = new SqlCommand(DropStr, Conn);
try
{
Conn.Open();
AlterCmd.ExecuteNonQuery();
DropCmd.ExecuteNonQuery();
Conn.Close();
}
catch(Exception ex)
{
if((Conn.State == ConnectionState.Open))
{
Conn.Close();
}
Trace.WriteLine("Failed... Sorry!" + Environment.NewLine + ex.Message);
}
}
To delete a database even if it's running, you can use this batch file
#echo off
set /p dbName= "Enter your database name to drop: "
echo Setting to single-user mode
sqlcmd -Q "ALTER DATABASE [%dbName%] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
echo Dropping...
sqlcmd -Q "drop database %dbName%"
echo Completed.
pause
You cannot drop a database currently being used however you can use sp_detach_db stored procedure if you want to remove a database from the server without deleting the database files.
just renaming the DB (to be delete) did the trick for me. it got off the hold of whatever process was accessing the database, and so I was able to drop the database.
Go to available databases section and select master. Then Try DROP DATABASE the_DB_name.
Use this:
/* Delete Database Backup and Restore History from MSDB System Database */
EXEC msdb.dbo.sp_delete_database_backuphistory #database_name = N'[dba]'
GO
/* Query to Get Exclusive Access of SQL Server Database before Dropping the Database */
USE [master]
GO
ALTER DATABASE [dba]
SET SINGLE_USER
WITH
ROLLBACK IMMEDIATE
GO
/* Query to Drop Database in SQL Server */
DROP DATABASE [dba]
GO

Categories