I am working on a big solution in C#. This solution uses different oracle databases and is checked in in TFS. Actually I develop a small library which can deploy the SQL Scripts in the solution to the used databases. The deployment of the application is handled with TFS and in the future my tool should be included in the build process. For the Oracle Connection an execution of the statements I use Oracle.ManagedDataAccess.Client.
If there are only several DDL or DML statements in the SQL Files the first tests were successful. But there are a few special special things I have to handle, like SET DEFINE OFF. This is no real SQL Statement and if I run the Script with ORacle SQL*PLus there is no problem. In the beginning I tried to execute the whole script but this doesn't work because multiple statements were put in one line by my application and the only solution I found was the splitting of the file and single execution.
Example:
/*************************************************************************************************************************
##NAME ScriptName.sql
##DESCR ScriptDescription
*************************************************************************************************************************/
Create table tmp_thm_Test
( tmp_thm_Test_id number(8)
, text varchar2(200));
Create table tmp_thm_Test2
( tmp_thm_Test2_id number(8)
, text varchar2(200));
This is an example content for a script. Also there can be Insert (Set Define off is needed while having the & in strings), Update Statements. Also Begin/End Scripts.
If there are only DDL and DML Statements I can split them but Begin/End parts are not splittable. At the beginning I thought I can deploy a script like in the past with SQL*Plus (#Scriptname.sql). Read the whole script an execute it.
Any ideas?
Since I've worked with Oracle in C# before I kind of understand you even with the lack of concrete samples.
SET DEFINE OFF never worked for me in C#, so I simply use query = query.Replace("'","''");. You could also use command parameters like the one below to avoid exceptions.
string query = string.Format(
#"UPDATE CardStatus
SET DateExpired = #dateExpired,
ModifiedBy = #modifiedBy,
ModifiedOn = #modifiedOn
WHERE
IDNo = #idNo");
List<OracleParameter> parameters = new List<OracleParameter>();
OracleParameter pIdNo = new OracleParameter("idNo", OracleDbType.Varchar2);
pIdNo.Value = idNo;
OracleParameter pDateExpired = new OracleParameter("dateExpired", OracleDbType.Date);
pDateExpired.Value = dateExpired;
OracleParameter pModifiedBy = new OracleParameter("modifiedBy", OracleDbType.Varchar2);
pModifiedBy.Value = "SIS";
OracleParameter pModifiedOn = new OracleParameter("modifiedOn", OracleDbType.Date);
pModifiedOn.Value = DateTime.Now;
parameters.Add(pIdNo);
parameters.Add(pDateExpired);
parameters.Add(pModifiedBy);
parameters.Add(pModifiedOn);
bool result = _DAL.ExecuteNonQuery(query, parameters.ToArray());
_DAL is simply my helper class for data access, it manages the query executions placing it inside a single transaction so that I get to decide whether to commit or rollback the changes of single to multiple queries.
public bool ExecuteNonQuery(string query, object[] parameters, [CallerMemberName] string callerMemberName = "")
{
bool success = false;
try
{
using (OracleCommand command = new OracleCommand())
{
command.Connection = _connection;
command.Transaction = _transaction;
command.CommandText = query;
command.CommandType = CommandType.Text;
command.Parameters.AddRange(parameters);
command.ExecuteNonQuery();
}
this.AuditSQL(query, string.Empty);
success = true;
}
catch (OracleException ex)
{
this.AuditSQL(query, ex.Message, callerMemberName);
if (ex.Number == 54) // SELECT .. FOR UPDATE NOWAIT failed.
{
throw new RowLockException();
}
}
return success;
}
I am not sure if you are interested in looking at my _DAL class but I provided you a snippet to give you an idea of what you can do.
EDIT: Since you've mentioned that the scripts already exists then it is easier, you just have to make sure that there is no ' character within the script to make oracle ask for a variable value and all batch queries must have BEGIN and END;.
Example:
BEGIN
-- INSERT
-- UPDATE
-- INSERT
... etc.
END;
Now, what exactly is your problem? You can read the query from the file and replace all SET DEFINE OFF (as well as other statements you deem unnecessary) with string.Empty and replace all ' with '' if needed. Worst case, you would be using regex to identify and remove unwanted statements.
Related
I know I can run them with one ExecuteNonQuery() per statement, but I am being asked to run a large text file with many statements. A user who understands SQL (not a programmer) will update the file as the queries change in the future.
A typical file has a lot of CREATE VOLATILE TABLE statements with some INSERTs, DELETEs, and UPDATEs interspersed. If I try this as I have below, I get [Teradata Database] [3932] Only an ET or null statement is legal after a DDL Statement. Adding an ET; after either or both statements still returns the same error.
using (TdConnection tdConnection = new TdConnection())
{
TdConnectionStringBuilder conn = new TdConnectionStringBuilder()
{
DataSource = "XXXXX",
UserId = "ID",
Password = "PW",
AuthenticationMechanism = "LDAP",
ConnectionPooling = false
};
tdConnection.ConnectionString = conn.ConnectionString;
tdConnection.Open();
TdCommand command = new TdCommand(#"
CREATE VOLATILE TABLE ABC AS
(
...
)
WITH DATA PRIMARY INDEX(ABC_ID)
ON COMMIT PRESERVE ROWS;
DELETE FROM ABC;
", tdConnection)
{
CommandTimeout = 0
};
command.ExecuteNonQuery();
}
I see that "Multi-statement" is valid in the documentation, but I can't seem to understand why it's not working in this case. It does work if I run it from Teradata SQL Assistant rather than code.
I also understand that this is not the ideal way to implement this. I know I can load the statements individually or use stored procedures, but I'm not being given a choice in the design in this case.
I have an C# method to execute a SQL job. It executes the SQL job successfully.
And the code works perfect.
And I'm using standard SQL stored procedure msdb.dbo.sp_start_job for this.
Here is my code..
public int ExcecuteNonquery()
{
var result = 0;
using (var execJob =new SqlCommand())
{
execJob.CommandType = CommandType.StoredProcedure;
execJob.CommandText = "msdb.dbo.sp_start_job";
execJob.Parameters.AddWithValue("#job_name", "myjobname");
using (_sqlConnection)
{
if (_sqlConnection.State == ConnectionState.Closed)
_sqlConnection.Open();
sqlCommand.Connection = _sqlConnection;
result = sqlCommand.ExecuteNonQuery();
if (_sqlConnection.State == ConnectionState.Open)
_sqlConnection.Close();
}
}
return result;
}
Here is the sp which executing inside the job
ALTER PROCEDURE [Area1].[Transformation]
AS
BEGIN
SET NOCOUNT ON;
SELECT NEXT VALUE FOR SQ_COMMON
-- Transform Master Data
exec [dbo].[sp_Transform_Address];
exec [dbo].[sp_Transform_Location];
exec [dbo].[sp_Transform_Product];
exec [dbo].[sp_Transform_Supplier];
exec [dbo].[sp_Transform_SupplierLocation];
-- Generate Hierarchies and Product References
exec [dbo].[sp_Generate_HierarchyObject] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_HierarchyObject] 'Area1',RMDemand,2;
exec [dbo].[sp_Generate_Hierarchy] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_Hierarchy] 'Area1',RMDemand,2;
exec [dbo].[sp_Generate_ProductReference] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_ProductReference] 'Area1',RMDemand,2;
-- Transform Demand Allocation BOM
exec [Area1].[sp_Transform_FGDemand];
exec [Area1].[sp_Transform_FGAllocation];
exec [Area1].[sp_Transform_RMDemand];
exec [Area1].[sp_Transform_RMAllocation];
exec [Area1].[sp_Transform_BOM];
exec [Area1].[sp_Transform_RMDemand_FK];
-- Transform Purchasing Document Data
exec [dbo].[sp_Transform_PurchasingDoc];
exec [dbo].[sp_Transform_PurchasingItem];
exec [dbo].[sp_Transform_ScheduleLine];
exec [dbo].[sp_CalculateRequirement] 'Area1'
exec [dbo].[sp_Create_TransformationSummary] 'Area1'
-- Trauncate Integration Tables
exec [dbo].[sp_TruncateIntegrationTables] 'Area1'
END
The problem is, even the job is executed successfully or not it always returns -1. How can I identify whether job is successfully executed or not.
After running msdb.dbo.sp_start_job the return code is mapped to an output parameter. You have the opportunity to control the parameter's name prior to execution:
public int StartMyJob( string connectionString )
{
using (var sqlConnection = new SqlConnection( connectionString ) )
{
sqlConnection.Open( );
using (var execJob = sqlConnection.CreateCommand( ) )
{
execJob.CommandType = CommandType.StoredProcedure;
execJob.CommandText = "msdb.dbo.sp_start_job";
execJob.Parameters.AddWithValue("#job_name", "myjobname");
execJob.Parameters.Add( "#results", SqlDbType.Int ).Direction = ParameterDirection.ReturnValue;
execJob.ExecuteNonQuery();
return ( int ) sqlCommand.Parameters["results"].Value;
}
}
}
You need to know the datatype of the return code to do this - and for sp_start_job, it's SqlDbType.Int.
However, this is only the results of starting the job, which is worth knowing, but isn't the results of running your job. To get the results running of your job, you can periodically execute:
msdb.dbo.sp_help_job #jobName
One of the columns returned by the procedure is last_run_outcome and probably contains what you're really interested in. It will be 5 (unknown) while it's still running.
A job is usually the a number of steps - where each step may or may not be executed according to the outcome of previous steps. Another procedure called sp_help_jobhistory supports a lot of filters to specify which specific invocation(s) and/or steps of the job you're interested in.
SQL likes to think about jobs as scheduled work - but there's nothing to keep you from just starting a job ad-hoc - although it doesn't really provide you with much support to correlate your ad-hoc job with an instance is the job history. Dates are about as good as it gets (unless somebody knows a trick I don't know.)
I've seen where the job is created ad-hoc job just prior to running it, so the current ad-hoc execution is the only execution returned. But you end up with a lot of duplicate or near-duplicate jobs laying around that are never going to be executed again. Something you'll have to plan on cleaning up afterwards, if you go that route.
A note on your use of the _sqlConnection variable. You don't want to do that. Your code disposes of it, but it was apparently created elsewhere before this method gets called. That's bad juju. You're better off just creating the connection and disposing of it the same method. Rely on SQL connection pooling to make the connection fast - which is probably already turned on.
Also - in the code you posted - it looks like you started with execJob but switched to sqlCommand - and kinda messed up the edit. I assumed you meant execJob all the way through - and that's reflected in the example.
From MSDN about SqlCommand.ExecuteNonQuery Method:
For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. For all other types of statements, the return value is -1. If a rollback occurs, the return value is also -1.
In this line:
result = sqlCommand.ExecuteNonQuery();
You want to return the number of rows affected by the command and save it to an int variable but since the type of statement is select so it returns -1. If you test it with INSERT or DELETE or UPDATE statements you will get the correct result.
By the way if you want to get the number of rows affected by the SELECT command and save it to an int variable you can try something like this:
select count(*) from jobs where myjobname = #myjobname
And then use ExecuteScalar to get the correct result:
result = (int)execJob.ExecuteScalar();
You need to run stored proceedure msdb.dbo.sp_help_job
private int CheckAgentJob(string connectionString, string jobName) {
SqlConnection dbConnection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand();
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandText = "msdb.dbo.sp_help_job";
command.Parameters.AddWithValue("#job_name", jobName);
command.Connection = dbConnection;
using (dbConnection)
{
dbConnection.Open();
using (command){
SqlDataReader reader = command.ExecuteReader();
reader.Read();
int status = reader.GetInt32(21); // Row 19 = Date Row 20 = Time 21 = Last_run_outcome
reader.Close();
return status;
}
}
}
enum JobState { Failed = 0, Succeeded = 1, Retry = 2, Cancelled = 3, Unknown = 5};
Keep polling on Unknown, until you get an answer. Lets hope it is succeeded :-)
I followed this answer,
How can I supply a List<int> to a SQL parameter?
Please see these questions of mine for understanding scenario,
How can I update Crate IDs of List of Fruits in single SQL query in c#
how can i update SQL table logic
What I am trying and not working
private void relate_fruit_crate(List<string> selectedFruitIDs, int selectedCrateID)
{
string updateStatement = "UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID = #selectedFruitIDs";
using (SqlConnection connection = new SqlConnection(ConnectionString()))
using (SqlCommand cmd = new SqlCommand(updateStatement, connection))
{
connection.Open();
cmd.Parameters.Add(new SqlParameter("#selectedCrateID", selectedCrateID.ToString()));
cmd.Parameters.Add(new SqlParameter("#selectedFruitIDs", String.Join(",",selectedFruitIDs.ToArray())));
cmd.ExecuteNonQuery();
}
}
My code runs without any error,
You need to use the IN keyword in your scenario. The problem is that the SqlCommand.Parameters pattern does not build the query itself, but calls a stored procedure on the database:
exec sp_executesql N'UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID in(''#selectedFruitIDs'')', N'#selectedCrateID nvarchar(1),#selectedFruitIDs nvarchar(5)', #selectedCrateID = N'1', #selectedFruitIDs = N'1,2'
This will not work as the array is escaped.
The workaround would be to either use a normal StringBuilder to create the query. (Warning! SQL Injection) or to call the query for each ID separately.
Maybe there's a way to do this with the SqlCommand.Parameters, but I could not find one.
OLD POST::
string updateStatement = "UPDATE relate_fruit_crate set CrateID IN ('#selectedCrateID') where FruitID = '#selectedFruitIDs'";
[....]
cmd.Parameters.Add(new SqlParameter("#selectedFruitIDs", String.Join("','",selectedFruitIDs.ToArray())));
and equals (=) query will only match a single value.
Multi-value parameter queries are a bit of a pain in TSQL. There are options like table-valued parameters, or "split" UDFs - otherwise... it is a bit tricky. You end up having to add multiple parameters (depending on the data), and change the query to suit. If I may suggest... a library like "dapper" may help you here - it is designed to make scenarios like this easy:
using Dapper; // at the top of your code file, to enable dapper
...
private void relate_fruit_crate(List<string> selectedFruitIDs, int selectedCrateID)
{
// note the slightly unusual "in" here (no paranethesis) - that is because
// dapper is going to do some voodoo...
using (SqlConnection connection = new SqlConnection(ConnectionString()))
{
connection.Open();
connection.Execute(
"UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID in #selectedFruitIDs",
new { selectedFruitIDs, selectedCrateID });
}
}
here "dapper" does all the work of figuring out how to express that in using multiple parameters, adding the correct number of parameters. It is also just much easier (in particular, look at how little work we did with commands and parameters; it handles readers nicely too).
Dapper is freely available from NuGet
I'm trying to update an MSSQL table using SqlCommand, I think it's a syntax error with my T-SQL, but here is what I have so far:
SqlCommand sqlCmd = new SqlCommand("UPDATE yak_tickets SET email = #emailParam, subject = #subjectParam, text = #textParam, statusid = #statusIDParam, ticketClass = #ticketClassParam WHERE id = #ticketIDParam", sqlConn);
The parameters are working as they should, however, the table never gets updated when I run the code. Any help would be appreciated =)
Here is the rest of the code:
#region Parameters
/* Parameters */
sqlCmd.Parameters.Add("#ticketIDParam", SqlDbType.BigInt);
sqlCmd.Parameters["#ticketIDParam"].Value = ticketID;
sqlCmd.Parameters.Add("#emailParam", SqlDbType.NVarChar);
sqlCmd.Parameters["#emailParam"].Value = ticketToBeSubmitted.getEmail();
sqlCmd.Parameters.Add("#subjectParam", SqlDbType.NVarChar);
sqlCmd.Parameters["#subjectParam"].Value = ticketToBeSubmitted.getSubject();
sqlCmd.Parameters.Add("#textParam", SqlDbType.Text);
sqlCmd.Parameters["#textParam"].Value = ticketToBeSubmitted.getTicketContent();
sqlCmd.Parameters.Add("#statusIDParam", SqlDbType.NVarChar);
sqlCmd.Parameters["#statusIDParam"].Value = ticketToBeSubmitted.getStatus();
sqlCmd.Parameters.Add("#ticketClassParam", SqlDbType.NVarChar);
sqlCmd.Parameters["#ticketClassParam"].Value = ticketToBeSubmitted.getTicketClass();
#endregion
#region Try/Catch/Finally
/* Try/Catch/Finally */
try
{
sqlConn.Open();
sqlCmd.ExecuteNonQuery();
}
catch (SqlException sqlEx)
{
sqlErrorLabel.Text = sqlEx.ToString();
sqlErrorLabel.ForeColor = System.Drawing.Color.Red;
}
finally
{
sqlConn.Close();
}
And the method's signature:
public static void updateTicketInDatabase(Ticket ticketToBeSubmitted, Label sqlErrorLabel, int ticketID)
UPDATE FROM is invalid syntax (edit: OP corrected this). The problem might also be the "text" column. text is a keyword in SQL Server, since it's a datatype. Try putting brackets around it.
UPDATE yak_tickets
SET email = #emailParam,
subject = #subjectParam,
[text] = #textParam,
statusid = #statusIDParam,
ticketClass = #ticketClassParam
WHERE id = #ticketIDParam
Had to use if(!Page.IsPostBack)
Couple of questions:
Is this inside of a transaction thats getting rolledback?
Have you verified that you #ticketIDParam matches a set of rows on the table? Especially if its not just a integer key
Are you updating rows that have no side effects (i.e. your updating to the same values)?
Can you provide the paramaters.Add statements for this query
Is there a trigger or other setting on the table (I assume not, as you did not mention anything).
You said you know the params are working correctly, can you say how you verified this? (profiler, visual inspection, etc).
Sounds like your hosting provider limits your debug options, forcing you to do it the old fashioned way. What if immediately after the update, you put something like:
;SELECT ##ROWCOUNT
then instead of ExecuteNonQuery, do ExecuteScalar, and see if SQL even thinks its updated anything.
I have C# code that cycles through .sql files and executes what's inside them to set up a database.
One .sql file is basically as follows:
DROP PROCEDURE IF EXISTS myProc;
DELIMITER $$
CREATE PROCEDURE myProc()
BEGIN
-- procedure stuff goes here
END $$
DELIMITER ;
CALL myProc();
When I input this into the MySQL Query Browser's script window, it runs perfectly... over and over again, just as one would want it to.
However, if I put the string into my IDbCommand and execute it...
connection.Open(); // An IDbConnection
IDbTransaction transaction = connection.BeginTransaction();
using (IDbCommand cmd = connection.CreateCommand())
{
cmd.Connection = connection;
cmd.Transaction = transaction;
cmd.CommandText = line;
cmd.CommandType = CommandType.Text;
try
{
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
transaction.Rollback();
return false;
}
}
transaction.Commit();
connection.Close();
... I get the dreaded exception 1064...
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 PROCEDURE myProc() BEGIN...
So, the question is... why does MySQL let me do this with no problems, but when I try to run it from C#, it fails? And of course the second question is how I'm supposed to fix it.
For those looking for a quick snippet...
var connectionString = #"server=ChangeMe;user=ChangeMe;pwd=ChangeMe;database=ChangeMe;";
var scriptText = File.ReadAllText(#"C:\script.sql");
using (var connection = new MySqlConnection(connectionString))
{
var script = new MySqlScript(connection, scriptText);
connection.Open();
script.Execute();
}
I think what you are looking for is this: "Bug #46429: use DELIMITER command in MySql.Data.MySqlClient.MySqlScript"
Some of the commands in your script are interpreted bt the mysqlk client excutable before the SQL is sent to the server.
See mysql commands
In effect all the delimiters
To interpret the script from C# you will have to write code that knows what the command is, you cannot just pass the commands line by line to the server.
e.g. you have 3 commands in that file and so want to call ExecuteNonQuery only 3 times
and from paracycle's coment you need to use the MySqlScript class