Firebird insert...returning asp.net - asp.net

I'm using Firebird 2.5 and asp.net (4.5).
I'm trying to find out how to use insert ... returning, or some equivalent.
Using fbDataReader, it executes the insert OK, but I can't find anyway of accessing a returned value. Using fbDataReader.GetName(0) seems to work ok, returning the variable name in the "returning" clause. This even applies to a max() in a subselect:
..... returning (select max(userid) as newid from users)
returns the text "newid".
I can't find where, or whether, the value is available.
Using a fbDataAdaptor to fill a DataTable, the insert works OK, but data table seems empty.
Does anyone know whether this is possible, and if so, how it's done?
Thanks
EDIT
Code supplied :
strConn = ....
dbConn = New FirebirdSql.Data.FirebirdClient.FbConnection(strConn)
dbConn.Open()
MySQL = "insert into users (Firstname, Lastname) VALUES (#fname,#lname) returning userid"
FbC = New FirebirdSql.Data.FirebirdClient.FbCommand(MySQL, dbConn)
FbC.Parameters.Add("fname", FirebirdSql.Data.FirebirdClient.FbDbType.Text).Value = "Pete"
FbC.Parameters.Add("lname", FirebirdSql.Data.FirebirdClient.FbDbType.Text).Value = "Davis"
FbDataReader = FbC.ExecuteReader()
FbDataReader.Read()
TextBox1.Text = FbDataReader.GetName(0)
'TextBox1.Text = str(FbDataReader.GetInt64())
'TextBox1.Text = FbDataReader.GetString(0)
TextBox1.Text = FbDataReader.GetValue(0)

According to this thread INSERT ... RETURNING ... behaves like output parameters for the Firebird .NET provider. So you will need to add an output parameter.
So something like the code below should work:
FbParameter outParam = new FbParam("userid", FbDbType.Integer)
{
Direction = ParameterDirection.Output
};
FbC.Parameters.Add(outParam);
FbC.ExecuteNonQuery();
int? userId = outParam.Value as int?;

Related

Update always encrypted column from decrypted column

I would like to encrypt an existing database column with always encrypted. My project is a ASP.NET project using code first and database is SQL Server. The database has already data. I created a migration to achieve my goal.
First I tried to alter the column type, using the following.
ALTER TABLE [dbo].[TestDecrypted] ALTER COLUMN [FloatCol] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
I got the following error.
Operand type clash: float is incompatible with float encrypted with (encryption_type = 'RANDOMIZED', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'TestEncrypt')
Then I decided to created another column and migrate the data.
ALTER TABLE [dbo].[TestDecrypted] ADD [FloatCol2] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = [FloatCol]
And I got the same error.
After I looked at this, I noticed that it is possible to insert data like the following
DECLARE #floatCol FLOAT = 1.1
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #floatCol
But if I try to obtain the value from my existing column, it fails.
DECLARE #floatCol FLOAT = (SELECT TOP 1 FloatCol FROM TestDecrypted)
UPDATE [dbo].[TestDecrypted] SET FloatCol2 = #floatCol
The error follows.
Encryption scheme mismatch for columns/variables '#floatCol'. The encryption scheme for the columns/variables is (encryption_type = 'PLAINTEXT') and the expression near line '4' expects it to be (encryption_type = 'RANDOMIZED', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'TestEncrypt').
Does anyone knows how can I achieve my goal?
Update 1
#Nikhil-Vithlani-Microsoft did some interesting suggestions.
Always Encrypted Wizard in SSMS - I would like to achieve my goal with code first migrations, so this idea does not fit.
SqlBulkCopy - It does not work inside migrations, because the new column will only exist after all 'Up' method is run. Therefore we cannot insert data into this column in this way inside this method.
Anyway, his suggestions drove me to another attempt: obtain the decrypted values and update the encrypted column with them.
var values = new Dictionary<Guid, double>();
var connectionString = ConfigurationManager.ConnectionStrings["MainDb"].ConnectionString;
using (var sourceConnection = new SqlConnection(connectionString))
{
var myCommand = new SqlCommand("SELECT * FROM dbo.TestDecrypted", sourceConnection);
sourceConnection.Open();
using (var reader = myCommand.ExecuteReader())
{
while (reader.Read())
{
values.Add((Guid)reader["Id"], (double)reader["FloatCol"]);
}
}
}
Sql("ALTER TABLE [dbo].[TestDecrypted] ADD [FloatCol2] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL");
foreach (var valuePair in values)
{
// The error occurs here
Sql($#"DECLARE #value FLOAT = {valuePair.Value}
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #value WHERE Id = '{valuePair.Key}'");
}
In fact, I did not try to create another column and to migrate the data, as mentioned in an example above. I tried it only on SSMS.
And now I got a different error.
Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
I tried to do it without encrypting the new column, and it worked properly.
Any idea why this error occurs?
You will have to do the always encrypted related migration outside of entity framework. This blog should help
https://blogs.msdn.microsoft.com/sqlsecurity/2015/08/27/using-always-encrypted-with-entity-framework-6/
If you want to encrypt an existing column, you can use Always Encrypted Wizard in SSMS, or use this article that explains how to migrate existing data.
Also, please note that doing bulk inserts through a C# (.NET 4.6.1+ client) app is supported.
You can do this in c# using SqlBulkCopy specifically using SqlBulkCopy.WriteToServer(IDataReader) Method.
Create a new table (encryptedTable) with the same schema as that of your plaintext table (unencryptedTable) but with the encryption turned on for the desired columns.
Do select * from unencryptedTable to load the data in a SqlDataReader then use SqlBulkCopy to load it to the encryptedTable using SqlBulkCopy.WriteToServer(IDataReader) Method
For example,
Plaintext Table
CREATE TABLE [dbo].[Patients](
[PatientId] [int] IDENTITY(1,1),
[SSN] [char](11) NOT NULL)
Encrypted Table
CREATE TABLE [dbo].[Patients](
[PatientId] [int] IDENTITY(1,1),
[SSN] [char](11) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL)
As for why your method does not work,
when you use parameterization for always encrypted, the right hand side (RHS) of the declare statement needs to be a literal. Because the driver will identify the literal and encrypt it for you. So, the following will not work, since RHS is a sql expression and cannot be encrypted by the driver
DECLARE #floatCol FLOAT = (SELECT TOP 1 FloatCol FROM TestDecrypted)
UPDATE [dbo].[TestDecrypted] SET FloatCol2 = #floatCol
Update:
The following code will not work because parameterization for Always Encrypted only applies to SSMS
foreach (var valuePair in values)
{
// The error occurs here
Sql($#"DECLARE #value FLOAT = {valuePair.Value}
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #value WHERE Id = '{valuePair.Key}'");
}
However, if you rewrite your code as follows, that should work
foreach (var valuePair in values)
{
SqlCommand cmd = _sqlconn.CreateCommand();
cmd.CommandText = #"UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #FloatVar WHERE Id = '{valuePair.Key}'");";
SqlParameter paramFloat = cmd.CreateParameter();
paramFloat.ParameterName = #"#FloatVar";
paramFloat.DbType = SqlDbType.Float;
paramFloat.Direction = ParameterDirection.Input;
paramFloat.Value = floatValue;
cmd.Parameters.Add(paramFloat);
cmd.ExecuteNonQuery();
}
Hope that helps, if you have additional question, please leave them in the comments.

Error: String not recognized as a valid boolean on a varchar?

I have a gridview with a checkbox, that when selected inserts the selected items into two databases. The error occurs at a specific insert parameter that is a data type nvarchar. I'm not sure why it's giving me a boolean error. Any help would be greatly appreciated, I can post code if needed.
Okay, so let's get started. First, this has got to change from this:
sqlds1.UpdateCommand = String.Format(
"UPDATE Fees SET Inactive = '1' WHERE Description = '{0}'",
Request("cbusno"))
to this:
sqlds1.UpdateCommand = "UPDATE Fees SET Inactive = '1' WHERE Description = #Desc"
sqlds1.UpdateCommand.Parameters.AddWithValue("#Desc", Request("cbusno"))
Next, this has got to change from this:
sqlds2.UpdateCommand = String.Format(
"UPDATE OLLicenses SET lactive = '1' WHERE cbusno = '{0}'",
Request("cbusno"))
to this:
sqlds2.UpdateCommand = "UPDATE OLLicenses SET lactive = '1' WHERE cbusno = #no")
sqlds2.UpdateCommand.Parameters.AddWithValue("#no", Request("cbusno"))
Next, you'll want to use a different API here:
Sqldatasource1.Clear()
Sqldatasource1.InsertParameters.Add("cbusno", Request("cbusno"))
Sqldatasource1.InsertParameters.Add("licsubcategory", tb5.Text)
Sqldatasource1.InsertParameters.Add("description", tb5.Text)
Sqldatasource1.InsertParameters.Add("nfee", tb2.Text)
Sqldatasource1.InsertParameters.Add("lactive", "0")
Sqldatasource1.InsertParameters.Add("tadded", DateTime.Now.ToString())
Sqldatasource1.InsertParameters.Add("cuserid", Session("Username"))
Sqldatasource1.InsertParameters.Add("myfield", Request("myfield"))
Sqldatasource1.Insert()
This allows the data source to handle the typing for you.
Same as aforementioned with this block:
SqldatasourceFee.InsertParameters.Add("myfield", Request("myfield"))
SqldatasourceFee.InsertParameters.Add("Amount", tb2.Text)
SqldatasourceFee.InsertParameters.Add("CalculatorDescription", (string)clicnotxt.Table(0)(0))
SqldatasourceFee.InsertParameters.Add("Dept", "Business Tax")
SqldatasourceFee.InsertParameters.Add("Module", "Business Tax")
SqldatasourceFee.InsertParameters.Add("Type", tb4.Text)
SqldatasourceFee.InsertParameters.Add("SubType", tb5.Text)
SqldatasourceFee.InsertParameters.Add("Inactive", "0")
SqldatasourceFee.InsertParameters.Add("UserCreated", Session("Username"))
SqldatasourceFee.InsertParameters.Add("Description", Request("cbusno"))
SqldatasourceFee.Insert()
Now, take note to a couple of things. First, notice I casted this value to a string here (string)clicnotxt.Table(0)(0). That value was suspect to me, but I don't believe it's the source of your error. I think the issue here are stuff like this:
SqldatasourceFee.InsertParameters.Add("Inactive", "0")
I'm thinking, but can't be 100% sure, that it should be:
SqldatasourceFee.InsertParameters.Add("Inactive", "False")
Finally, when you build a SqlDataSource inline like this:
Dim sqlds1 As SqlDataSource = New SqlDataSource()
you need to wrap it in a Using because it implements IDisposable:
Using sqlds1 As SqlDataSource = New SqlDataSource()
sqlds1.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings("myconnectionstring").ConnectionString
sqlds1.UpdateCommand = "UPDATE Fees SET Inactive = '1' WHERE Description = #Desc"
sqlds1.UpdateCommand.Parameters.AddWithValue("#Desc", Request("cbusno"))
End Using

SqlCommand slow when executing a query with parameters

DbDataAdapter.Fill() is extremly slow when performing parameters!
I have a query with 2 parameters inside, and when I put those parameters hardcoded in the query it takes 1 second to execute (in a 470k table rows, returning only 20 rows).
I found many posts similars here and I tried all those solutions (set arithabort, option recompile, option optimize for, ...) with no luck.
I just perform a query (sql server 2008) and not a stored procedure, so the query with arithabort is like this:
string strSql = #"set ARITHABORT ON;
select TOP 20 ....
Also I tried to call set arithabort in the same transaction but performing that query first..
I don't know if I'm doing something wrong, but the sensation is the ado.net is performing a very bad execution plan in ado.net when I have defined parameters on it.
As a result of this bad choice, the execution time in SSMS is 1 second (after being cached) but in asp is like 9 seconds!
The query is something like this:
strSQL #="
select *
from Table1
where Name like #name";
And then:
DbProviderFactory factory = DbProviderFactories.GetFactory(mProvider);
DbCommand dbcmd = factory.CreateCommand();
if (CommandTimeout != null)
dbcmd.CommandTimeout = CommandTimeout.Value;
if(this.transaccion != null)
dbcmd.Transaction = this.transaccion;
dbcmd.Connection = dbc;
dbcmd.CommandText = strSQL;
if (parametros != null)
dbcmd.Parameters.AddRange(parametros);
DbDataAdapter dbda = factory.CreateDataAdapter();
dbda.SelectCommand = dbcmd;
DataTable dt = new DataTable();
dbda.Fill(dt);
return dt;
EDIT 14/01/2013 (18:44)
I'm not longer retrieve the connection from DbProviderFactory, insted I'm using directly SqlConnection and SqlCommand. I know DbCommand and DbProvider are a base clase... but I think there is something more in there.. because the performance drasticaly increase like 300%!
It's not the fill method, because I already tried in the code shown before..
Anyway, I don't know the reason why but using a SqlConnection is much faster! Any idea? Maybe isn't making that bad execution plan made before?
SqlCommand objCmd = new SqlCommand(strSQL, sqlConn);
if (CommandTimeout != null)
objCmd.CommandTimeout = CommandTimeout.Value;
if (this.transaccion != null)
objCmd.Transaction = SQLtransaccion;
if (parametros != null)
objCmd.Parameters.AddRange(parametros);
DbDataReader dbReader = objCmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(dbReader);
dbReader.Close();
return dt;
Any help will be greatly appreciated,
Thanks,
I found the solution!
It was parameters!
I was using a wrong type in the the List!
Parametross.Add(bd.MakeParameter("#val", "%" + txtFind.Text + "%",
DbType.String));
DbType.String vs. DbType.AnsiString
Although both DbType.String and DbType.AnsiString deal with character data, these datatypes are processed differently, and using the wrong data type can have a negative effect on the application’s performance. DbType.String identifies the parameter as a 2-byte Unicode value and is sent to the server as such.DbType.AnsiString causes the parameter to be sent as a multibyte character string. To avoid excessive string conversions, use:
DbType.AnsiString for char or varchar columns and parameters.
DbType.String for unichar and univarchar columns and parameters.
Source:
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc20066.0115/html/adonet/adonet49.htm
In my query there is a:
....
where Table.Col1 like #val
But the column type was varchar and I should use DbType.AnsiString, instead of DbType.String
Parametross.Add(bd.MakeParameter("#val", "%" + txtFind.Text + "%",
DbType.AnsiString));
In my huge table I was making a lot of unnecesary casts and this is the reason why the performance drastically fall down!
Hope this will help someone,

Passing an object collection as a parameter into SQL Server stored procedure

I have a general question on whether something can be done - and whether it will be the most efficient way of doing it !
To summarise: can I pass an object collection as a parameter to a stored procedure?
Let's say that I have a SQL Server table called Users [UserID, Forename, Surname]
and another table called Hobbies [HobbyID, UserID, HobbyName, HobbyTypeID]
This set up is to record multiple hobbies against a user.
In my application, I want to update the user record.
Normally - I would update the user table and then in code, loop through each hobby and update the hobbies table record by record.
If I'm updating the user forename and 2 of their hobbies, this would require 3 calls to the database.
(1 call to a stored procedure to update the forename/surname, and 2 calls to a stored procedure to update the 2 hobby records)
My question is:
Can I make just 1 call to the database by passing all the parameters to just 1 stored procedure.
eg.
intUserID = 1
strForename = "Edward"
strSurname = "ScissorHands"
dim objHobbyCollection as New List(Of Hobby)
'Assume that I have 2 hobby objects, each with their hobbyID, UserID, HobbyName & HobbyTypeID
Dim params As SqlParameter()
params = New SqlParameter() {
New SqlParameter("#UserID", intUserID),
New SqlParameter("#Forename", strForename),
New SqlParameter("#Surname", strSurname),
New SqlParameter("#Hobbies", objHobbyCollection)
}
Can I do this ? (and which way would be more efficient?)
What would the Stored Procedure look like ?
ALTER PROCEDURE [dbo].[User_Update]
#UserID INT
,#Forename NVARCHAR(50) = NULL
,#Surname NVARCHAR(50) = NULL
,#Hobbies ??????????????
Assuming SQL Server 2008+, you can do this using a table-valued parameter. First in SQL Server create a table type:
CREATE TYPE dbo.HobbiesTVP AS TABLE
(
HobbyID INT PRIMARY KEY,
HobbyName NVARCHAR(50),
HobbyTypeID INT
);
Then your stored procedure would say:
#Hobbies dbo.HobbiesTVP READONLY
In C# (sorry I don't know vb.net equivalent) it would be as follows (but if you just have one UserID, this doesn't need to be part of the collection, does it?):
// as Steve pointed out, you may need to have your hobbies in a DataTable.
DataTable HobbyDataTable = new DataTable();
HobbyDataTable.Columns.Add(new DataColumn("HobbyID"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyName"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyTypeID"));
// loop through objHobbyCollection and add the values to the DataTable,
// or just populate this DataTable in the first place
using (connObject)
{
SqlCommand cmd = new SqlCommand("dbo.User_Update", connObject);
cmd.CommandType = CommandType.StoredProcedure;
// other params, e.g. #UserID
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Hobbies", HobbyDataTable);
tvparam.SqlDbType = SqlDbType.Structured;
// ...presumably ExecuteNonQuery()
}

Having problems with sqlDataReader

I am using a sqlDataReader to get data and set it to session variables. The problem is it doesn't want to work with expressions. I can reference any other column in the table, but not the expressions. The SQL does work. The code is below. Thanks in advance, Anthony
Using myConnectionCheck As New SqlConnection(myConnectionString)
Dim myCommandCheck As New SqlCommand()
myCommandCheck.Connection = myConnectionCheck
myCommandCheck.CommandText = "SELECT Projects.Pro_Ver, Projects.Pro_Name, Projects.TL_Num, Projects.LP_Num, Projects.Dev_Num, Projects.Val_Num, Projects.Completed, Flow.Initiate_Date, Flow.Requirements, Flow.Req_Date, Flow.Dev_Review, Flow.Dev_Review_Date, Flow.Interface, Flow.Interface_Date, Flow.Approval, Flow.Approval_Date, Flow.Test_Plan, Flow.Test_Plan_Date, Flow.Dev_Start, Flow.Dev_Start_Date, Flow.Val_Start, Flow.Val_Start_Date, Flow.Val_Complete, Flow.Val_Complete_Date, Flow.Stage_Production, Flow.Stage_Production_Date, Flow.MKS, Flow.MKS_Date, Flow.DIET, Flow.DIET_Date, Flow.Closed, Flow.Closed_Date, Flow.Dev_End, Flow.Dev_End_Date, Users_1.Email AS Expr1, Users_2.Email AS Expr2, Users_3.Email AS Expr3, Users_4.Email AS Expr4, Users_4.FNAME, Users_3.FNAME AS Expr5, Users_2.FNAME AS Expr6, Users_1.FNAME AS Expr7 FROM Projects INNER JOIN Users AS Users_1 ON Projects.TL_Num = Users_1.PIN INNER JOIN Users AS Users_2 ON Projects.LP_Num = Users_2.PIN INNER JOIN Users AS Users_3 ON Projects.Dev_Num = Users_3.PIN INNER JOIN Users AS Users_4 ON Projects.Val_Num = Users_4.PIN INNER JOIN Flow ON Projects.id = Flow.Flow_Pro_Num WHERE id = "
myCommandCheck.CommandText += QSid
myConnectionCheck.Open()
myCommandCheck.ExecuteNonQuery()
Dim count As Int16 = myCommandCheck.ExecuteScalar
If count = 1 Then
Dim myDataReader As SqlDataReader
myDataReader = myCommandCheck.ExecuteReader()
While myDataReader.Read()
Session("TL_email") = myDataReader("Expr1").ToString()
Session("PE_email") = myDataReader("Expr2").ToString()
Session("DEV_email") = myDataReader("Expr3").ToString()
Session("VAL_email") = myDataReader("Expr4").ToString()
Session("Project_Name") = myDataReader("Pro_Name").ToString()
End While
myDataReader.Close()
End If
End Using
This may be because column names need to be unique for the SqlDataReader to be able to index them using a string name for the column.
A couple of things:
1) You are executing the query 3 times. You can lose the ExecuteNonQuery and ExecuteScalar calls, and replace the while loop with "if myDataReader.Read() / end if" to get the data values for the first resulting record. If no records are found, no session variables are set, just as in your current code.
2) It looks more like the problem lies in your session management (ie getting values from Session) rather than your sql query, which looks OK to me.
Check:
that you have sessionState enabled in your web.config file,
that you don't reset the Session values anywhere, and
that you ask for the same Session field name when you are trying to send the email. (e.g. are you setting Session("DEV_Email") but asking for Session("DEV Email") (space instead of underscore) ?
Sorry everyone. The code works just fine. The sqlDataReader WILL accept expressions as column names.
The reason I was getting an error saying the value of the from and to parameters cannot be null. There was no data in that column for any of the records in my table.

Resources