SMO Equivalent to SET HADR OFF - c#

In SMO (C#), what is the equivalent to...
ALTER DATABASE db SET HADR OFF;
I've looked in the documentation for both the Database and AvailabilityDatabase classes and nothing is really jumping out at me.
The end goal here is to drop a database that is a member of an Availability Group. Currently, we are doing this by first turning HADR off and then dropping the database on all secondary servers, and then, on the primary server, removing the database from the Availability Group and dropping the database there.

Here is the process I've settled on for using SMO to drop a database that is joined to an AlwaysOn Availability Group...
Server primaryServer = new Server();
AvailabilityDatabase pDb = primaryServer.AvailabilityGroups[agName].AvailabilityDatabases[dbName];
pDb.SuspendDataMovement();
while (!pDb.IsSuspended)
{
Thread.Sleep(1000);
pDb.Refresh();
}
foreach (var secondary in secondaryServers)
{
AvailabilityDatabase sDb = secondary.AvailabilityGroups[agName].AvailabilityDatabases[dbName];
sDb.SuspendDataMovement();
while (!sDb.IsSuspended)
{
Thread.Sleep(1000);
sDb.Refresh();
}
sDb.LeaveAvailabilityGroup(); // this appears to be the equivalent of SET HADR OFF
Database db = secondary.Databases[dbName];
db.UserAccess = DatabaseUserAccess.Single;
secondary.KillAllProcesses(dbName);
db.Drop();
}
pDb.Drop();
Database db = primaryServer.Databases[dbName];
db.UserAccess = DatabaseUserAccess.Single;
primaryServer.KillAllProcesses(dbName);
db.Drop();

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

The best way to detect Oracle connection leak?

A program written in C# Oracle client that proved to have "Connection leak" which it is not closing all database connections and so after some time it can no longer connect to the database as there are too many open connections.
I wrote the following helper function (quite expansive):
private static int tryFindConnCount(){
var connstk = new Stack<Oracle.ManagedDataAccess.Client.OracleConnection>();
try
{
for (var i = 0; i < 10000; ++i)
{
var conn = new Oracle.ManagedDataAccess.Client.OracleConnection(
myDatabaseConnection);
conn.Open();
connstk.Push(conn);
}
}
catch(Exception e)
{
foreach (var conn in connstk)
{
conn.Close();
}
}
return connstk.Count;
}
Here is the code in a test case that uses the above:
var co = tryFindConnCount();
CodeThatMayLeakConnection();
var cn = tryFindConnCount();
Assert.That(cn, Is.EqaulTo(co));
It helped me identify at least one case that have connection leak.
The problem of tryFindConnCount is that it should never be used in production. And I think there should be some way to obtain the same value much cheaper.
How can I do this in the code so I can monitor this value in production?
Trying to find places where connections where not closed is a difficult task.
If you leave the program and forget to close the connection the last sql which was executed is stored in column SQL_ID in v$session (gv$session for RAC). You can search v$session for idle/dead sessions. You can then use v$sql to find the SQL text which may tell you more about what was done last. By this you may get a hint where to search in your code.
select a.sid, a.username, a.program, a.machine, a.sql_id, b.sql_fulltext
from v$session a, v$sql b
where b.sql_id(+) = a.sql_id
and a.username is not null -- filter system processes, maybe filter more stuff
;
You can query Oracle DB on "gv$session" view to get the info that you need. With a query on this view you can cyclically monitor the DB every 10-15 minutes for a count of connections from this program.
Example query below :
select count(*)
from gv$session
where machine = 'XXXXX'
and username = 'YYYYY'
and program = 'ZZZZZ';
You only need values that uniquely identify those connections like for example machine from which the connections originate.
Also the query is very light and doesn't add performance overhead.

Copy database with smo raise error: "user already exists"

I try to copy a database using smo, but I get the error:
"User, group, or role '%' already exists in the current database"
My code:
var conn = GetServerConnection();
var server = new Server(conn);
var sourceDb = server.Databases[sourceDatabase.Name];
var destinationDbName = GetNameForDatabase(dbName);
var destinationDb = new Database(server, destinationDbName);
destinationDb.Create();
var transfer = new Transfer(sourceDb) {
DestinationDatabase = destinationDbName,
DestinationServer = server.Name,
DestinationLoginSecure = true,
CopySchema = true,
CopyAllTables = true,
CopyData = true,
CopyAllUsers = false,
};
transfer.Options.WithDependencies = true;
transfer.Options.ContinueScriptingOnError = true;
transfer.TransferData();
Thanks in advance for any suggestions!
Do you have any mappings from the database server to the source database? Try removing those before attempting the copy.
I was copying the database using the Copy Database wizard in order to create a test database on the same server, and got this error. The problem was that the source database had a user login mapped to that database. Somewhere in the mix of it all, the Copy Database wizard was trying to add a user to the destination database via a straight copy, but also add the same user through the mapping. The trick was to remove the mapping of the source database, then copy the database, then add the mapping back to the source (it was already added at the destination).

How to set owner of existing SQL Server database

I'm trying to set a SQL Server database owner using C# and SMO. If the database does not exist, I can assign its owner. But if I want to set the owner of an existing database, I get an error.
Working code for new database:
Server server = new Server("WINSERVER\\SQLEXPR");
server.ConnectionContext.LoginSecure = true;
Database database = new Database(server, "MyDatabase");
db.Create();
database.SetOwner("SOMEOWNER", true)
database.Refresh();
Not working code for an existing database:
Server server = new Server("WINSERVER\\SQLEXPR");
server.ConnectionContext.LoginSecure = true;
Database database = new Database(server, "MyDatabase");
database.SetOwner("SOMEOWNER", true)
database.Refresh();
Error:
Microsoft.SqlServer.Management.Smo.InvalidSmoOperationException: You cannot execute this operation since the object has not benn created.
Server server = new Server("WINSERVER\\SQLEXPR");
server.ConnectionContext.LoginSecure = true;
// changed line below
Database database = server.Databases["MyDatabase"];
database.SetOwner("SOMEOWNER", true)
database.Refresh();
No need to create a new Database object, just pull it from the database collection on the server already.

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();
}

Categories

Resources