miniSql

汉化
zgc authored at 11/28/2023 1:00:12 PM zgc committed at 11/28/2023 1:00:26 PM
18.37 KiB
ViewTableForm.cs
#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
{
    /// <summary>The view table form.</summary>
    public partial class ViewTableForm : DockContent, IViewTable
    {
        /// <summary>The _batch.</summary>
        private readonly QueryBatch _batch;

        /// <summary>The _services.</summary>
        private readonly IApplicationServices _services;

        /// <summary>The _settings.</summary>
        private readonly IApplicationSettings _settings;

        /// <summary>The _sync lock.</summary>
        private readonly object _syncLock = new object();

        /// <summary>The _db connection.</summary>
        private DbConnection _dbConnection;

        /// <summary>The _is busy.</summary>
        private bool _isBusy;

        /// <summary>The _meta data service.</summary>
        private IDatabaseSchemaService _metaDataService;

        /// <summary>The _status.</summary>
        private string _status = string.Empty;

        private int? _rowCount;

        /// <summary>Stores the widths of the columns for this window.</summary>
        private Dictionary<string, int> _columnSizes = new Dictionary<string, int>();

        /// <summary>When tru the grid is being resized on fill, used to avoid overriting column width values.</summary>
        private bool _resizingGrid;

        /// <summary>Initializes a new instance of the <see cref="ViewTableForm"/> class.</summary>
        public ViewTableForm()
        {
            InitializeComponent();
        }

        /// <summary>Initializes a new instance of the <see cref="ViewTableForm"/> class.</summary>
        /// <param name="services">The services.</param>
        /// <param name="settings">The settings.</param>
        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;
        }

        /// <summary>Gets a value indicating whether AutoReload.</summary>
        public bool AutoReload
        {
            get { return chkAutoReload.Checked; }
        }

        /// <summary>Gets Batch.</summary>
        public QueryBatch Batch
        {
            get { return _batch; }
        }

        /// <summary>Gets CursorColumn.</summary>
        public int CursorColumn
        {
            get { return dataGridViewResult.SelectedCells.Count > 0 ? dataGridViewResult.SelectedCells[0].ColumnIndex : 0; }
        }

        /// <summary>Gets CursorLine.</summary>
        public int CursorLine
        {
            get { return (dataGridViewResult.CurrentRow != null) ? dataGridViewResult.CurrentRow.Index : 0; }
        }

        /// <summary>Gets CursorOffset.</summary>
        public int CursorOffset
        {
            get { return CursorLine; }
        }

        /// <summary>Gets or sets a value indicating whether IsBusy.</summary>
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                lock (_syncLock)
                {
                    _isBusy = value;
                }
            }
        }

        /// <summary>Gets or sets TableName.</summary>
        public string TableName
        {
            get { return cboTableName.Text; }
            set { cboTableName.Text = value; }
        }

        /// <summary>Gets or sets Text.</summary>
        public override string Text
        {
            get { return base.Text; }
            set
            {
                base.Text = value;
                TabText = Text;
            }
        }

        /// <summary>Gets TotalLines.</summary>
        public int TotalLines
        {
            get { return (dataGridViewResult.DataSource != null) ? dataGridViewResult.Rows.Count : 0; }
        }

        /// <summary>The set status.</summary>
        /// <param name="text">The text.</param>
        public void SetStatus(string text)
        {
            _status = text;
            UpdateHostStatus();
        }

        public void SetRowCount(int? rows)
        {
            _rowCount = rows;
            UpdateHostStatus();
        }

        /// <summary>The set cursor by location.</summary>
        /// <param name="line">The line.</param>
        /// <param name="column">The column.</param>
        /// <returns>The set cursor by location.</returns>
        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;
        }

        /// <summary>The set cursor by offset.</summary>
        /// <param name="offset">The offset.</param>
        /// <returns>The set cursor by offset.</returns>
        public bool SetCursorByOffset(int offset)
        {
            return SetCursorByLocation(offset, 0);
        }

        /// <summary>The cancel task.</summary>
        public void CancelTask()
        {
            // not supported (yet?)
        }

        /// <summary>The execute task.</summary>
        public void ExecuteTask()
        {
            LoadTableData();
        }

        /// <summary>The update host status.</summary>
        protected void UpdateHostStatus()
        {
            _services.HostWindow.SetStatus(this, _status);
            _services.HostWindow.SetResultCount(this, _rowCount);
        }

        /// <summary>The data grid view result data binding complete.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        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;
            }
        }

        /// <summary>The get tables and views.</summary>
        private void GetTablesAndViews()
        {
            if (_metaDataService == null)
            {
                _metaDataService = DatabaseMetaDataService.Create(_services.Settings.ConnectionDefinition.ProviderName);

                DbModelInstance model = _metaDataService.GetDbObjectModel(_services.Settings.ConnectionDefinition.ConnectionString);
                List<string> tableNames = new List<string>();
                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());
            }
        }

        /// <summary>The load table data.</summary>
        private void LoadTableData()
        {
            if (_services.Settings.ConnectionDefinition == null)
            {
                _services.HostWindow.DisplaySimpleMessageBox(this, "请选择一个连接。", "选择连接");
                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("已加载的表 '{0}'", TableName));
                Text = TableName;
            }
            catch (DbException dbExp)
            {
                // todo: improve!
                _services.HostWindow.DisplaySimpleMessageBox(this, dbExp.Message, "查看表错误");
                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);
            }
        }


        /// <summary>
        /// Sets the command timeout, currently only tested against MSSQL.
        /// </summary>
        /// <param name="cmd">The command.</param>
        /// <param name="commandTimeout">The command timeout.</param>
        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("命令超时仅受SQL客户端支持(到目前为止)");
            }
        }

        /// <summary>The services system message posted.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void ServicesSystemMessagePosted(object sender, SystemMessageEventArgs e)
        {
            if (e.Message == SystemMessage.TableTruncated)
            {
                if (AutoReload && TableName.Equals(e.Data))
                {
                    LoadTableData();
                }
            }
        }

        /// <summary>The settings database connection reset.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void SettingsDatabaseConnectionReset(object sender, EventArgs e)
        {
            _dbConnection = null;
        }

        /// <summary>The update status.</summary>
        /// <param name="msg">The msg.</param>
        private void UpdateStatus(string msg)
        {
            _services.HostWindow.SetStatus(this, msg);
            Application.DoEvents();
        }

        /// <summary>The view table form_ shown.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void ViewTableForm_Shown(object sender, EventArgs e)
        {
            LoadTableData();
        }

        /// <summary>The data grid view result_ data error.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void dataGridViewResult_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            e.ThrowException = false;
        }

        /// <summary>The lnk export script_ link clicked.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        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<ISqlWriter>();
                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("正在处理{0} 行中的 {1} 行", i + 1, dt.Rows.Count));
                    }
                }

                UpdateStatus(string.Format("正在处理 {0} 行. 打开文件...", 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<IQueryEditor>();
                editor.AllText = stringWriter.ToString();
                hostWindow.DisplayDockedForm(editor as DockContent);

                UpdateStatus(null);
            }
        }

        /// <summary>The lnk refresh_ link clicked.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void lnkRefresh_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            LoadTableData();
        }

        private void ViewTableForm_Activated(object sender, EventArgs e)
        {
            UpdateHostStatus();
        }
    }
}