Firebird dot net provider doesn't fully execute query? - c#

I'm running a number of SQL command using the dot net provider for Firebird in c#. Specifically I'm changing the database schema, and doing data updates and the like.
As part of my processing I create a new table, run a query to copy data from an old table, and then drop the old table.
When I do this firebird generates and error:
unsuccessful metadata update object is in use
I've done some looking and it seems like the query to copy the data hasn't been "cleared" our or something yet. What I mean is when I check the monitoring tables in Firebird with my c# execution paused I see the query in the MON$STATEMENTS table, as inactive. This is after I've run a commit statement.
My questions:
Is there a way to pause, or wait, or force the query to fully complete before I try to run the next command?
When I run the same sequence of queries in ISQL it works perfectly. Is there something different ISQL does that I can force the dot net Firebird provider to do so it doesn't keep this query open or something?
So for reference the code looks something like this (obviously this is a very simplified):
// create the table
string commandString = "CREATE TABLE ...";
// run the command in a transaction and commit it
mtransaction = Connection.BeginTransaction( IsolationLevel.Serializable );
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
transaction.Commit();
transaction.Dispose();
transaction = null;
// copy the data to the new table from the old
commandString = "INSERT INTO ...";
mtransaction = Connection.BeginTransaction(IsolationLevel.Serializable);
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
transaction.Commit();
transaction.Dispose();
transaction = null;
// drop the old table
commandString = "DROP TABLE ...";
mtransaction = Connection.BeginTransaction(IsolationLevel.Serializable);
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
// this command fails with the exception
// if I pause execution in c# before running this command, and
// use isql to look at the db I see the new table, and the data fully populated
// and I also see the inactive insert command in MON$STATEMENTS
transaction.Commit();
transaction.Dispose();
transaction = null;

I encountered the same problem and verified Beau's (crude) hotfix. Yet, I found a simple solution: Dispose the command after the transaction has been committed! Then reconnection is not neccessary anymore.
mtransaction = Connection.BeginTransaction(IsolationLevel.Serializable);
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
transaction.Commit();
command.Dispose(); // Thus!
transaction.Dispose();
Regards,
AtoN

Okay, Horrible solution to problem:
I actually was able to get this to work by closing and disposing the connection, then re-connecting. This resulted in the "stuck" query being removed somehow, and then I can exectute the table drop command. So squence looks something like this:
// create the table
string commandString = "CREATE TABLE ...";
// run the command in a transaction and commit it
mtransaction = Connection.BeginTransaction( IsolationLevel.Serializable );
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
transaction.Commit();
transaction.Dispose();
transaction = null;
// copy the data to the new table from the old
commandString = "INSERT INTO ...";
mtransaction = Connection.BeginTransaction(IsolationLevel.Serializable);
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
transaction.Commit();
transaction.Dispose();
transaction = null;
// ------------------
// Drop the connection entirely and start a new one
// so the table can be dropped
Connection.Close();
Connection.Dispose();
// build connection string
FbConnectionStringBuilder csb = new FbConnectionStringBuilder();
csb.DataSource ... etc...
// connect
Connection = new FbConnection(connectionString);
Connection.Open();
// Now have new connection that does not have weird
// lingering query, and table can now be dropped
// -----------------
// drop the old table
commandString = "DROP TABLE ...";
mtransaction = Connection.BeginTransaction(IsolationLevel.Serializable);
FbCommand command = new FbCommand(commandString, Connection, mtransaction);
command.ExecuteNonQuery();
// this no longer fails because the connection was complete closed
// and re-opened
transaction.Commit();
transaction.Dispose();
transaction = null;
NOTE: I am very not happy with this solution. It works, but I don't know why. It seems excessive and unnessicary for me to have to do this to drop a table. I would very much appricate any insight anyone may have to offer in this matter!!!

I believe I've ran into a similar thing. My guess is that the root cause seems to be a feature: MVCC. When I mess with schemas, or drop tables only then to recreate, Visual Studio is usually holding it open. I just restart the service, and everything is fine.

Related

Cannot see changes in database without running manually the commit command

I'm trying to save the data I'm inserting into a local Firebird database.
I've tried running an sql command, in the C# code, containg commit; after inserting the data but it doesn't seem to work. The informations are sent but the database isn't saving them.
This is the code I'm using for inserting the data.
FbConnectionStringBuilder csb = new FbConnectionStringBuilder
{
DataSource = "localhost",
Port = 3050,
Database = #"D:\db\DBUTENTI.FDB",
UserID = "SYSDBA",
Password = "masterkey",
ServerType = FbServerType.Default
};
using (FbConnection myConn = new FbConnection(csb.ToString()))
{
if (myConn.State == ConnectionState.Closed)
{
try
{
myConn.Open();
Console.WriteLine("CONNECTION OPENED");
string Id = txt_Id.Text;
string Utente = txt_User.Text;
string Password = txt_Password.Text;
FbCommand cmd = new FbCommand("insert into utenti(id,utente,password)values(#id, #utente, #password)", myConn);
cmd.Parameters.AddWithValue("id", Id);
cmd.Parameters.AddWithValue("utente", Utente);
cmd.Parameters.AddWithValue("password", Password);
cmd.ExecuteNonQuery();
myConn.Close();
Console.WriteLine("CONNECTION CLOSED");
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
}
}
The code runs without any errors/exceptions, but I have to manually commit in the ISQL Tool to see the changes.
Thanks to anyone who is willing to help.
If your workaround (solution) is to manually commit in ISQL, the problem is that you had an active transaction in ISQL (and one is started as soon as you start ISQL). This transaction cannot see changes from transactions committed after the transaction in ISQL started (ie: the changes in your program).
ISQL by default starts transactions with the SNAPSHOT isolation level (which is somewhat equivalent to the SQL standard REPEATABLE READ). If you want ISQL to be able to see changes made by your program, you either need to relax its isolation level to READ COMMITTED, or - as you already found out - you need to explicitly commit (so a new transaction is used).
For example to switch ISQL to use READ COMMITTED, you can use statement:
set transaction read committed record_version;
This will only change the transaction setting for the current session.
For details, see
Firebird 2.5 Language Reference, Transaction Statements
ISQL, Transaction Handling

Cannot insert value into database?(ERROR:The database file is locked)

I created a sqlite database in unity... and tried to connect with this function.
void AddScores(string conn)
{
IDbConnection dbconn;
dbconn = (IDbConnection)new SqliteConnection(conn);
dbconn.Open();
using(IDbCommand dbCmd = dbconn.CreateCommand())
{
// string sqlQuery = "SELECT Id FROM PickAndPlace ";
string sqlQuery= "INSERT INTO PickAndPlace (Id) VALUES (324)";
dbCmd.CommandText = sqlQuery;
using(IDataReader reader = dbCmd.ExecuteReader())
{
while (reader.Read())
{
print(reader.GetInt32(0));
}
dbconn.Close();
reader.Close();
dbCmd.Dispose();
}
}
}
The following code not working if I try insert values...and it is showing this error "The database file is locked:
database is locked" But If I try select this works fine.So where is my mistake?
Sqlite generally accepts a single "connection". Once one application connects to the database, which means just acquiring a write lock on it, no other applications can access it for writes, but can access it for reads. Which is just the behaviour you are seeing. See File Locking And Concurrency Control in SQLite Version 3 for a bunch more details about how this works, the various locking states etc.
But in principle, you can only have a single connection open. So somehow you have more than one. Either you forget to close some connections, or multiple threads or applications are trying to modify it. Or perhaps some error occurred and left the locking files in a bad state.

Do you need to "commit" between multiple oracle sql executions in a c# app?

var oraConnectionString = new OracleConnectionStringBuilder(_connectionString);
using (var oraConnection = new OracleConnection(oraConnectionString.ConnectionString))
{
oraConnection.Open();
try
{
using (var command = new OracleCommand(sqlText, oraConnection))
{
command.Parameters.Clear();
command.BindByName = true;
command.Parameters.Add("node", _currentNode);
command.Parameters.Add("carrierId", CarrierId);
command.Connection.BeginTransaction();
command.ExecuteNonQuery();
command.CommandText = UtilityMethods.LoadTemplate("Resources/CreateCarrier.sql", "Carriers");
command.Parameters.Clear();
command.Parameters.Add("carrierName", CarrierName);
command.Parameters.Add("line1", Line1);
command.Parameters.Add("line2", Line2);
command.Parameters.Add("line3", Line3);
command.Parameters.Add("line4", Line4);
command.ExecuteNonQuery();
command.CommandText = UtilityMethods.LoadTemplate("Resources/GetCarrierId.sql", "Carriers");
CarrierId = Convert.ToString(command.ExecuteScalar());
if (CarrierId == null)
{
//TODO
}
command.Parameters.Clear();
command.CommandText = command.CommandText = UtilityMethods.LoadTemplate("Resources/AddCarrierToNode.sql", "Carriers");
command.Parameters.Add("node", _currentNode);
command.Parameters.Add("rank", Convert.ToInt32(Rank));
command.Parameters.Add("carrierId", Convert.ToInt32(CarrierId));
command.ExecuteNonQuery();
command.Transaction.Commit();
}
}
For some reason CarrierId is returning null. The only thing that I can think of is that the changes from the previous sql commands have not been committed. Can anyone please tell me if you need to commit between each execution?
It depends...
If you use the ODBC connection, you should check the settings for this link. By default, ODBC connection doesn't request autocommit for every operation.
I recommend you to call "Commit" after your operation anytime. You should implement your business logic in program and don't wait the potential configuration issue in ODBC configuration or other environment.

Unable to restore SQL database, exclusive access could not be obtained (single user mode)

I am writing a simple database backup and restore routine for an application. I can backup my database without issues, however when I restore is I am unable to gain exclusive access to my database.
I am trying all the combinations of fixes on SO, putting in single user mode, taking it offline then placing it back only with no success.
I can successfully restore the database within studio manager (express)
This method is the only connection to the SQL server at the time, so I don't understand why I can't perform the restore.
Appreciate the help to point out where the issue may be.
internal void RestoreDatabase(string databaseFile)
{
//get database details
var databaseConfiguration = new DatabaseConfiguration().GetDatabaseConfiguration();
try
{
//construct server connection string
var connection = databaseConfiguration.IsSqlAuthentication
? new ServerConnection(databaseConfiguration.ServerInstance,
databaseConfiguration.SqlUsername,
databaseConfiguration.SqlPassword)
: new ServerConnection(databaseConfiguration.ServerInstance);
//set database to single user and kick everyone off
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET OFFLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET ONLINE",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
sqlconnection.Close();
}
//setup server connection and restore
var server = new Server(connection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
}
catch (Exception ex)
{
//my bad
restoreDatabaseServerError(ex.InnerException.Message, EventArgs.Empty);
}
finally
{
//set database to multi user
using (
var sqlconnection =
new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (
var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Multi_User",
sqlconnection))
{
sqlcommand.ExecuteNonQuery();
sqlcommand.Dispose();
}
sqlconnection.Close();
}
}
}
If anybody is connected to your database, SQL Server cannot drop it, so you have to disconnect existing connections, as you have tried. The problem with single_user is, that it still allows a single user to connect. As you yourself cannot be connected to the database when dropping it you have to get out of there. That opens up that slot for someone else to connect and in turn prevent you from dropping it.
There are a few SQL Server processes that are particularly good at connecting to a database in that split second. Replication is one example. (You shouldn't really drop a database that is published anyway, bat that is another story.)
So what can we do about this? The only 100% safe way is to prevent users from connecting to the database. The only practical way is to switch the database offline and then drop it. However, that has the nasty side effect, that SQL Server does not delete the files of that database, so you have to do that manually.
Another option is to just be fast enough. In your example you bring the database back online before you drop it. That is a fairly resource intensive process that gives an "intruder" lots of time to connect.
The solution I have been using with success looks like this:
ALTER DATABASE MyDb SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE;
USE MyDb;
ALTER DATABASE MyDb SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
USE tempdb;
DROP DATABASE MyDb;
This first sets the database to restricted user and connects to it. Then, while still connected it sets the database to single user. Afterwards the context is switched to tempdb and the drop is executed immediately thereafter. Important here is, to send these commands as one batch to SQL Server to minimize the time between the USE tempdb; and the DROP. Setting the database to restricted user in the beginning catches some rare edge cases, so leave it in even though it does not make sense at first glance.
While this still leaves a theoretical gap for someone else to get in, I have never seen it fail.
After the database is dropped you can run your restore as normal.
Good luck.
Your restore needs to take place on the same connection you set the DB server to single user mode.
In summary for the changes below, I moved the end of the using to below your restore code, and moved the close for the SQL connection to after the restore so it uses the same connection. Also removed set offline and online since they aren't needed. Can't test at the moment, so let me know if it works.
//set database to single user and kick everyone off
using (var sqlconnection = new SqlConnection(new DatabaseConfiguration().MakeConnectionString(databaseConfiguration)))
{
sqlconnection.Open();
using (var sqlcommand = new SqlCommand("ALTER DATABASE " + databaseConfiguration.DatabaseName + " SET Single_User WITH Rollback IMMEDIATE",sqlconnection))
{
sqlcommand.ExecuteNonQuery();
}
//setup server connection and restore
var server = new Server(sqlconnection);
var restore = new Restore();
restore.Database = databaseConfiguration.DatabaseName;
restore.Action = RestoreActionType.Database;
restore.Devices.AddDevice(databaseFile, DeviceType.File);
restore.ReplaceDatabase = true;
restore.Complete += Restore_Complete;
restore.SqlRestore(server);
sqlconnection.Close();
}

Dropping SQL Server database through C#

I am using this code to delete a database through C#
Int32 result = 0;
try
{
String Connectionstring = CCMMUtility.CreateConnectionString(false, txt_DbDataSource.Text, "master", "sa", "happytimes", 1000);
SqlConnection con = new SqlConnection();
con.ConnectionString = Connectionstring;
String sqlCommandText = "DROP DATABASE [" + DbName + "]";
if (con.State == ConnectionState.Closed)
{
con.Open();
SqlConnection.ClearPool(con);
con.ChangeDatabase("master");
SqlCommand sqlCommand = new SqlCommand(sqlCommandText, con);
sqlCommand.ExecuteNonQuery();
}
else
{
con.ChangeDatabase("master");
SqlCommand sqlCommand = new SqlCommand(sqlCommandText, con);
sqlCommand.ExecuteNonQuery();
}
con.Close();
con.Dispose();
result = 1;
}
catch (Exception ex)
{
result = 0;
}
return result;
But I get an error
Database currently in use
Can anyone help?
Try this:
String sqlCommandText = #"
ALTER DATABASE " + DbName + #" SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [" + DbName + "]";
Also make sure that your connection string defaults you to the master database, or any other database other than the one you're dropping!
As an aside, you really don't need all of that stuff around your queries. The ConnectionState will always start off Closed, so you don't need to check for that. Likewise, wrapping your connection in a using block eliminates the need to explicitly close or dispose the connection. All you really need to do is:
String Connectionstring = CCMMUtility.CreateConnectionString(false, txt_DbDataSource.Text, "master", "sa", "happytimes", 1000);
using(SqlConnection con = new SqlConnection(Connectionstring)) {
con.Open();
String sqlCommandText = #"
ALTER DATABASE " + DbName + #" SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [" + DbName + "]";
SqlCommand sqlCommand = new SqlCommand(sqlCommandText, con);
sqlCommand.ExecuteNonQuery();
}
result = 1;
Here is how you do it using Entity Framework version 6
System.Data.Entity.Database.Delete(connectionString);
You should take a look at SMO.
These allow you to manage all aspects of SQL Server from code, including deleting of databases.
The database object has a Drop method to delete database.
Create sqlconnection object for different database other than you want to delete.
sqlCommandText = "DROP DATABASE [DBNAME]";
sqlCommand = new SqlCommand(sqlCommandText , sqlconnection);
sqlCommand.ExecuteNonQuery();
In this case i would recommend that you take the database offline first... that will close all connections and etc... heres an article on how to do it: http://blog.sqlauthority.com/2010/04/24/sql-server-t-sql-script-to-take-database-offline-take-database-online/
Microsoft clearly states that A database can be dropped regardless of its state: offline, read-only, suspect, and so on. on this MSDN article (DROP DATABASE (Transact-SQL))
Connection pooling at a guess, use sql server's activity monitor to make sure though.
Pooling keeps connections to the database alive in a cache, then when you create a new one, if there's one in the cache it hands it back instead of instantiating a new one. They hang around for a default time, (2 minutes I think) if they don't get re-used in that time, then they killed off.
So as a first go connect straight to master, instead of using change database, as I suspect change database will simply swap connections in the pool.
Add a check routine for database in use (use a connection to master to do it!). You can force the database to be dropped anyway by first executing
ALTER DATABASE [MyDatabase] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
again from the connection to master!
However everybody else using the db, will no longer like you at all...
Just don't use DB name in connection string.
"Data Source=.\SQLEXPRESS;Integrated Security=True;"
I was having the same troubles as Anshuman...
By my testing of the code in question of Anshuman there have been very simple error:
there have to be SqlConnection.ClearAllPools(); instead of SqlConnection.ClearPool(con);
Like this trouble of
"cannot drop database because is in use..."
disappears.

Categories

Resources