using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; namespace Sdaleo.Systems.SQLServer { /// /// Specific actions for SQL Server Databases /// public class SQLServerDatabase { #region Database List /// /// Returns a array of any found Databases on the specified SQL Server intance /// /// SQL Server Credentials /// list of Databases if any found, or null if not /// DBError Object with ErrorOccured, if error Occured public static DBError DatabasesListAll(IConnectDb credential, out string[] DatabaseList) { DatabaseList = null; DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; // Execute the Query string sql = "SELECT [name] FROM sys.databases"; DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.FillDataTable(sql); if (retVal.IsValid) { List DataBaseNames = new List(); foreach (DataRow row in retVal.GetDataTableRetVal().Rows) DataBaseNames.Add(row["name"].ToString()); DatabaseList = DataBaseNames.ToArray(); } return retVal; } /// /// Returns a array of any found Databases that are NOT SQL Default Databases (like master, model, etc); /// /// SQL Server Credentials /// list of Databases if any found, or null if not /// DBError Object with ErrorOccured, if error Occured public static DBError DatabasesListNonDefault(IConnectDb credential, out string[] DatabaseList) { DatabaseList = null; DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; // Look for all Databases that are Non-Default string[] DatabaseListAll = null; DBError retVal = DatabasesListAll(credential, out DatabaseListAll); if(!retVal.ErrorOccured && DatabaseListAll != null) { List DatabaseListNonDefault = new List(); foreach (string DatabaseName in DatabaseListAll) { if(!SQLServerUtilities.IsDefaultDatabaseName(DatabaseName)) DatabaseListNonDefault.Add(DatabaseName); } DatabaseList = DatabaseListNonDefault.ToArray(); } return retVal; } #endregion #region Alter Database /// /// Use this to Raise the specified Database to it's highest Compatibility Level as allowed /// on the specified SQLServer (2005/2008/etc.) /// /// SQL Server Credentials with Database Info /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseRaiseCompatibility(IConnectDb credential) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // How to change compatibility level has changed in sql server 2008 (so we'll try to // use the newer way of doing it if we can. string sql = string.Empty; SQLServerVersion ServerVersion; DBError dberror = SQLServerGeneral.GetSQLServerVersion(credential, out ServerVersion); if (dberror.ErrorOccured) return dberror; switch (ServerVersion) { case SQLServerVersion.SQL_SERVER_2008: sql = String.Format("ALTER DATABASE [{0}] SET COMPATIBILITY_LEVEL = {1}", credential.DBMS.Database, (int)ServerVersion); break; default: sql = String.Format("EXEC sp_dbcmptlevel [{0}], {1}", credential.DBMS.Database, (int)ServerVersion); break; } // Execute the Query and return result DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); return retVal; } /// /// Allows the caller to set the Database into Single User Mode. This is * microsoft recommended * /// for certain type of operations on the Database to make sure nobody else can use the DB. /// Note: If Database is in Use * This will timeout /// /// SQL Server Credentials with Database Info /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseSetToSingleUserMode(IConnectDb credential) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Execute the Query and return result string sql = String.Format("ALTER DATABASE [{0}] SET SINGLE_USER", credential.DBMS.Database); DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); return retVal; } /// /// Allows the caller to set the Database into Multi User Mode. This should be called /// if the caller called DatabaseSetToSingleUserMode() previously /// /// SQL Server Credentials with Database Info /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseSetToMultiUserMode(IConnectDb credential) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Execute the Query and return result string sql = String.Format("ALTER DATABASE [{0}] SET MULTI_USER", credential.DBMS.Database); DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); return retVal; } /// /// Calls DBCC SHRINKDATABASE and DBCC UPDATEUSAGE on the passed in Database /// /// SQL Server Credentials with Database Info /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseShrink(IConnectDb credential) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Execute the Query and return result DB db = DB.Create(credential.DBMS.WithoutDatabase()); string sql = String.Format("DBCC SHRINKDATABASE ([{0}])", credential.DBMS.Database); DBRetVal retVal = db.ExecuteNonQuery(sql); if (!retVal.ErrorOccured) { sql = String.Format("DBCC UPDATEUSAGE ([{0}])", credential.DBMS.Database); retVal = db.ExecuteNonQuery(sql); } return retVal; } /// /// Iterates all tables on the database and enables/disables all trigers for each table /// /// SQL Server Credentials with Database Info /// true to enable, false to disable /// DBError Object with ErrorOccured, if error Occured public static DBError EnableDisableAllTriggers(IConnectDb credential, bool bEnable) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Get All Table Names from the Database string[] TableNames = null; DBError dberror = SQLServerTable.Tables(credential, out TableNames); if (dberror.ErrorOccured) return dberror; // Execute the Query and return result DB db = DB.Create(credential); DBRetVal retVal = new DBRetVal(); foreach (string TableName in TableNames) { // Execute the Query for each Table string sql = String.Format("ALTER TABLE [{0}] {1} TRIGGER ALL", TableName, (bEnable ? "ENABLE" : "DISABLE")); retVal = db.ExecuteNonQuery(sql); if (retVal.ErrorOccured) break; } return retVal; } #endregion #region Databae Create / Exists /// /// Checks to see if we can query the specified server for the Database (Existence check) /// /// SQL Server Credentials with Database Info /// Returns true if the Database Exists, false otherwise /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseExists(IConnectDb credential, out bool bExists) { bExists = false; DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Execute the Query and return result string sql = String.Format("EXEC sp_helpdb @dbname = [{0}]", credential.DBMS.Database); DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); bExists = !retVal.ErrorOccured; return retVal; } /// /// Create a new Database with the specified DatabaseName in the specified DataPath, if specified, or DefaultPath otherwise /// /// SQL Server Credentials with Database Info /// Data Path to Create Database in (not required) /// true to allow Attach Database when error occurs, false otherwise /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseCreate(IConnectDb credential, string DataPath, bool TryAttachDBOnError) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // if DataPath is empty, get default datapath for SQL ServerInstance if (String.IsNullOrEmpty(DataPath)) SQLServerGeneral.SQLServerDefaultPath(credential.DBMS.WithoutDatabase(), out DataPath); // Fill in Database FileName and Path Details string sql = String.Format("CREATE DATABASE [{0}] ", credential.DBMS.Database); if (!String.IsNullOrEmpty(DataPath)) sql += String.Format(@"ON (NAME = [{0}_dat], FILENAME = '{1}\{0}.dat') LOG ON (NAME = [{0}_log], FILENAME = '{1}\{0}_log.ldf')", credential.DBMS.Database, DataPath); // Execute the Command DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); // Upon DB Creation error, see if we could possibly attach the DB bool bErrorOccuredThatCouldMaybeBeResolvedByAttaching = false; if (retVal.ErrorOccured && !retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_ALREADY_EXISTS) && ( retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_SOME_FILE_NAMES_COULD_NOT_BE_CREATED) || retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_CAN_NOT_CREATE_FILE_BECAUSE_IT_ALREADY_EXISTS) || retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_THERE_ALREADY_IS_AN_OBJECT_IN_THE_DB_WITH_THIS_NAME))) { bErrorOccuredThatCouldMaybeBeResolvedByAttaching = true; } // Upon Error, and if we are allowed to attach && datapath is valid, try to attach if (retVal.ErrorOccured && TryAttachDBOnError && bErrorOccuredThatCouldMaybeBeResolvedByAttaching && !String.IsNullOrEmpty(DataPath)) { String[] strFileNames = new String[] { string.Format(@"{1}\{0}.dat", credential.DBMS.Database, DataPath), string.Format(@"{1}\{0}_log.ldf", credential.DBMS.Database, DataPath) }; DBError retValError = DatabaseAttach(credential, strFileNames); return retValError; } return retVal; } #endregion #region Database Attach / Dettach /// /// Attaches a Database to an SQL Server Instance /// /// SQL Server Credentials with Database Info /// Pass in the filenames when attaching Database (required) /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseAttach(IConnectDb credential, string[] strFileNames) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); if ((strFileNames == null || strFileNames.Length == 0)) return DBError.Create("Invalid FileNames Passed In"); // Generate the SQL Query string sql = String.Format("EXEC sp_attach_db @dbname = [{0}]", credential.DBMS.Database); for (int i = 0; i < strFileNames.Length; ++i) sql = sql + String.Format(", @filename{0:d1} = '{1}'", (i + 1), strFileNames[i]); // Execute the Query DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); // If Error Occured try renaming dat files to mdf * legacy code * bool bRetryAttachingErrorOccured = false; if (retVal.ErrorOccured && (retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_THERE_ALREADY_IS_AN_OBJECT_IN_THE_DB_WITH_THIS_NAME) || retVal.GetAllErrorNumbers.Contains(ErrorConst.ERROR_DB_DEVICE_ACTIVATION_FAILED))) { bRetryAttachingErrorOccured = true; } // Retry on Attach if failure occured & // filenames contain .dat files & we have an attach specific error * LEGACY CODE Usuage * if (retVal.ErrorOccured && bRetryAttachingErrorOccured) { bool bRetry = false; List retryArr = new List(); foreach (string strFileName in strFileNames) { if (strFileName.Contains(".dat")) { strFileName.Replace(".dat", ".mdf"); bRetry = true; } retryArr.Add(strFileName); } // Only do an actually retry if .dat files were replaced if (bRetry) return DatabaseAttach(credential, retryArr.ToArray()); } return retVal; } /// /// Dettaches a Database from an SQL Server Instance /// /// SQL Server Credentials with Database Info /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseDettach(IConnectDb credential) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); // Execute the Query and return result string sql = String.Format("sp_detach_db @dbname = [{0}]", credential.DBMS.Database); DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); return retVal; } #endregion #region Database Copy /// /// Copies a specified Database on the specified SQLServer into a new specified Database /// /// SQL Server Credentials with Database Info /// Name of Database To be copied To (required) /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseCopy(IConnectDb credential, string DatabaseNameDest) { DBError dbError = ValidationConsts.IsCredentialValid(credential, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credential.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); dbError = Validation.IsValidDatabaseName(DatabaseNameDest); if (dbError.ErrorOccured) return dbError; string strDefaultPath; DBError dberror = SQLServerGeneral.SQLServerDefaultPath(credential, out strDefaultPath); if (dberror.ErrorOccured) return dberror; // First Step - Backup the source database to a custom Backup file string sql = String.Format(@"BACKUP DATABASE [{0}] TO DISK = '{1}\{0}.DB Backup' WITH INIT, NAME = '{0} Backup', PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33', INIT , FORMAT", credential.DBMS.Database, strDefaultPath); DB db = DB.Create(credential.DBMS.WithoutDatabase()); DBRetVal retVal = db.ExecuteNonQuery(sql); if (retVal.ErrorOccured) return retVal; // Second Step - Retrieve the dat & log Logical Names for the newly backed-up Database string strDataLogical = string.Empty; string strLogLogical = string.Empty; sql = String.Format(@"RESTORE FILELISTONLY FROM DISK = '{0}\{1}.DB Backup' WITH FILE = 1, PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33'", strDefaultPath, credential.DBMS.Database); retVal = db.FillDataTable(sql); if (!retVal.IsValid) return retVal; foreach (DataRow row in retVal.GetDataTableRetVal().Rows) { switch (row["Type"].ToString()) { case "D": strDataLogical = row["LogicalName"].ToString(); break; case "L": strLogLogical = row["LogicalName"].ToString(); break; } } if (String.IsNullOrEmpty(strDataLogical) || String.IsNullOrEmpty(strLogLogical)) return DBError.Create("DataLogical or LogLogical could not be found"); // Third Step - Restore Backup file to the new Database name sql = String.Format(@"RESTORE DATABASE [{0}] FROM DISK = '{1}\{2}.DB Backup' WITH FILE = 1, PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33', MOVE '{3}' TO '{1}\{0}.dat', MOVE '{4}' TO '{1}\{0}_log.ldf', RECOVERY, REPLACE", DatabaseNameDest, strDefaultPath, credential.DBMS.Database, strDataLogical, strLogLogical); retVal = db.ExecuteNonQuery(sql); if (retVal.ErrorOccured) { // try again retVal = db.ExecuteNonQuery(sql); if (!retVal.IsValid) return retVal; } // Fourth Step - Try to Delete the Backup file (No Need to have a potentially massive data file just sitting there // ~SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the // security configuration for this server. A system administrator can enable the use of 'xp_cmdshell' by using sp_configure. // For more information about enabling 'xp_cmdshell', see "Surface Area Configuration" in SQL Server Books Online // sql = String.Format("exec master.dbo.xp_cmdshell 'del /Q /F \"{0}\\{1}.DB Backup\"'", strDefaultPath, DatabaseNameSrc); // db.ExecuteNonQuery(sql); // Legacy Usuage, we are leaving the Backup File behind (*leaking the file* so to speak) return retVal; } /// /// Copies a specified Database on the specified SQLServer into a new specified Database onto a different specified Instance * Must be on Same Server * /// /// SQL Server Credentials with Database Info to be copied from (required) /// SQL Server Credentials with Database Info to be copied to (required) /// DBError Object with ErrorOccured, if error Occured public static DBError DatabaseCopySepInst(IConnectDb credentialSrc, IConnectDb credentialDst) { DBError dbError = ValidationConsts.IsCredentialValid(credentialSrc, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credentialSrc.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); dbError = ValidationConsts.IsCredentialValid(credentialDst, DBSystem.SQL_SERVER); if (dbError.ErrorOccured) return dbError; if (!credentialDst.DBMS.IsDatabaseSetAndNonSystem) return DBError.Create("Invalid Database Passed In via Credential"); if (String.Compare(credentialSrc.DBMS.Server, credentialDst.DBMS.Server, true) != 0) return DBError.Create("Must be on the Same Server"); if (String.Compare(credentialSrc.DBMS.Instance, credentialDst.DBMS.Instance, true) == 0) return DBError.Create("Can not be the same Instance"); // Get Src Default Path *of the DB* string strDefaultPathSrc; DBError dberror = SQLServerGeneral.SQLServerDefaultPath(credentialSrc, out strDefaultPathSrc); if (dberror.ErrorOccured) return dberror; // Get Dest Default Path *of the Server* string strDefaultPathDest; dberror = SQLServerGeneral.SQLServerDefaultPath(credentialDst, out strDefaultPathDest); if (dberror.ErrorOccured) return dberror; // Make sure that both Paths aren't the same *Two instances should never have the same path!* if (strDefaultPathSrc.ToUpper() == strDefaultPathDest.ToUpper()) return DBError.Create(String.Format("The File Path of the Source Database {0} and the Destination Database {1} on Server {1} are identical.", credentialSrc.DBMS.Database, credentialDst.DBMS.Database, credentialDst.DBMS.Server)); // Create Src N' Dest Connections * No Limit on the Command timeout * // Could be very large database that we are trying to copy DB dbSrc = DB.Create(credentialSrc.DBMS.WithoutDatabase().Timeouts.WithCommandTimeout(0)); DB dbDst = DB.Create(credentialDst.DBMS.WithoutDatabase().Timeouts.WithCommandTimeout(0)); // First Step - Backup the source database to a custom DB Backup file to the DESTINATION path string sql = String.Format(@"BACKUP DATABASE [{0}] TO DISK = '{1}\{0}.DB Backup' WITH INIT, NAME = '{0} Backup', PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33', INIT , FORMAT", credentialSrc.DBMS.Database, strDefaultPathSrc); DBRetVal retVal = dbSrc.ExecuteNonQuery(sql); if (retVal.ErrorOccured) return retVal; // Second Step - Retrieve the dat & log Logical Names for the newly backed-up Database File string strDataLogical = string.Empty; string strLogLogical = string.Empty; sql = String.Format(@"RESTORE FILELISTONLY FROM DISK = '{0}\{1}.DB Backup' WITH FILE = 1, PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33'", strDefaultPathSrc, credentialSrc.DBMS.Database); retVal = dbSrc.FillDataTable(sql); if (!retVal.IsValid) return retVal; foreach (DataRow row in retVal.GetDataTableRetVal().Rows) { switch (row["Type"].ToString()) { case "D": strDataLogical = row["LogicalName"].ToString(); break; case "L": strLogLogical = row["LogicalName"].ToString(); break; } } if (String.IsNullOrEmpty(strDataLogical) || String.IsNullOrEmpty(strLogLogical)) return DBError.Create(String.Format("The retrieval of Logical Names for Database '{0}' failed for Server Instance '{1}' in Path '{2}'.", credentialSrc.DBMS.Database, credentialSrc.DataSource, strDefaultPathDest)); // Third Step - Restore Backup file to the new Database name onto the new (Dest) instance sql = String.Format(@"RESTORE DATABASE [{0}] FROM DISK = '{1}\{2}.DB Backup' WITH FILE = 1, PASSWORD = 'dbCopy Backup', MEDIAPASSWORD = 'XaseY 1-33', MOVE '{3}' TO '{5}\{0}.dat', MOVE '{4}' TO '{5}\{0}_log.ldf', RECOVERY, REPLACE", credentialDst.DBMS.Database, strDefaultPathSrc, credentialSrc.DBMS.Database, strDataLogical, strLogLogical, strDefaultPathDest); retVal = dbDst.ExecuteNonQuery(sql); if (!retVal.ErrorOccured) { // try again retVal = dbDst.ExecuteNonQuery(sql); if (!retVal.IsValid) return retVal; } // Fourth Step - Try to Delete the Backup file (No Need to have a potentially massive data file just sitting there // ~SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the // security configuration for this server. A system administrator can enable the use of 'xp_cmdshell' by using sp_configure. // For more information about enabling 'xp_cmdshell', see "Surface Area Configuration" in SQL Server Books Online // sql = String.Format("exec master.dbo.xp_cmdshell 'del /Q /F \"{0}\\{1}.DB Backup\"'", strDefaultPath, DatabaseNameSrc); // db.RunSQLCommandTextExecuteNonQuery(sql); // Legacy Usuage, we are leaving the Backup File behind (*leaking the file* so to speak) return retVal; } #endregion } }