using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.IO; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Data.SqlClient; using System.Data.OleDb; using Volian.Base.Library; [assembly: log4net.Config.XmlConfigurator(Watch = true)] //RoAccessToSql is the program to be used to convert Referenced Object data that is stored in a Microsoft Access database to a sql database. // Input to this program is: // /acc= // /sqldb= // /server= // for example: 'RoAccessSql /acc=D:\Backup2012\APP\RO /sqldb=VEPROMS_APP /server=.\sqlexpress_2012' // // Some things to note: // 1) the database must exist in the server and must have the 'roall' table and stored procedures. It cannot have any data. // 2) no ro images are stored. These will continue to be used as before // 3) log4net is used for errors, it is stored in the same place as the Proms error log // 4) The program can be run standalone, but if run from Proms, the database name, server and romaster.mdb path will be initialized based on the rodb selected // 5) Data in sql is stored in only one table, 'roall'. It as a column for the table from which it came. namespace RoAccessToSql { public partial class RoAccessToSql : Form { private static readonly log4net.ILog _MyLog = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private string _MSAccessPath = null; public string MSAccessPath { get { return _MSAccessPath; } set { _MSAccessPath = value; } } private string _SqlServerName = null; public string SqlServerName { get { return _SqlServerName; } set { _SqlServerName = value; } } private string _SqlDatabaseName = null; public string SqlDatabaseName { get { return _SqlDatabaseName; } set { _SqlDatabaseName = value; } } bool _Initializing = false; public RoAccessToSql(string[] args) { _Initializing = true; InitializeComponent(); foreach (string parm in args) // get access & sql database info from program arguments, or use defaults { if (parm.StartsWith("/acc=")) { MSAccessPath = parm.Substring(5); } else if (parm.StartsWith("/sqldb=")) { SqlDatabaseName = parm.Substring(7) + "_RO"; } else if (parm.StartsWith("/server=")) { SqlServerName = parm.Substring(8); } } if (MSAccessPath != null && !(Directory.Exists(MSAccessPath))) { MessageBox.Show("Invalid MS Access path, cannot convert. Fix before running conversion"); if (MSAccessPath != null) tbAccessPath.Text = MSAccessPath; } else tbAccessPath.Text = MSAccessPath; if (SqlServerName == null) { SqlServerName = @".\sqlserver"; } tbSqlServerName.Text = SqlServerName; if (SqlDatabaseName == null) { SqlDatabaseName = "VEPROMS_RO"; } if (UserInRoEditor()) this.Close(); tbSqlDbName.Text = SqlDatabaseName; _TmpFileForConnectStr = Path.GetTempPath(); _TmpFileForConnectStr = _TmpFileForConnectStr + @"\PromsConnect.txt"; File.Delete(_TmpFileForConnectStr); _Initializing = false; } private bool UserInRoEditor() { FileInfo fiown = null; FileStream fsown = null; // The following code was taken from the roeditor. It uses the 'RoEditor.own' file to assure that // no one is in the roeditor. This file is located in the microsoft access database directory. try { string filename = MSAccessPath + @"\RoEditor.own"; fiown = new FileInfo(filename); try { // Try to delete the owner file. If another process has the file open, this delete will fail. // If the file is closed, it will be deleted, and the user will be placed in the editor. The users // name and the time when this session began will be placed in the owner file. fiown.Delete(); } catch (Exception ex) { fsown = fiown.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); TextReader tr1 = new StreamReader(fsown); string who1 = tr1.ReadToEnd(); MessageBox.Show(ex.Message + "\r\n\r\n" + who1, "RO Editor In Use", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } fsown = fiown.Open(FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite); TextReader tr = new StreamReader(fsown); string who = tr.ReadToEnd(); tr.Close(); if (who.Contains(Environment.UserName.ToUpper())) { return true; } } catch (Exception e) { MessageBox.Show(e.Message, "fileinfo"); } // Open the file just specified. Open it so that no-one else can use it try { fsown = fiown.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.Read); TextWriter tw = new StreamWriter(fsown); tw.WriteLine("Current User: {0}, Date and Time Started: {1}", Environment.UserName.ToUpper(), DateTime.Now.ToString("MM/dd/yyyy @ hh:mm")); tw.Flush(); } catch (IOException ex) { fsown = fiown.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); TextReader tr = new StreamReader(fsown); string who = tr.ReadToEnd(); tr.Close(); if (!who.Contains(Environment.UserName.ToUpper())) MessageBox.Show(who, "Another user is executing the RoEditor"); return true; } return false; } static string _ErrorLogFileName; public static string ErrorLogFileName { get { return _ErrorLogFileName; } set { _ErrorLogFileName = value; } } // make error log file name (uses same logic as proms error log) static bool ChangeLogFileName(string AppenderName, string NewFilename) { log4net.Repository.ILoggerRepository RootRep; RootRep = log4net.LogManager.GetRepository(); foreach (log4net.Appender.IAppender iApp in RootRep.GetAppenders()) { if (iApp.Name.CompareTo(AppenderName) == 0 && iApp is log4net.Appender.FileAppender) { log4net.Appender.FileAppender fApp = (log4net.Appender.FileAppender)iApp; string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); fApp.File = folderPath + @"\VEPROMS\" + (Volian.Base.Library.VlnSettings.GetCommand("prefix", "") + "_").TrimStart("_".ToCharArray()) + NewFilename; ErrorLogFileName = fApp.File; fApp.ActivateOptions(); return true; } } return false; } // Browse to romaster.mdb (Microsoft access database) private void btnBrowse_Click(object sender, EventArgs e) { FolderBrowserDialog fbd = new FolderBrowserDialog(); if (fbd.ShowDialog() == DialogResult.OK) { if (Directory.Exists(fbd.SelectedPath)) tbAccessPath.Text = fbd.SelectedPath; else MessageBox.Show("Path doesn't exist " + tbAccessPath.Text); } } // Convert the data private void btnConvert_Click(object sender, EventArgs e) { if (!Directory.Exists(tbAccessPath.Text)) { MessageBox.Show("Invalid Path to Access Database", "Cannot convert data"); return; } bool canmigrate = TestConnection(false); if (!canmigrate) return; SetLogFileInfo(); this.Cursor = Cursors.WaitCursor; MigrateData(); this.Cursor = Cursors.Default; } // Setup the log file, including the name and adding Session beginning info to the log file private void SetLogFileInfo() { DateTime dtSunday = DateTime.Now.AddDays(-((int)DateTime.Now.DayOfWeek)); ChangeLogFileName("LogFileAppender", SqlDatabaseName + " " + dtSunday.ToString("yyyyMMdd") + " ErrorLog.txt"); _MyLog.InfoFormat("\r\nSession Beginning\r\n<===={0}[SQL:{1:yyMM.ddHH}]====== User: {2}/{3} Started {4} ===============>{5}" , Application.ProductVersion, RevDate, DateTime.Now, Environment.UserDomainName, Environment.UserName, DateTime.Now.ToString("dddd MMMM d, yyyy h:mm:ss tt"), ""); } private string _SqlConnectStr = null; // Start the migration process, this method checks for valid connections and calls MigrateTables to actually migrate the data private void MigrateData() { _SqlConnectStr = string.Format(@"Data Source={0};Initial Catalog={1};Integrated Security=True", SqlServerName, SqlDatabaseName); using (SqlConnection sqlConnection = new SqlConnection(_SqlConnectStr)) { try { sqlConnection.Open(); if (sqlConnection.State == ConnectionState.Open) { // now try to open access db: string strDatabaseConnectionCommand = "Provider=Microsoft.Jet.OLEDB.4.0;Password=\"\";User ID=Admin;Data Source=" + MSAccessPath + "\\ROMaster.mdb;Mode=Share Deny None;Extended Properties=\"\";Jet OLEDB:System database=\"\";Jet OLEDB:Registry Path=\"\";Jet OLEDB:Database Password=\"\";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:New Database Password=\"\";Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False"; using (OleDbConnection accessConnection = new OleDbConnection(strDatabaseConnectionCommand)) { try { accessConnection.Open(); if (accessConnection.State == ConnectionState.Open) { MigrateTables(accessConnection, sqlConnection); if (accessConnection.State == ConnectionState.Open) accessConnection.Close(); } } catch (OleDbException ex) { _MyLog.Error("Access Connection failed.", ex); MessageBox.Show(ex.Message, "Access Connection failed."); } } if (sqlConnection.State == ConnectionState.Open) sqlConnection.Close(); } else { _MyLog.Error("SQL Connection is not open. Check connection string: " + tbSqlDbName.Text); MessageBox.Show("Check connection string: " + tbSqlDbName.Text, "SQL Connection is not open."); } } catch (SqlException ex) { _MyLog.Error("SQL Connection is not open. Check connection string: " + tbSqlDbName.Text); MessageBox.Show(ex.Message, "Unknown error when migrating RO data from MS Access to SQL"); } } } private void MigrateTables(OleDbConnection accessConnection, SqlConnection sqlConnection) { DateTime lastTimeDbs = DateTime.Now; DateTime lastTimeRos = DateTime.Now; List allTables = new List(); List allTablesText = new List(); // set up the Progress bars. The top one is for the tables and the bottom one tracks ros as migrated for the current table. int maxCntRo = GetRecordCountForTable("ROMaster", accessConnection); lastTimeDbs = ProgressBarUpdate(pBarDbs, null, 0, 1, 0, 0, lastTimeDbs, false); lastTimeRos = ProgressBarUpdate(pBarROs, null, 0, maxCntRo, 0, 0, lastTimeRos, false); lastTimeDbs = ProgressBarUpdate(pBarDbs, "ROMaster Table", 0, 5, 0, 1, lastTimeDbs, false); OleDbDataReader reader = null; OleDbCommand command = new OleDbCommand("select * from ROMaster", accessConnection); reader = command.ExecuteReader(); // Do access's romaster table first so that the list of other tables to process is found. While doing this, migrate this table's data. int cntRo = 0; int cntDb = 0; int maxCntDb = 0; while (reader.Read()) // reading from Access Database { string RecID = reader[0].ToString(); int RecType = int.Parse(reader[1].ToString()); string ParentID = reader[2].ToString(); string AccPageID = reader[3].ToString(); string ModDateTime = reader[4].ToString(); string Info = reader[5].ToString(); // Migrate the access record to sql AddRecordToRoAll(sqlConnection, RecID, RecType, ParentID, AccPageID, ModDateTime, Info, "ROMASTER"); cntRo++; lastTimeRos = ProgressBarUpdate(pBarROs, "ROMaster", 0, maxCntRo, cntRo, 1, lastTimeRos, true); // store table names so that the progress bars can be managed. Table names are at end of rectype=1 info's field if (RecType == 1) { int tabIndx = Info.LastIndexOf("\tRO"); string tname = Info.Substring(tabIndx); allTables.Add(tname.Substring(1)); allTablesText.Add(Info.Substring(0, tabIndx)); maxCntDb++; } } lastTimeRos = ProgressBarUpdate(pBarROs, "ROMaster", 0, maxCntRo, maxCntRo, 1, lastTimeRos, true); lastTimeDbs = ProgressBarUpdate(pBarDbs, null, 0, maxCntDb, 0, 0, lastTimeDbs, false); reader.Close(); reader = null; // now migrate all the tables that were found foreach (string tableName in allTables) { lastTimeRos = DateTime.Now; cntRo = 0; maxCntRo = GetRecordCountForTable(tableName, accessConnection); lastTimeRos = DateTime.Now; lastTimeRos = ProgressBarUpdate(pBarROs, null, 0, maxCntRo, 0, 0, lastTimeRos, true); lastTimeDbs = ProgressBarUpdate(pBarDbs, allTablesText[cntDb], 0, maxCntDb, cntDb, 1, lastTimeDbs, false); command = null; command = new OleDbCommand("select * from " + tableName, accessConnection); reader = command.ExecuteReader(); while (reader.Read()) // read all of the records from the current access table { string RecID = reader[0].ToString(); int RecType = int.Parse(reader[1].ToString()); string ParentID = reader[2].ToString(); string AccPageID = reader[3].ToString(); string ModDateTime = reader[4].ToString(); string Info = reader[5].ToString(); // Migrate to sql: AddRecordToRoAll(sqlConnection, RecID, RecType, ParentID, AccPageID, ModDateTime, Info, tableName); cntRo++; lastTimeRos = ProgressBarUpdate(pBarROs, AccPageID, 0, maxCntRo, cntRo, 1, lastTimeRos, true); } cntDb++; reader.Close(); reader = null; lastTimeRos = ProgressBarUpdate(pBarROs, tableName, 0, maxCntRo, maxCntRo, 1, lastTimeRos, false); } lastTimeDbs = ProgressBarUpdate(pBarDbs, allTablesText[maxCntDb - 1], 0, maxCntDb, maxCntDb, 1, lastTimeDbs, false); pBarDbs.Text = "Referenced Object Tables Migration Completed"; pBarROs.Text = "Referenced Objects Migration Completed"; btnConvert.Enabled = false; // add a record to the access database so that proms/roeditor will know that the data has been converted. UpdateAccessToFlagConverted(accessConnection); // if called from proms, the database connection string must be stored in the rodb record. write the connection string // out to a temporary file so the Proms can set this field in its database. WriteSqlPathToTempFile(); } private void UpdateAccessToFlagConverted(OleDbConnection accessConnection) { try { OleDbDataReader reader = null; string dt = string.Format("{0:yyyyMMddHHmmss}", System.DateTime.Now); string strInsert = "INSERT INTO ROMaster (RecID, RecType, ModDateTime, Info) "; // note that '8' for rectype flags database converted: strInsert = strInsert + " VALUES ('09999999', 8, '" + dt + "', '" + _SqlConnectStr + "');"; OleDbCommand command = new OleDbCommand(strInsert, accessConnection); reader = command.ExecuteReader(); if (reader.Read()) return; } catch (Exception e) { MessageBox.Show(e.Message, "Error on insert to flag migration completed"); } } private string _TmpFileForConnectStr = null; private void WriteSqlPathToTempFile() { System.IO.File.WriteAllText(_TmpFileForConnectStr, _SqlConnectStr); } // the following is used to update the progress bars, it finds how many records are in a table. private int GetRecordCountForTable(string tableName, OleDbConnection accessConnection) { OleDbDataReader reader = null; int retcnt = 100; try { OleDbCommand command = new OleDbCommand("SELECT COUNT (RecID) as cnt FROM " + tableName, accessConnection); reader = command.ExecuteReader(); if (reader.Read()) { retcnt = reader.GetInt32(0); } reader.Close(); command.Dispose(); } catch { } return retcnt; } DateTime ProgressBarUpdate(DevComponents.DotNetBar.Controls.ProgressBarX pbi, string msg, int min, int max, int progress, int flag, DateTime lastUpdateDisplay, bool doTimeCheck) { if (flag == 0) // setup pbi.Maximum = max; else { if (doTimeCheck && lastUpdateDisplay != null && DateTime.Now.Subtract(lastUpdateDisplay).TotalSeconds < 0.25f) return lastUpdateDisplay; if (progress == pbi.Maximum) pbi.Text = msg.Trim(); pbi.Value = progress; pbi.Text = string.Format("Processing {0} ({1} of {2})", msg.Trim(), progress + 1, pbi.Maximum); } pbi.Refresh(); return DateTime.Now; } // Using the data as defined by the arguments, save the data to sql. private void AddRecordToRoAll(SqlConnection sqlConnection, string RecID, int RecType, string ParentID, string AccPageID, string ModDateTime, string Info, string tableName) { try { SqlCommand command = new SqlCommand(); command.Connection = sqlConnection; command.CommandType = CommandType.StoredProcedure; command.CommandText = "insertAllRectypes"; command.Parameters.AddWithValue("@RecType", RecType); if (AccPageID == null || AccPageID == "") command.Parameters.AddWithValue("@AccPageID", string.Empty); else command.Parameters.AddWithValue("@AccPageID", AccPageID); command.Parameters.AddWithValue("@ROTable", tableName); command.Parameters.AddWithValue("@RecID", RecID); command.Parameters.AddWithValue("@ParentID", ParentID); command.Parameters.AddWithValue("@Info", Info); command.Parameters.AddWithValue("@ModDateTime", ModDateTime); using (SqlDataReader reader = command.ExecuteReader()) { bool success = true; } command = null; } catch (Exception ex) { string msg = string.Format("Error when adding a record to the sql database for table {0} recid {1}", tableName, RecID); _MyLog.Error(msg, ex); } } private void btnExit_Click(object sender, EventArgs e) { this.Close(); } // the user selected the button to test the sql connection string private void btnTestConnect_Click(object sender, EventArgs e) { TestConnection(true); } // the RevDate & RevDescription are found in a stored procedure, vesp_GetSQLCodeRevision, in the sql database. These get updated each // time a revision is made to the sql database using ROFixes.sql script. private static DateTime _RevDate = DateTime.MinValue; public static DateTime RevDate { get { return _RevDate; } set { _RevDate = value; } } private static string _RevDescription = "Unknown"; public static string RevDescription { get { return _RevDescription; } set { _RevDescription = value; } } // This is called to test the sql connection, either from the button when pressed by user, or from the convert button, before // the conversion is done. See below for what is checked private bool TestConnection(bool notifyUser) { // The following conditions must be true in order to migrate the ro data to sql. Only the first condition can be // tested in this executable since the roall database is interfaced to by the roeditor & the program that migrates the data. // The migration program will make the 2-4 tests, put up a message box if it cannot migrate & will send a failure back // to this program, PROMS. // 1) (done in proms to enable the migrate button) the rodb record's connection string must be 'cstring' (it is the connection string if data was migrated) // 2) the roall database must exist when using the rodb record's connection string and this database must have the stored procedures // 3) roall must be empty // 4) the database must be the correct version bool success = false; bool dbsuc = false; try { string conStr = string.Format(@"Data Source={0};Initial Catalog={1};Integrated Security=True", SqlServerName, SqlDatabaseName); using (SqlConnection connection = new SqlConnection(conStr)) { try { connection.Open(); if (connection.State == ConnectionState.Open) { // now see if there is an roall table SqlCommand command = new SqlCommand(); command.Connection = connection; command.CommandType = CommandType.Text; command.CommandText = "(SELECT count(*) FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ROALL]') AND type in (N'U'))"; using (SqlDataReader reader = command.ExecuteReader()) { if (reader.Read()) { int count = reader.GetInt32(0); if (count < 1) { MessageBox.Show("The database exists but does not have the required 'ROALL' table. Contact your DBA", "Invalid database"); dbsuc = false; } else dbsuc = true; } } success = dbsuc; if (success) { // now see if there are stored procedures: command = null; // reset command command = new SqlCommand(); command.Connection = connection; command.CommandType = CommandType.Text; command.CommandText = "(SELECT count(*) FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[deleteByROTable]') AND OBJECTPROPERTY(id,N'IsProcedure') = 1)"; using (SqlDataReader reader = command.ExecuteReader()) { if (reader.Read()) { int count = reader.GetInt32(0); if (count < 1) { MessageBox.Show("The database exists but does not have the necessary stored procedures. Contact your DBA", "Invalid database"); dbsuc = false; } else dbsuc = true; } } success = dbsuc; } if (success) { // now test that the roall table is empty: command = null; // reset command command = new SqlCommand(); command.Connection = connection; command.CommandType = CommandType.Text; command.CommandText = "SELECT count(*) FROM roall"; using (SqlDataReader reader = command.ExecuteReader()) { if (reader.Read()) { int count = reader.GetInt32(0); if (count > 0) { MessageBox.Show("The database already has data, cannot migrate additional data into it. Contact your DBA", "Invalid contents in database"); dbsuc = false; } else dbsuc = true; } } success = dbsuc; } if (success) { // now test that the database version is ok command = null; // reset command command = new SqlCommand(); command.Connection = connection; command.CommandType = CommandType.StoredProcedure; command.CommandText = "vesp_GetSQLCodeRevision"; using (SqlDataReader reader = command.ExecuteReader()) { if (reader.Read()) { _RevDate = reader.GetDateTime(0); _RevDescription = reader.GetString(1); dbsuc = true; } else dbsuc = false; } success = dbsuc; } } else { if (notifyUser) MessageBox.Show("Connection failed.", "Connection Failed"); } } catch (SqlException ex) { if (notifyUser) MessageBox.Show("Connection failed: " + ex, "Connection Failed"); } } } catch (Exception ex) { if (notifyUser) MessageBox.Show("Connection failed: " + ex, "Connection Failed"); } if (notifyUser && success) MessageBox.Show("You have been successfully connected to the database!", "Connection Succeeded"); return success; } private void tbSqlDbName_Leave(object sender, EventArgs e) { if (_Initializing) return; SqlDatabaseName = tbSqlDbName.Text; } private void tbSqlServerName_Leave(object sender, EventArgs e) { if (_Initializing) return; SqlServerName = tbSqlServerName.Text; } private void tbAccessPath_Leave(object sender, EventArgs e) { if (_Initializing) return; MSAccessPath = tbAccessPath.Text; } } }