TSQL logging inside transaction using CLR stored procedure - c#

I'm trying to write a log into another database inside a transaction so that the log will survive even if the transaction is rolled back. I've read this answer which says:
One possibility is to use a CLR stored procedure to do the logging. This can open its own connection to the database outside the transaction and enter and commit the log data.
So I created CLR stored procedure using this article:
[SqlProcedure]
public static void Voice(SqlString procedureName, SqlInt32 id)
{
Connection = new SqlConnectionStringBuilder();
Connection.ContextConnection = true;
using (TransactionScope transScope = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(Connection.ToString()))
{
conn.Open();
SqlCommand cmdInsert = conn.CreateCommand();
cmdInsert.CommandText = sql;
cmdInsert.Parameters.Add("#id", SqlDbType.Int);
cmdInsert.Parameters[0].Value = id;
cmdInsert.Parameters.Add("#procedureName", SqlDbType.NVarChar);
cmdInsert.Parameters[1].Value = procedureName;
cmdInsert.ExecuteNonQuery();
}
transScope.Complete();
}
}
However, data is not saved afer I executed and rolled back stored procedure in SQL Server:
BEGIN TRAN
EXEC dbo.SayHelloVoice #id = 1,
#procedureName = N'FooProcedure'
ROLLBACK TRAN
We have three environments:
dev. Server name is Q-SQL001
test. Server name is Q-SQL002
prod. Server name is Q-SQL003
So this CLR stored procedure should work on all environments.
Could you say what I am doing wrong?
Any help would be greatly appreciated!
UPDATE:
So the work version looks like this. Big thanks to the #Milney:
var serverName = string.Empty;
var dbName = string.Empty;
serverName = SqlExecuteScalar("SELECT ##SERVERNAME");
dbName = SqlExecuteScalar("SELECT DB_NAME()");
SqlConnectionStringBuilder sqlConn = new SqlConnectionStringBuilder();
sqlConn.InitialCatalog = dbName;
sqlConn.DataSource = serverName;
sqlConn.IntegratedSecurity = true;
sqlConn.ContextConnection = false;
sqlConn.Enlist = false;
sqlConn.ApplicationName = "New application";
var sql = "USE FooDatabase
INSERT INTO dbo.MyTable ..."
using (SqlConnection conn2 = new SqlConnection(sqlConn.ConnectionString))
{
conn2.Open();
SqlCommand cmdInsert = conn2.CreateCommand();
cmdInsert.CommandText = sql;
cmdInsert.Parameters.Add("#id", SqlDbType.Int);
cmdInsert.Parameters[0].Value = storeTime;
cmdInsert.Parameters.Add("#messageText", SqlDbType.NVarChar);
cmdInsert.Parameters[1].Value = messageText;
cmdInsert.ExecuteNonQuery();
}

If you use:
Connection.ContextConnection = true;
Then it's going to use the same connection that the CLR Sproc is running in - you need to open a new connection.

Related

ODBC Command Procedure Function Expects Parameters

The stored procedure in SQL works perfectly with the desired result.
ALTER PROCEDURE [dbo].[My_StoredProc]
(#autoidx int,
#r varchar(max) OUTPUT)
--with encryption
AS
BEGIN
-- ... some code here ...
END
Trying to call the stored procedure in VS 2019 using ODBC command but I get getting an error :
Procedure or function My_StoredProc expects parameter #autoidx, which was not supplied.
My code:
string connectionString = String.Format("DSN={0};uid=my_user;pwd=my_pwd", toolStripComboBoxDSN.Text);
// OdbcCommand DbCommand = new OdbcCommand("My_StoredProc", DbConnection);
// DbCommand.CommandType = CommandType.StoredProcedure;
//DbCommand.Parameters.AddWithValue("#autoidx", this.AutoIndex); //this.AutoIndex has value
//DbCommand.Parameters.Add("#r", OdbcType.VarChar,500);
//DbCommand.Parameters["#r"].Direction = ParameterDirection.Output;
//DbConnection.Open();
//DbCommand.ExecuteNonQuery();
//string s = (string)DbCommand.Parameters["#r"].Value;
//Code below is working. With a major change in CommandType
OdbcCommand DbCommand = new OdbcCommand(" EXEC dbo.My_StoredProc #autoidx=? ", DbConnection);
DbCommand.CommandType = CommandType.Text;
DbCommand.Parameters.Add("?", OdbcType.Int).Value = this.AutoIndex;
DbConnection.Open();
var s = DbCommand.ExecuteScalar();
I had one opinion:
1.- Try to change this
DbCommand.Parameters.AddWithValue("#autoidx", this.AutoIndex);
for this:
DbCommand.Parameters.Add("#autoidx", SqlDbType.Int);
DbCommand.Parameters["#autoidx"].Value = this.AutoIndex;
2.- Check you DbConnection, sometimes this contain other database for example db_developer instead of db_production.
Try to use the System.Data.SqlClient native SQL client to SQL Server, and use proper using blocks and proper parameter definition - something like this:
// standard SQL Client connection string
string connectionString = "......";
// define connection and command in proper "usinh" blocks
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand("dbo.My_StoredProc", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
// define parameters properly
cmd.Parameters.Add("#autoidx", SqlDbType.Int);
cmd.Parameters.Add("#r", SqlDbType.VarChar, -1).Direction = ParameterDirection.Output;
// set values to parametesr
cmd.Parameters["#autoidx"] = this.AutoIndex;
// open connection, execute procedure, close connection
conn.Open();
int rowsAffected = cmd.ExecuteNonQuery();
string s = cmd.Parameters["#r"].Value?.ToString();
conn.Close();
}

ADO.NET + T-SQL Invoke scalar function from another DB?

I have an SQL script select [master].[dbo].sp_SearchDuplicatedAddress('','','','') that I can invoke successfully from management studio. The function sp_SearchDuplicatedAddress exists in master database and is accessible by the logged user, no problem here. The problem arises when I try to execute that SQL script from ADO.Net I get the following error:
System.Data.SqlClient.SqlException: 'Cannot find either column "master" or the user-defined function or aggregate "master.dbo.sp_SearchDuplicatedAddress", or the name is ambiguous.'
And here's the code of sample app:
var conn = new SqlConnection();
var cmd = conn.CreateCommand();
cmd.CommandText = "select [master].[dbo].sp_SearchDuplicatedAddress('','','','')";
conn.Open();
var outt = cmd.ExecuteScalar();
}
Any idea how can I execute cross database queries from ADO.Net?
No repro for me. Can you modify the below to reproduce the behavior?
using Microsoft.Data.SqlClient;
using System;
namespace SqlClientTest
{
class Program
{
static void Main(string[] args)
{
var constr = "server=localhost;database=master;integrated security=true";
using (var conn = new SqlConnection(constr))
{
var cmd = conn.CreateCommand();
cmd.CommandText = #"
create or alter function dbo.sp_SearchDuplicatedAddress ( #a varchar(20),#b varchar(20),#c varchar(20),#d varchar(20) )
returns int
as
begin
return 3;
end ";
conn.Open();
cmd.ExecuteNonQuery();
}
constr = "server=localhost;database=tempdb;integrated security=true";
using (var conn = new SqlConnection(constr))
{
var cmd = conn.CreateCommand();
cmd.CommandText = "select [master].[dbo].sp_SearchDuplicatedAddress('','','','')";
conn.Open();
var outt = cmd.ExecuteScalar();
Console.WriteLine(outt);
}
}
}
}

How to connect to Oracle database

I am using below code to connect to an oracle server. I need to particularly use OdbcConnection class because I am doing enhancement of an existing application which currently connects to SQL Server using this method. So I have to use the same method for other DBMS also.
I have tried with the code below:
const string ConnectionString = #"SERVER=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MachineName.Domain.com)(PORT=1521))(CONNECT_DATA=(SERVER = DEDICATED)(SERVICE_NAME=orcl.Domain.com)));uid=system;pwd=user";
using (OdbcConnection connection = new OdbcConnection(ConnectionString))
{
connection.Open();
}
But I get an exception when calling the Open() method as follows:
"ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified"
Any idea of what I am doing wrong here?
Have a look at Oracle connection strings
You connectionString should be like this:
var DB = #"SERVER=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MachineName.Domain.com)(PORT=1521))(CONNECT_DATA=(SERVER = DEDICATED)(SERVICE_NAME=orcl.Domain.com)))";
string ConnectionString = "Driver={Oracle in OraClient11g_home1};Dbq=" + DB + ";uid=system;pwd=user;"
The driver name might be different on your machine. You could also use the ODBC driver from Microsoft (Driver={Microsoft ODBC for Oracle};Server=...), however this is deprecated for ages.
Actually I prefer the DbConnectionStringBuilder (or even OdbcConnectionStringBuilder)
var str = new DbConnectionStringBuilder(true);
str.Add("Driver", "Oracle in OraClient11g_home1");
str.Add("Dbq", #"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MachineName.Domain.com)(PORT=1521))(CONNECT_DATA=(SERVER = DEDICATED)(SERVICE_NAME=orcl.Domain.com)))");
str.Add("Uid", "system");
str.Add("Pwd", "user");
string ConnectionString = str.ConnectionString;
///Your connection string should be like
string str = Data Source=(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = abc )(PORT = 123))(CONNECT_DATA =(SID = xyz)));User Id=abc_xyz;Password=111;Min Pool Size=10;Connection Lifetime=120;Connection Timeout=600;Incr Pool Size=5; Decr Pool Size=2;validate connection=true;
using (OracleConnection con = new OracleConnection(str))
{
using (OracleCommand cmd = con.CreateCommand())
{
if (con.State != ConnectionState.Open)
{
con.Open();
}
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = RoutineConstant.Text;
cmd.BindByName = true;
cmd.ExecuteNonQuery();
if (cmd.Parameters["Value"].Value != null && cmd.Parameters["Value"].Value != DBNull.Value)
{
return Convert.ToDecimal(cmd.Parameters["Value"].Value.ToString());
}
}
}

Issue connecting to the SQL server from Web API

I have the below Controller that gets the account as the input parameter, which connects to the Sql server and will have to call the stored procedure passing account.The stored procedure inserts a new record if the Account is not present and updates it when already in there
string strcon = ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
SqlConnection DbConnection = new SqlConnection(strcon);
DbConnection.Open();
SqlCommand command = new SqlCommand("[dbo].[usp_InserUpadte]", DbConnection);
command.CommandType = CommandType.StoredProcedure;
//create type table
DataTable table = new DataTable();
table.Columns.Add("AccountID", typeof(string));
table.Rows.Add(Account);
SqlParameter parameter = command.Parameters.AddWithValue("#Account_TT", table);
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "Account_TT";
Below is the ConnectionString in web.config
<connectionStrings>
<add name="DBConnection"
providerName="System.Data.SqlClient"
connectionString="Data Source=ServerName;Initial Catalog=DatabaseName;Integrated Security=True;MultipleActiveResultSets=True" />
</connectionStrings>
The stored procedure is like
ALTER PROCEDURE [dbo].[usp_InserUpadte]
#account_TT AS account_TT READONLY
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
MERGE dbo.[Account] prj
USING #account_TT tt
ON prj.AccountID = tt.AccountID
WHEN MATCHED THEN UPDATE SET prj.CounterSeq = prj.CounterSeq+1
WHEN NOT MATCHED THEN INSERT (AccountID,CounterSeq)
VALUES (tt.AccountID, 1);
COMMIT TRANSACTION;
END;
where the table type is created with
CREATE TYPE account_TT AS TABLE
(
AccountID nvarchar(50),
)
GO
When I try to call the API it doesnot throw any exception but neither creates/update any records with the stored procedure. I tried to debug the adding breakpoints. I see the in
DbConnection.Open();
Looks like the connection is not opened. I am able to connect to the SQL server from the same server I am working on though SQL Management Studio. Can anyone please suggest me what could be the issue.
You probably have open connection.Correct way of opening connection
string strcon = ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString;
using(SqlConnection dbConnection = new SqlConnection(strcon))
{
if (dbConnection.State==ConnectionState.Closed)
{
con.Open();
}
}
you didn't connect to data base correct. try this:
using (var connection = new SqlConnection(ConnectionString))
using (var command = new SqlCommand(commandText, connection) { CommandTimeout = 160, CommandType = commandType })
using (var dataAdaptor = new SqlDataAdapter(command))
{
command.Parameters.AddRange(parameters);
connection.Open();
dataAdaptor.Fill(dS);
}

ASP.net form SQL Server update from inputbox

I'm trying to update a table in my SQL Server database with text from an input box on my site;
My table is MemberSite.dbo.Users and the columns within this table are:
ID (Auto incrementing) UserName, Password, ApiKey, VeriF
It's not updating my SQL Server table.
What I want this to do: take the input text and put it in my SQL Server table against the user that is logged in.
Here is some code web.config :
<add name="WRITER"
connectionString="Data Source=localhost;Initial Catalog=MembershipSite;User ID=test;Password=test!"
providerName="System.Data.SqlClient" />
Backend to button click;
protected void Save_Click(object sender, EventArgs e)
{
SqlConnection conn = null;
try
{
string sql = "UPDATE dbo.Users SET ApiKey = #API, VeriF = #verif WHERE UserName = #username";
conn = new SqlConnection(ConfigurationManager.ConnectionStrings["WRITER"].ConnectionString);
SqlCommand cmd = new SqlCommand(sql, conn);
SqlParameter api = new SqlParameter();
api.ParameterName = "#API";
api.Value = APIinput;
cmd.Parameters.Add(api);
SqlParameter verif = new SqlParameter();
verif.ParameterName = "#verif";
verif.Value = Veri;
cmd.Parameters.Add(verif);
SqlParameter UserN = new SqlParameter();
UserN.ParameterName = "#username";
UserN.Value = User.Identity.Name;
cmd.Parameters.Add(UserN);
conn.Open();
}
finally
{
if (conn !=null)
conn.Close();
}
}
Because you never execute your command. Just add:
cmd.ExecuteNonQuery();
after you open your connection.
Also use using statement to dispose your connection and command automatically instead of calling Close or Dispose methods manually. Like;
using(var conn = new SqlConnection(conString))
using(var cmd = conn.CreateCommand())
{
// Set your CommandText
// Add your parameters
// Open your connection
// Execute your command.
}
You have missed cmd.ExecuteNonQuery() after connection.Open(). That's why the values are not updated

Categories