#region License // Copyright 2005-2019 Paul Kohler (https://github.com/paulkohler/minisqlquery). All rights reserved. // This source code is made available under the terms of the GNU Lesser General Public License v3.0 // https://github.com/paulkohler/minisqlquery/blob/master/LICENSE #endregion using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Diagnostics; using System.IO; using System.Windows.Forms; using MiniSqlQuery.Core; using MiniSqlQuery.Core.DbModel; using MiniSqlQuery.Properties; using WeifenLuo.WinFormsUI.Docking; namespace MiniSqlQuery.PlugIns.ViewTable { /// The view table form. public partial class ViewTableForm : DockContent, IViewTable { /// The _batch. private readonly QueryBatch _batch; /// The _services. private readonly IApplicationServices _services; /// The _settings. private readonly IApplicationSettings _settings; /// The _sync lock. private readonly object _syncLock = new object(); /// The _db connection. private DbConnection _dbConnection; /// The _is busy. private bool _isBusy; /// The _meta data service. private IDatabaseSchemaService _metaDataService; /// The _status. private string _status = string.Empty; private int? _rowCount; /// Stores the widths of the columns for this window. private Dictionary _columnSizes = new Dictionary(); /// When tru the grid is being resized on fill, used to avoid overriting column width values. private bool _resizingGrid; /// Initializes a new instance of the class. public ViewTableForm() { InitializeComponent(); } /// Initializes a new instance of the class. /// The services. /// The settings. public ViewTableForm(IApplicationServices services, IApplicationSettings settings) : this() { _services = services; _settings = settings; _batch = new QueryBatch(); TableName = string.Empty; Text = Resources.ViewData; dataGridViewResult.DefaultCellStyle.NullValue = _settings.NullText; dataGridViewResult.DataBindingComplete += DataGridViewResultDataBindingComplete; dataGridViewResult.ColumnWidthChanged += OnColumnWidthChanged; _services.Settings.DatabaseConnectionReset += SettingsDatabaseConnectionReset; _services.SystemMessagePosted += ServicesSystemMessagePosted; } /// Gets a value indicating whether AutoReload. public bool AutoReload { get { return chkAutoReload.Checked; } } /// Gets Batch. public QueryBatch Batch { get { return _batch; } } /// Gets CursorColumn. public int CursorColumn { get { return dataGridViewResult.SelectedCells.Count > 0 ? dataGridViewResult.SelectedCells[0].ColumnIndex : 0; } } /// Gets CursorLine. public int CursorLine { get { return (dataGridViewResult.CurrentRow != null) ? dataGridViewResult.CurrentRow.Index : 0; } } /// Gets CursorOffset. public int CursorOffset { get { return CursorLine; } } /// Gets or sets a value indicating whether IsBusy. public bool IsBusy { get { return _isBusy; } set { lock (_syncLock) { _isBusy = value; } } } /// Gets or sets TableName. public string TableName { get { return cboTableName.Text; } set { cboTableName.Text = value; } } /// Gets or sets Text. public override string Text { get { return base.Text; } set { base.Text = value; TabText = Text; } } /// Gets TotalLines. public int TotalLines { get { return (dataGridViewResult.DataSource != null) ? dataGridViewResult.Rows.Count : 0; } } /// The set status. /// The text. public void SetStatus(string text) { _status = text; UpdateHostStatus(); } public void SetRowCount(int? rows) { _rowCount = rows; UpdateHostStatus(); } /// The set cursor by location. /// The line. /// The column. /// The set cursor by location. public bool SetCursorByLocation(int line, int column) { if (line > 0 && line <= TotalLines) { dataGridViewResult.FirstDisplayedScrollingRowIndex = line; dataGridViewResult.Refresh(); dataGridViewResult.Rows[line].Selected = true; return true; } return false; } /// The set cursor by offset. /// The offset. /// The set cursor by offset. public bool SetCursorByOffset(int offset) { return SetCursorByLocation(offset, 0); } /// The cancel task. public void CancelTask() { // not supported (yet?) } /// The execute task. public void ExecuteTask() { LoadTableData(); } /// The update host status. protected void UpdateHostStatus() { _services.HostWindow.SetStatus(this, _status); _services.HostWindow.SetResultCount(this, _rowCount); } /// The data grid view result data binding complete. /// The sender. /// The e. private void DataGridViewResultDataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { DataTable dt = dataGridViewResult.DataSource as DataTable; if (dt == null) { return; } try { _resizingGrid = true; // create a reasonable default max width for columns int maxColWidth = Math.Max(dataGridViewResult.ClientSize.Width / 2, 100); // Autosize the columns then change the widths, gleaned from SO - http://stackoverflow.com/a/1031871/276563 dataGridViewResult.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells); string nullText = _settings.NullText; string dateTimeFormat = _settings.DateTimeFormat; for (int i = 0; i < dt.Columns.Count; i++) { if (dt.Columns[i].DataType == typeof(DateTime)) { DataGridViewCellStyle dateCellStyle = new DataGridViewCellStyle(); dateCellStyle.NullValue = nullText; dateCellStyle.Format = dateTimeFormat; dataGridViewResult.Columns[i].DefaultCellStyle = dateCellStyle; } // sync column sizes: int columnWidth = dataGridViewResult.Columns[i].Width; dataGridViewResult.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.None; string headerText = dataGridViewResult.Columns[i].HeaderText; if (!string.IsNullOrEmpty(headerText) && _columnSizes.ContainsKey(headerText)) { // use the previous column size in case its been adjusted etc dataGridViewResult.Columns[i].Width = _columnSizes[headerText]; } else { // reset to a the smaller of the 2 sizes, this is mainly for the bigger text columns that throw the size out dataGridViewResult.Columns[i].Width = Math.Min(columnWidth, maxColWidth); if (!string.IsNullOrEmpty(headerText)) { _columnSizes[headerText] = dataGridViewResult.Columns[i].Width; } } } } finally { _resizingGrid = false; } } public void OnColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) { if (_resizingGrid) { return; } string headerText = e.Column.HeaderText; if (!string.IsNullOrEmpty(headerText)) { _columnSizes[headerText] = e.Column.Width; } } /// The get tables and views. private void GetTablesAndViews() { if (_metaDataService == null) { _metaDataService = DatabaseMetaDataService.Create(_services.Settings.ConnectionDefinition.ProviderName); DbModelInstance model = _metaDataService.GetDbObjectModel(_services.Settings.ConnectionDefinition.ConnectionString); List tableNames = new List(); foreach (DbModelTable table in model.Tables) { tableNames.Add(Utility.MakeSqlFriendly(table.FullName)); } foreach (DbModelView view in model.Views) { tableNames.Add(Utility.MakeSqlFriendly(view.FullName)); } cboTableName.Items.AddRange(tableNames.ToArray()); } } /// The load table data. private void LoadTableData() { if (_services.Settings.ConnectionDefinition == null) { _services.HostWindow.DisplaySimpleMessageBox(this, "Please select a connection.", "Select a Connection"); return; } GetTablesAndViews(); DbDataAdapter adapter = null; DbCommand cmd = null; DataTable dt = null; Query query = new Query("SELECT * FROM " + Utility.MakeSqlFriendly(TableName)); if (string.IsNullOrEmpty(TableName)) { Text = Resources.Table_none; return; } try { IsBusy = true; UseWaitCursor = true; dataGridViewResult.DataSource = null; Application.DoEvents(); if (_dbConnection == null) { _dbConnection = _services.Settings.GetOpenConnection(); } query.Result = new DataSet(TableName + " View"); _batch.Clear(); _batch.Add(query); adapter = _services.Settings.ProviderFactory.CreateDataAdapter(); cmd = _dbConnection.CreateCommand(); cmd.CommandText = query.Sql; cmd.CommandType = CommandType.Text; SetCommandTimeout(cmd, _settings.CommandTimeout); adapter.SelectCommand = cmd; adapter.Fill(query.Result); SetStatus(string.Format("Loaded table '{0}'", TableName)); Text = TableName; } catch (DbException dbExp) { // todo: improve! _services.HostWindow.DisplaySimpleMessageBox(this, dbExp.Message, "View Table Error"); SetStatus(dbExp.Message); Text = Resources.ViewDataError; } finally { if (adapter != null) { adapter.Dispose(); } if (cmd != null) { cmd.Dispose(); } UseWaitCursor = false; IsBusy = false; } if (query.Result != null && query.Result.Tables.Count > 0) { dt = query.Result.Tables[0]; Text = Resources.Table_colon + TableName; } dataGridViewResult.DefaultCellStyle.NullValue = _settings.NullText; dataGridViewResult.DataSource = dt; if (dt != null) { SetRowCount(dt.Rows.Count); } } /// /// Sets the command timeout, currently only tested against MSSQL. /// /// The command. /// The command timeout. private void SetCommandTimeout(IDbCommand cmd, int commandTimeout) { if (_services.Settings.ProviderFactory is SqlClientFactory) { if (cmd == null) { throw new ArgumentNullException("cmd"); } cmd.CommandTimeout = commandTimeout; } else { Trace.WriteLine("Command Timeout only supported by SQL Client (so far)"); } } /// The services system message posted. /// The sender. /// The e. private void ServicesSystemMessagePosted(object sender, SystemMessageEventArgs e) { if (e.Message == SystemMessage.TableTruncated) { if (AutoReload && TableName.Equals(e.Data)) { LoadTableData(); } } } /// The settings database connection reset. /// The sender. /// The e. private void SettingsDatabaseConnectionReset(object sender, EventArgs e) { _dbConnection = null; } /// The update status. /// The msg. private void UpdateStatus(string msg) { _services.HostWindow.SetStatus(this, msg); Application.DoEvents(); } /// The view table form_ shown. /// The sender. /// The e. private void ViewTableForm_Shown(object sender, EventArgs e) { LoadTableData(); } /// The data grid view result_ data error. /// The sender. /// The e. private void dataGridViewResult_DataError(object sender, DataGridViewDataErrorEventArgs e) { e.ThrowException = false; } /// The lnk export script_ link clicked. /// The sender. /// The e. private void lnkExportScript_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { DataTable dt = dataGridViewResult.DataSource as DataTable; if (dt != null) { var stringWriter = new StringWriter(); var hostWindow = _services.HostWindow; var dbModelTable = hostWindow.DatabaseInspector.DbSchema.FindTableOrView(TableName); var sqlWriter = _services.Resolve(); sqlWriter.IncludeComments = false; sqlWriter.InsertLineBreaksBetweenColumns = false; sqlWriter.IncludeReadOnlyColumnsInExport = _settings.IncludeReadOnlyColumnsInExport; for (int i = 0; i < dt.Rows.Count; i++) { DataRow dataRow = dt.Rows[i]; foreach (var column in dbModelTable.Columns) { column.DbType.Value = dataRow[dt.Columns[column.Name]]; } sqlWriter.WriteInsert(stringWriter, dbModelTable); if (_settings.EnableQueryBatching) { stringWriter.WriteLine("GO"); } stringWriter.WriteLine(); if (i % 10 == 0) { UpdateStatus(string.Format("Processing {0} of {1} rows", i + 1, dt.Rows.Count)); } } UpdateStatus(string.Format("Processed {0} rows. Opening file...", dt.Rows.Count)); // HACK - need to clean up the values for now as the model is holding the last rows data ;-) // TODO - add a "deep clone" method to the table/columns foreach (var column in dbModelTable.Columns) { column.DbType.Value = null; } // create a new sql editor and push the sql into it IEditor editor = _services.Resolve(); editor.AllText = stringWriter.ToString(); hostWindow.DisplayDockedForm(editor as DockContent); UpdateStatus(null); } } /// The lnk refresh_ link clicked. /// The sender. /// The e. private void lnkRefresh_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { LoadTableData(); } private void ViewTableForm_Activated(object sender, EventArgs e) { UpdateHostStatus(); } } }