Using the OracleClient that comes with ADO.NET in .NET Framework, I'm trying to call OracleCommandBuilder.DeriveParameters() method on a procedure in the database, but I keep getting an OracleException with the message: ORA-06564: object CustOrdersOrders does not exist, even though I created the procedure successfully. I'm more familiar with SQL Server, so perhaps I'm missing something here.
SQL
file 1:
create or replace PACKAGE PKGENTLIB_ARCHITECTURE
IS
TYPE CURENTLIB_ARCHITECTURE IS REF CURSOR;
END PKGENTLIB_ARCHITECTURE;
/
file 2
CREATE OR REPLACE PROCEDURE "CustOrdersOrders"(VCUSTOMERID IN Orders.CustomerID%TYPE := 1, CUR_OUT OUT PKGENTLIB_ARCHITECTURE.CURENTLIB_ARCHITECTURE)
AS
BEGIN
OPEN cur_OUT FOR
SELECT
OrderID,
OrderDate,
RequiredDate,
ShippedDate
FROM Orders
WHERE CustomerID = vCustomerId;
END;
/
Both these files were executed in SQL*Plus as #"path\to\file1.sql".
Code
This is using the Enterprise Library Data Access Application Block, which ultimately wraps the ADO.NET API.
DatabaseProviderFactory factory = new DatabaseProviderFactory(...); //this gets a custom configuration source
Database db = factory.Create("OracleTest");
DbCommand storedProcedure = db.GetStoredProcCommand("CustOrdersOrders");
DbConnection connection = db.CreateConnection();
connection.Open();
storedProcedure.Connection = connection;
db.DiscoverParameters(storedProcedure); //this ultimately calls OracleCommandBuilder.DeriveParameters(), which throws the exception.
When I run direct SQL queries using the same connection, they succeed.
More Details
This is actually part of unit tests written for the Data Access Application Block, which I forked here in an attempt to revive this library. That's why it's using the System.Data.OracleClient and not the ODP.NET. The entire set of tests at https://github.com/tsahi/data-access-application-block/blob/master/source/Tests/Oracle.Tests.VSTS/OracleParameterDiscoveryFixture.cs breaks in a similar way.
The tests are running on an Oracle Database XE I installed locally.
Update
Following question by #madreflection, yes, the following code runs correctly:
Database db = DatabaseFactory.CreateDatabase("OracleTest");
string spName = "AddCountry";
DbCommand dbCommand = db.GetStoredProcCommand(spName);
db.AddInParameter(dbCommand, "vCountryCode", DbType.String);
db.AddInParameter(dbCommand, "vCountryName", DbType.String);
db.SetParameterValue(dbCommand, "vCountryCode", "UK");
db.SetParameterValue(dbCommand, "vCountryName", "United Kingdom");
db.ExecuteNonQuery(dbCommand);
using (DataSet ds = db.ExecuteDataSet(CommandType.Text, "select * from Country where CountryCode='UK'"))
{
Assert.IsTrue(1 == ds.Tables[0].Rows.Count);
Assert.AreEqual("United Kingdom", ds.Tables[0].Rows[0]["CountryName"].ToString().Trim());
}
where "AddCountry" is defined as
CREATE OR REPLACE PROCEDURE ADDCOUNTRY
(vCountryCode IN Country.CountryCode%TYPE,
vCountryName IN Country.CountryName%TYPE
)
AS
BEGIN
INSERT INTO Country (CountryCode,CountryName)
VALUES (vCountryCode,vCountryName);
END;
/
It's interesting to note, though, that in this case the OracleDatabase pointed by db has in it's packages list just EntlibTest, defined (if I understand correctly) by
CREATE OR REPLACE PACKAGE EntlibTest AS
PROCEDURE GetProductDetailsById
(vProductID IN NUMBER,vProductName OUT VARCHAR2,vUnitPrice OUT NUMBER);
END EntlibTest;
/
and then there is another file defining the body of this procedure with
CREATE OR REPLACE PACKAGE BODY EntlibTest AS
PROCEDURE GetProductDetailsById
(vProductID IN NUMBER,vProductName OUT VARCHAR2,vUnitPrice OUT NUMBER)
AS
BEGIN
SELECT ProductName,UnitPrice INTO vProductName,vUnitPrice FROM Products where ProductId = vProductId;
END;
END EntlibTest;
/
Related
I am using EntityFramework and code migrations to keep my Postgres database up to date.
Before deployment on a new environment I would like to make sure the database exists and that the user that will execute queries for the application has enough permissions to do this.
So in short I would like to do the following from my Migrations project before running context.Database.Migrate():
Check whether the database exists
If it doesn't exist create it
Create the user if it doesn't exists
Grant permissions to the user
Run migrations
I have two options (I think):
Run multiple statements at once, keeping the logic inside the query itself and avoid having it in code (C#)
Run statements and deciding in code what to do next.
Option 1 has my preference but when I run the statement checking whether the database exists and create it if it doesn't I get an error: CREATE DATABASE cannot be executed from a function.
The query I'm running looks like this:
DO $$
BEGIN
IF NOT EXISTS(SELECT datname FROM pg_database WHERE datname='database_name') THEN
CREATE DATABASE database_name;
END IF;
END
$$
Option 2 would involve running the statement to check if the database exists with the following code:
using (var conn = new NpgsqlConnection("connection"))
{
conn.Open();
var sql = "SELECT 1 FROM pg_database WHERE datname='database_name'";
var command = new NpgsqlCommand(sql, conn);
var dbExists = command.ExecuteScalar();
if(dbExists == null)
{
command = new NpgsqlCommand("CREATE DATABASE database_name", conn);
command.ExecuteNonQuery();
}
conn.Close();
}
The above code works but I think I prefer option 1.
So I have 2 questions:
How can I fix the CREATE DATABASE cannot be executed from a function error.
Is the second option considered 'wrong'?
I am working on a program in c# where I have a layout as shown in the image below:
The purpose of the program is to perform data archiving in SQL server. If I choose "Create Tables", it will generate new tables into my database ( should generate about 40 tables in order) which has similar table structure (columns,constraint,triggers,etc) as original tables in the same database as well. How this works is I'll execute the SQL scripts in c# and call them (all 40 scripts) to create tables.
Right now, I added another button "Transfer data" where it will select specfic data(based on date) in old data and transfer them into the new tables I created. I will use the query Insert Into....SELECT from to transfer data.
My question is should I create sql scripts for transferring data and execute them in c# or just put the SQL queries inside my c# code ?
If I go with SQL scripts, should I split them into 40 scripts as well or place all the queries inside 1 script? I know it will be tedious if i put everything in one script as if an error occurs, it's hard to trace the source of the problem.
Below is a sample of how the sql query looks like :
SET IDENTITY_INSERT Kiosk_Log_New ON
INSERT INTO Kiosk_Log_New(LOGID,
logAPPID,
logLOGTID,
logGUID,
logOriginator,
logReference,
logAssemblyName,
logFunctionName,
logMessage,
logException,
CreatedBy,
CreatedDate)
SELECT LOGID,
logAPPID,
logLOGTID,
logGUID,
logOriginator,
logReference,
logAssemblyName,
logFunctionName,
logMessage,
logException,
CreatedBy,
CreatedDate FROM Kiosk_Log
WHERE CreatedDate BETWEEN '2015-01-01' AND GETDATE()
EDIT: Since many suggested stored procedure is the best option, this would be my create tables script:
string constr = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
/* open sql connection to execute SQL script: PromotionEvent_New */
try
{
using (SqlConnection con = new SqlConnection(constr))
{
con.Open();
FileInfo file = new FileInfo("C:\\Users\\88106221\\Documents\\SQL Server Management Studio\\PromotionEvent_New.sql");
string script = file.OpenText().ReadToEnd();
Server server = new Server(new ServerConnection(con));
server.ConnectionContext.ExecuteNonQuery(script);
Display("PromotionEvent_New table has been created successfully");
con.Close();
}
}
catch(Exception ex)
{
textBox1.AppendText(string.Format("{0}", Environment.NewLine));
textBox1.AppendText(string.Format("{0} MainPage_Load() exception - {1}{2}", _strThisAppName, ex.Message, Environment.NewLine));
Display(ex.Message + "PromotionEvent_New could not be created");
textBox1.AppendText(string.Format("{0}", Environment.NewLine));
Debug.WriteLine(string.Format("{0} MainPage_Load() exception - {1}", _strThisAppName, ex.Message));
}
It's best to use a stored procedure with a transaction to execute all your INSERT queries.
It's not advisable to submit queries from your C# code as explained in last post by John Ephraim Tugado due to a number of reasons; the most important reasons being,
easier maintenance of INSERT queries
minimal bandwidth consumption between web server and database server
Sending long queries strings from C# code will consume more bandwidth between web server and database server and could slow the database response in a high traffic scenario.
You can execute the following T-SQL code against your database to create a stored procedure for transferring/archiving data to archived tables. This procedure makes sure that all your INSERTS are executed within a transaction, that ensures you don't end up with orphaned tables and unnecessary headaches down the road.
Stored Procedure for transferring data
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Lord Cookie
-- Create date: 11/01/2017
-- Description: Transfers data to existing archived tables
-- =============================================
CREATE PROCEDURE dbo.ArchiveData
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
--use transaction when inserting data else you may end up with orphaned data and hard to debug issues later on
BEGIN TRANSACTION
--add your INSERT queries one after the other below
SET IDENTITY_INSERT Kiosk_Log_New ON
INSERT INTO Kiosk_Log_New (LOGID,
logAPPID,
logLOGTID,
logGUID,
logOriginator,
logReference,
logAssemblyName,
logFunctionName,
logMessage,
logException,
CreatedBy,
CreatedDate)
SELECT
LOGID
,logAPPID
,logLOGTID
,logGUID
,logOriginator
,logReference
,logAssemblyName
,logFunctionName
,logMessage
,logException
,CreatedBy
,CreatedDate
FROM Kiosk_Log
WHERE CreatedDate BETWEEN '2015-01-01' AND GETDATE()
--add more of your insert queries below
-- finally commit transaction
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DECLARE #errorDetails NVARCHAR(MAX);
set #errorDetails = 'Error ' + CONVERT(VARCHAR(50), ERROR_NUMBER()) +
', Severity ' + CONVERT(VARCHAR(5), ERROR_SEVERITY()) +
', State ' + CONVERT(VARCHAR(5), ERROR_STATE()) +
', Line ' + CONVERT(VARCHAR(5), ERROR_LINE());
--roll back the transaction
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRANSACTION
END
--you can log the above error message and/or re-throw the error so your C# code will see an error
--but do this only after rolling back
END CATCH;
END
GO
You can then call the above stored procedure using C# as shown in sample code below.
Call above stored procedure using C#
using(SqlConnection sqlConn = new SqlConnection("Your database Connection String")) {
using(SqlCommand cmd = new SqlCommand()) {
cmd.CommandText = "dbo.ArchiveData";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = sqlConn;
sqlConn.Open();
cmd.ExecuteNonQuery();
}
}
Depending on the table naming and design, I would suggest creating a script to create a stored procedure for this that would generate one for each of your tables. I'm no expert in scripting but it is the same with the script that generates an audit trail for each of your tables or at least the ones you defined in the script.
Hard-coding this inside your c# application is a big NO as there is the possibility of database changes. We would want our app to be flexible to change with the least amount of effort.
If generating the script to create a stored procedure is hard for you, I would still recommend manually creating stored procedures for this task.
I am developing (converting application db from MS SQL to MySQL) an application using C#.Net and MySQL. My C# code and stored procedure is working perfect in MS SQL but when trying to ingrate with MySQL getting parameter error. My C# Code is as below and MySQL Stored Procedure is running perfectly (tested in editor using CALL key work and parameter)
public DataTable AlapValidateUser(string email, string password,string Type)
{
DataTable dt = new DataTable();
cmd = new MySqlCommand("UserIdValidation");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = cnn;
string pass = reEncryptpassword(password);
MySqlParameter pramEmail = new MySqlParameter("#v_emailId", email);
pramEmail.Direction = ParameterDirection.Input;
cmd.Parameters.Add(pramEmail);
MySqlParameter pramPassword = new MySqlParameter("#v_password", pass);
pramPassword.Direction = ParameterDirection.Input;
cmd.Parameters.Add(pramPassword);
MySqlDataAdapter adap = new MySqlDataAdapter(cmd);
if (cnn.State != ConnectionState.Open ||
cnn.State == ConnectionState.Broken ||
cnn.State != ConnectionState.Connecting ||
cnn.State != ConnectionState.Executing ||
cnn.State != ConnectionState.Fetching)
cnn.Open();
adap.Fill(dt);
cnn.Close();
return dt;
}
MySQL Stored Procedure is here:
CREATE DEFINER=`root`#`localhost` PROCEDURE `UserIdValidation`(v_emailId NATIONAL VARCHAR(100),v_password
NATIONAL VARCHAR(50))
BEGIN
SELECT UserId ,eMail,BloodGroup
,BloodGroupID,Country AS CountrySlNo ,CountryName ,State ,District
,Location,fName,lName ,DonorType ,LastLogIn ,Validated ,ProfileImage
,MaritalStatus ,Sex ,Height ,Weight ,HealthStatus
,MyFileLocation FROM vwUser WHERE eMail = v_emailId AND
PASSWORD = v_password AND Validated = 'Y';
END$$
During execution exception as below:
Incorrect number of arguments for PROCEDURE alap.UserIdValidation; expected 2, got 1
Can you please help me to find out the error.
UPDATE: My MySQL connector is v.6.6.5. I have checked in debug mode in C# parameter is correct and can see both parameter in command object. Next it is trying to filling Adapter hence this command object is passing to MySQL from Connector and there parameter is missing. I have tried to add same 1st parameter by creating 3rd line then getting error that same parameter already exist.
From this test I am sure it is purely MySQL or mysql connector bug.
I don't know how this bug can exists in such DB where so many people is using mysql.
This is a BUG of MY SQL and I have received solution from MySQL Team. I have implemented this changes in my MySQL Stored Procedure and got solution. MySQL reply as below
Hi Suman,
I have reproduced the issue you described, and exists a workaround that
works perfectly fine and hopefully it will keep you up to speed in your
migration to MySql. The problem here is the NATIONAL usage in the
definition of the parameters for the routine. If you want to define a
particular character set you can define the routine like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE
`UserIdValidation`(v_emailId VARCHAR(100) ***CHARACTER SET utf8***,v_password
VARCHAR(50) CHARACTER SET utf8)
BEGIN
.... rest of code
Or you just can use
CREATE DEFINER=`root`#`localhost` PROCEDURE
`UserIdValidation`(v_emailId VARCHAR(100),v_password VARCHAR(50))
BEGIN
....
The default character set in the server is utf8 and is equivalent to
NATIONAL according to the documentation.
You can check more information here:
http://dev.mysql.com/doc/refman/5.5/en/charset-national.html
Please let me know if the workaround worked for you.
I think you might need to change the way you call the stored proc.
Take a look at this example http://forums.asp.net/t/988462.aspx
I'm having problems to run a script inside a custom action. The script creates and sets up a database in a localhost MySQL server.
Concretely, I have problems with one of my procedures:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `UpdateAutoInc`(IN `increment` BIGINT)
BEGIN
SET #s = concat('ALTER TABLE tblactionservices AUTO_INCREMENT ',increment);
PREPARE stmt FROM #s;
EXECUTE stmt;
SET #s = concat('ALTER TABLE tblbannedclient AUTO_INCREMENT ',increment);
PREPARE stmt FROM #s;
EXECUTE stmt;
END$$
DELIMITER ;
In the script, before this procedure, I have 2 other procedures that run perfectly.
The error I get when I install the application is "Parameter '#s' must be defined". Looking for the Internet I found this blog but I added the "Allow User Variables=True;" with no luck. With this piece of text, the script crashes directly on the first procedure. Indeed, it crashes always, no matter what it finds (procedures, tables...). The error I get is absolutely useless "You have an error in your SQL Syntax; check the manual bla bla", so I can't find the problem.
This is the class I'm using to run the script from c#:
private void ExecuteSql(string DatabaseName, string Sql)
{
MySqlConnection connection = new MySqlConnection { ConnectionString = "server=127.0.0.1;User Id=root" };
MySqlCommand command = new MySqlCommand(Sql, connection);
command.Connection.Close();
command.Connection.Open();
try
{
command.ExecuteNonQuery();
}
finally
{
// Closing the connection should be done in a Finally block
command.Connection.Close();
}
}
The script wasn't manually typed but it was automatically generated from PhPMyAdmin. The version of MySQL server is 5.5 and the connector version is 6.5.5.0
I am writing a CLR Update trigger for SQL Server 2008 R2. The trigger needs to write updated values to a table in another database hosted in the same SQL Server instance. When I try to open a connection created with the following connection string from within my trigger I get a "SecurityException":
...new SqlConnection("Data Source=(local);Initial Catalog=[my database];Integrated Security=True")
It is highly desirable that I leave my assembly's permission level as SAFE. I am pretty sure that I'd have to set my assembly's permission level to EXTERNAL_ACCESS to connect to a remote database, but is it possible to connect to another database in the same SQL Server instance with the SAFE permission level?
Thanks.
Yes, it is definitely possible to reference another database in a SQLCLR trigger that resides inside of an Assembly marked as SAFE. The error encountered in the question is due simply to using a regular / external connection string, which requires a PERMISSION_SET of EXTERNAL_ACCESS. But using the in-process / internal connection string of "Context Connection = true;" allows you to run whatever query you want, including a query that references another database via 3-part object name.
I was able to do this with the following code:
Main Table (in TestDB1):
CREATE TABLE dbo.Stuff
(
[Id] INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
[Something] NVARCHAR(50) NOT NULL
);
Audit Table (in TestDB2):
CREATE TABLE dbo.AuditLog
(
AuditLogID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
EventTime DATETIME NOT NULL DEFAULT (GETDATE()),
BeforeValue NVARCHAR(50) NULL,
AfterValue NVARCHAR(50) NULL
);
SQLCLR Trigger on main table (partial code):
string _AuditSQL = #"
INSERT INTO TestDB2.dbo.AuditLog (BeforeValue, AfterValue)
SELECT del.Something, ins.Something
FROM INSERTED ins
FULL OUTER JOIN DELETED del
ON del.Id = ins.Id;
";
SqlConnection _Connection = new SqlConnection("Context Connection = true");
SqlCommand _Command = _Connection.CreateCommand();
_Command.CommandText = _AuditSQL;
try
{
_Connection.Open();
_Command.ExecuteNonQuery();
}
finally
{
_Command.Dispose();
_Connection.Dispose();
}
Test queries:
USE [TestDB1];
SELECT * FROM dbo.Stuff;
---
INSERT INTO dbo.Stuff (Something) VALUES ('qwerty');
INSERT INTO dbo.Stuff (Something) VALUES ('asdf');
SELECT * FROM dbo.Stuff;
SELECT * FROM TestDB2.dbo.AuditLog;
---
UPDATE tab
SET tab.Something = 'dfgdfgdfgdfgdfgdfgd'
FROM dbo.Stuff tab
WHERE tab.Id = 2;
SELECT * FROM dbo.Stuff;
SELECT * FROM TestDB2.dbo.AuditLog;
It looks like it's not possible. However a T-SQL statement can reference another database in the same instance by using [DatabaseName].[dbo].[TableName]. I can do the messy logic in my CLR trigger, then do a final insert into the second database by calling a simple T-SQL stored procedure and passing in parameters.