UPSERT query/syntax in OLE DB (MS Access) - c#

For SQL Server, I could do this
UPDATE [Table] SET b=2, c=3 WHERE a=1;
IF ##ROWCOUNT=0
INSERT INTO [Table] (a,b,c) VALUES (1,2,3)
How do you do a similar thing on MS Access (using OleDbConnection)?
Doing that I got
Characters found after end of SQL statement.

I don't think the Jet/ACE OleDB engine has an equivalent of T-SQL syntax for this kind of problem.
You should go for the long route of checking if record exist, then decide for INSERT or UPDATE.
However, being Access mainly a single/local user database system you should not have many problems doing something like this pseudocode:
using(OleDbConnection cn = new OleDbConnection(constring))
{
cn.Open();
using(OleDbCommand cmd = new OleDbCommand("select count(*) from table where pkID = ?", cn);
{
cmd.Parameters.AddWithValue("pk", myID);
int result = Convert.ToInt32(cmd.ExecuteScalar());
if(result == 0)
// do your insert command here
else
// do your update command here
}
}
Of course, as I have said, this doesn't take into account concurrency problems.

Related

How to fix SQL Injection Issue of truncation of table

Below is the line of code where I truncate table records. The table value is coming from the front end. In my Veracode scan, it is showing SQL injection. How can I avoid this? I cannot create a stored procedure as the connection string is dynamic where I need to truncate this table. Is there another approach?
SqlCommand cmd = connection.CreateCommand();
cmd.Transaction = transaction;
cmd.CommandText = "TRUNCATE TABLE " + tablename;
cmd.ExecuteNonQuery();
You need dynamic sql:
string sql = #"
DECLARE #SQL nvarchar(150);
SELECT #SQL = 'truncate table ' + quotename(table_name) + ';'
FROM information_schema.tables
WHERE table_name = #table;
EXEC(#SQL);";
using (var connection = new SqlConnection("connection string here"))
using (var cmd = new SqlCommand(sql, connection))
{
cmd.Transaction = transaction;
cmd.Parameters.Add("#table", SqlDbType.NVarChar, 128).Value = tablename;
connection.Open();
cmd.ExecuteNonQuery();
}
This is one of very few times dynamic SQL makes things more secure, rather than less. Even better, if you also maintain a special table in this database listing other tables users are allowed to truncate, and use that rather than information_schema to validate the name. The idea of letting users just truncate anything is kind of scary.
Parametrized or not, you can make it only a little more secured in this case. Never totally secured. For this you need
create table TruncMapping in DB where you store
id guid
statement varchar(300)
your data will look like
SOME-GUID-XXX-YYY, 'TRUNCATE TABLE TBL1'
In your front end use a listbox or combobox with text/value like "Customer Data"/"SOME-GUID-XXX-YYY"
In your code use ExecuteScalar to execute Select statement from TruncMapping where id = #1 , where id will be parameterized GUID from combo value
Execute your truncate command using ExecuteNonQuery as you do now but with a retrieved string from previous call.
Your scan tool will most likely choke. If it is still thinking code is unsafe, you can safely point this as false positive because what you execute is coming from your secured DB. Potential attacker has no way to sabotage your "non-tuncatable tables" because they are not listed in TruncMapping tables.
You've just created multi-layered defense against sql injection.
here is one way to hide it from scanning tools
private const string _sql = "VFJVTkNBVEUgVEFCTEU=";
. . . .
var temp = new { t = tablename };
cmd.CommandText =
Encoding.ASCII.GetString(Convert.FromBase64String(_sql)) + temp.t.PadLeft(temp.t.Length + 1);
security by obscurity

Writing information from SQL Server to C#

Below I have a piece of code that should be writing out columns from my database, however when I try to execute, it gives me an exception that says it can't read a column with no values, but its wrong because it should contain dates in the columns that I want information to come out of.
Here is the table definition:
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE
PATRICK_DEV dbo FILE_DATE_PROCESSED BASE TABLE
Here is the code:
try
{
SqlConnection connect = new SqlConnection("Server=OMADB01;Database=PATRICK_DEV;Trusted_Connection=True;");
connect.Open();
SqlCommand command = new SqlCommand("INSERT FILE_DATE_PROCESSED(UID, FILE_DATE_PROCESSED, FILE_NAME, DATE_ENTERED) SELECT newid(), '2015-12-31 19:32:45', 'myfilename.txt', getdate()", connect);
SqlDataReader reader = null;
reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(reader["FILE_DATE_PROCESSED"].ToString());
Console.WriteLine(reader["DATE_ENTERED"].ToString());
}
connect.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
This is what I am dealing with right now: I have a database in SQL that looks like this:
UID FILE_DATE_PROCESSED FILE_NAME DATE_ENTERED
In this database, every time I add a file, column 2 should contain the last time I entered the file, and column 4 should contain the time I am entering the file now. This is the result that I am looking for:
UID FILE_DATE_PROCESSED FILE_NAME DATE_ENTERED
random random date the file name the current date
number
If there is a different way of solving this problem than what I have posted please let me know I will very much appreciate it.
Your query executes an INSERT (IE. Adds data to your table) It doesn't retrive any record. The INSERT statement in T-SQL could use a SELECT subquery to feed the values that are requested by the columns listed after the INSERT.
So your query add a new record every time you run it, but there is no column returned from that query and using the reader indexer on a non existant column produces the mentioned error.
If you just want to read values then you should change your query to
try
{
using(SqlConnection connect = new SqlConnection(....))
using(SqlCommand command = new SqlCommand(
#"SELECT FILE_DATE_PROCESSED, DATE_ENTERED FROM FILE_DATE_PROCESSED", connect))
{
connect.Open();
using(SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["FILE_DATE_PROCESSED"].ToString());
Console.WriteLine(reader["DATE_ENTERED"].ToString());
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
I suggest also to change the column name FILE_DATE_PROCESSED or the table name because having two objects with the same name could be an endless source of confusion and an excellent error maker
You don't have a select statement. Changing to add a select statement with names might help:
#cmdText = #"DECLARE #FILE_DATE_PROCESSED DATETIME = '2015-12-31 19:32:45',
#DATE_ENTERED DATETIME =getdate()
INSERT FILE_DATE_PROCESSED(UID, FILE_DATE_PROCESSED, FILE_NAME, DATE_ENTERED)
SELECT newid(), #FILE_DATE_PROCESSED, 'myfilename.txt',#DATE_ENTERED
SELECT #FILE_DATE_PROCESSED AS FILE_DATE_PROCESSED, #DATE_ENTERED as DATE_ENTERED"
SqlCommand command = new SqlCommand(#cmdText)

asp.net : INSERT in oracle table only if the clause WHERE of the UPDATE find zero records

I am developping an asp.net application. I would like to know how to update a field of an oracle table record, and if the record is not present, inserting it.
I have a table with the following fields NAME and SURNAME.
I would like to change the SURNAME to "new_surname" of the record where the NAME equals="name". However, if none of the records in table contains a field NAME equals to name I would like to insert a new record (SURNAME=new_surname and NAME=name).
This is my code :
OracleConnection connection = new OracleConnection(connectionstring);
try
{
connection.Open();
OracleCommand command = connection.CreateCommand();
string sql = "UPDATE TABLE SET SURNAME=\'new_surname\' WHERE NAME=\'name\'";
command.CommandText = sql;
command.ExecuteNonQuery();
connection.Close();
}
catch (Exception exp)
{
}
Is there an optimal way to do the insert only if the update find zero records matching the "where" clause. I was thinking of first doing a select count of the record matching the "where" clause, and then if I found zero results I would do an insert, and if I found at least one result I would do an update. But I find this solution a little bit heavy.
Cannot test but you could try this
using(OracleConnection connection = new OracleConnection(connectionstring))
using(OracleCommand command = connection.CreateCommand())
{
connection.Open();
string sql = #"MERGE INTO TABLE t USING dual on(name='name')
WHEN NOT MATCHED THEN INSERT (name, surname) values ('name', 'new_surname')
WHEN MATCHED THEN UPDATE SET surname = 'new_surname'";
command.CommandText = sql;
command.ExecuteNonQuery();
}
you can modify the query as :
string sql = #"IF EXISTS (SELECT * FROM \'Given Table\' WHERE NAME=\'name\') THEN
BEGIN
--your update query
END
ELSE
BEGIN
-- your Insert Query
END
END IF";

SqlBulkInsert with a DataTable to a Linked Server

I'm working with 2 SQL 2008 Servers on different machines. The server names are source.ex.com, and destination.ex.com.
destination.ex.com is linked to source.ex.com and the appropriate permissions are in place for source.ex.com to write to a database called bacon-wrench on destination.ex.com
I've logged into source.ex.com via SMS and tested this query (successfully):
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (4,6);
In a C# .NET 4.0 WebPage I connect to source.ex.com and perform a similar query (successfully):
using(SqlConnection c = new SqlConnection(ConfigurationManager.ConnectionStrings["SOURCE"].ConnectionString))
{
c.Open();
String sql = #"
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (34,56);";
using(SqlCommand cmd = new SqlCommand(sql, c))
{
cmd.ExecuteNonQuery();
}
}
For small sets of insert statements (say 20 or less) doing something like this performs fine:
using(SqlConnection c = new SqlConnection(ConfigurationManager.ConnectionStrings["SOURCE"].ConnectionString))
{
c.Open();
String sql = #"
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (34,56);
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (22,11);
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (33,55);
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES (1,2);";
using(SqlCommand cmd = new SqlCommand(sql, c))
{
cmd.ExecuteNonQuery();
}
}
I'm trying to do something like this with around 20000 records. The above method takes 11 minutes to complete -- which I assume is the server sreaming at me to make it some kind of bulk operation. From other StackOverflow threads the SqlBulkCopy class was recommended and it takes as a parameter DataTable, perfect!
So I build a DataTable and attempt to write it to the server (fail):
DataTable dt = new DataTable();
dt.Columns.Add("PunchID", typeof(int));
dt.Columns.Add("BaconID", typeof(int));
for(int i = 0; i < 20000; i++)
{
//I realize this would make 20000 duplicate
//rows but its not important
dt.Rows.Add(new object[] {
11, 33
});
}
using(SqlConnection c = new SqlConnection(ConfigurationManager.ConnectionStrings["SOURCE"].ConnectionString))
{
c.Open();
using(SqlBulkCopy bulk = new SqlBulkCopy(c))
{
bulk.DestinationTableName = "[destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]";
bulk.ColumnMappings.Add("PunchID", "PunchID");
bulk.ColumnMappings.Add("BaconID", "BaconID");
bulk.WriteToServer(dt);
}
}
EDIT2: The below message is what I'm attempting to fix:
The web page crashes at bulk.WriteToServer(dt); with an error message Database bacon-wrench does not exist please ensure it is typed correctly. What am I doing wrong? How do I change this to get it to work?
EDIT1:
I was able to speed up the query significantly using the below syntax. But it is still very slow for such a small record set.
using(SqlConnection c = new SqlConnection(ConfigurationManager.ConnectionStrings["SOURCE"].ConnectionString))
{
c.Open();
String sql = #"
INSERT INTO [destination.ex.com].[bacon-wrench].[dbo].[tblFruitPunch]
(PunchID, BaconID) VALUES
(34,56),
(22,11),
(33,55),
(1,2);";
using(SqlCommand cmd = new SqlCommand(sql, c))
{
cmd.ExecuteNonQuery();
}
}
If you are using SQL Server 2008+, you can introduce a Table user datatype. Prepare the type, receiving table and stored procedure something like below. Data type and stored procedure is on the local system. I generally have an if statement in the code detecting whether the table is remote or local, remote I do this, local I use SqlBulkCopy.
if(TYPE_ID(N'[Owner].[TempTableType]') is null)
begin
CREATE TYPE [Owner].[TempTableType] AS TABLE ( [PendingID] uniqueidentifier, [Reject] bit)
end
IF NOT EXISTS (SELECT * FROM [LinkedServer].[DatabaseOnLS].sys.tables where name = 'TableToReceive')
EXEC('
CREATE TABLE [DatabaseOnLS].[Owner].[TableToReceive] ( [PendingID] uniqueidentifier, [Reject] bit)
') AT [LinkedServer]
else
EXEC('
TRUNCATE TABLE [DatabaseOnLS].[Owner].[TableToReceive]
') AT [LinkedServer]
CREATE PROCEDURE [Owner].[TempInsertTable]
#newTableType TempTableType readonly
AS
BEGIN
insert into [LinkedServer].[DatabaseOnLS].[Owner].[TableToReceive] select * from #newTableType
END
In the C# code you can then do something like this to insert the DataTable into the table on the linked server (I'm using an existing UnitOfWork, which already have a connection and transaction):
using (var command = new SqlCommand("TempInsertTable",
oUoW.Database.Connection as SqlConnection) { CommandType = CommandType.StoredProcedure }
)
{
command.Transaction = oUoW.Database.CurrentTransaction as SqlTransaction;
command.Parameters.Add(new SqlParameter("#newTableType", oTempTable));
drResults = command.ExecuteReader();
drResults.Close();
}
After trying a number of things including linked server settings, collations, synonyms, etc., I eventually got to this error message:
Inserting into remote tables or views is not allowed by using the BCP utility or by using BULK INSERT.
Perhaps you can bulk insert to a staging table on your local server (your code works fine for this) and then insert from that staging table to your linked server from there, followed by a local delete of the staging table. You'll have to test for performance.

Return last inserted ID without using a second query

I'm working on an ASP.NET project (C#) with SQL Server 2008.
When I insert a row into a table in the database, I would like to get the last inserted ID, which is the table's IDENTITY (Auto Incremented).
I do not wish to use another query, and do something like...
SELECT MAX(ID) FROM USERS;
Because - even though it's only one query - it feels lame...
When I insert something I usually use ExecuteNonQuery(), which returns the number of affected rows.
int y = Command.ExecuteNonQuery();
Isn't there a way to return the last inserted ID without using another query?
Most folks do this in the following way:
INSERT dbo.Users(Username)
VALUES('my new name');
SELECT NewID = SCOPE_IDENTITY();
(Or instead of a query, assigning that to a variable.)
So it's not really two queries against the table...
However there is also the following way:
INSERT dbo.Users(Username)
OUTPUT inserted.ID
VALUES('my new name');
You won't really be able to retrieve this with ExecuteNonQuery, though.
You can return the id as an output parameter from the stored procedure, e.g. #userId int output
Then, after the insert, SET #userId = scope_identity()
even though it's only one query - it feels lame...
It actually is also wrong as you can have multiple overlapping iserts.
That is one thing that I always fuind funny - people not reading the documentation.
SELECT SCOPE_IDENTITY()
returns the last identity value generated in a specific scope and is syntactically correct. It also is properly documented.
Isn't there a way to return the last inserted ID without using another query?
Yes. Ask for the number in the saame SQL batch.
INSERT (blablab9a); SELECT SCOPE_IDENTITY ();
as ONE string. ExecuteScalar.
You can have more than one SQL statement in one batch.
If you want to execute query from C# code & want to get last inserted id then you have to find the following code.
SqlConnection connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
connection.Open();
string sql = "Insert into [Order] (customer_id) values (" + Session["Customer_id"] + "); SELECT SCOPE_IDENTITY()";
SqlCommand cmd = new SqlCommand();
cmd.Connection = connection;
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
var order_id = cmd.ExecuteScalar();
connection.Close();
Console.Write(order_id);

Categories

Resources