From 53bc240c5f23191352bf47e7171ac00e9ad4c432 Mon Sep 17 00:00:00 2001
From: zhubanghao <58813184@qq.com>
Date: Mon, 10 Aug 2026 09:27:17 +0800
Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=AF=B9=E6=AF=94=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.cr/personal/FavoritesList/List.xml | 6 +
.../SmartSQL/Models/ColumnDiffInfoModel.cs | 33 ++
SmartSQL/SmartSQL/SmartSQL.csproj | 10 +-
.../UserControl/Main/UcMainDbCompare.xaml | 10 +-
.../UserControl/Main/UcMainDbCompare.xaml.cs | 64 ++++
.../SmartSQL/Views/TableFieldDiffWindow.xaml | 289 ++++++++++++++++++
.../Views/TableFieldDiffWindow.xaml.cs | 177 +++++++++++
7 files changed, 587 insertions(+), 2 deletions(-)
create mode 100644 .cr/personal/FavoritesList/List.xml
create mode 100644 SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
create mode 100644 SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
create mode 100644 SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml.cs
diff --git a/.cr/personal/FavoritesList/List.xml b/.cr/personal/FavoritesList/List.xml
new file mode 100644
index 0000000..a60e5ed
--- /dev/null
+++ b/.cr/personal/FavoritesList/List.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs b/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
new file mode 100644
index 0000000..5609670
--- /dev/null
+++ b/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
@@ -0,0 +1,33 @@
+using System.Windows.Media;
+
+namespace SmartSQL.Models
+{
+ public class ColumnDiffInfoModel : NotifyPropertyBase
+ {
+ public string SourceColumnName { get; set; }
+ public string SourceDataType { get; set; }
+ public string SourceLength { get; set; }
+ public string SourceNullable { get; set; }
+ public string SourceIsPrimaryKey { get; set; }
+ public string SourceIsIdentity { get; set; }
+ public string SourceDefaultValue { get; set; }
+ public string SourceComment { get; set; }
+
+ public string TargetColumnName { get; set; }
+ public string TargetDataType { get; set; }
+ public string TargetLength { get; set; }
+ public string TargetNullable { get; set; }
+ public string TargetIsPrimaryKey { get; set; }
+ public string TargetIsIdentity { get; set; }
+ public string TargetDefaultValue { get; set; }
+ public string TargetComment { get; set; }
+
+ public bool IsColumnExistsInSource { get; set; }
+ public bool IsColumnExistsInTarget { get; set; }
+
+ public SolidColorBrush DiffForeground { get; set; }
+ public SolidColorBrush DiffBackground { get; set; }
+
+ public bool IsDifferent { get; set; }
+ }
+}
diff --git a/SmartSQL/SmartSQL/SmartSQL.csproj b/SmartSQL/SmartSQL/SmartSQL.csproj
index fae0126..d04d3aa 100644
--- a/SmartSQL/SmartSQL/SmartSQL.csproj
+++ b/SmartSQL/SmartSQL/SmartSQL.csproj
@@ -1,4 +1,4 @@
-
+
@@ -184,6 +184,7 @@
+
@@ -437,6 +438,9 @@
SettingWindow.xaml
+
+ TableFieldDiffWindow.xaml
+
MSBuild:Compile
Designer
@@ -814,6 +818,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
diff --git a/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml b/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
index 84ca36f..d9c60e4 100644
--- a/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
+++ b/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
@@ -215,7 +215,15 @@
HeadersVisibility="All"
ItemsSource="{Binding DiffInfoData, Mode=TwoWay}"
RowHeaderWidth="60"
- SelectionUnit="FullRow">
+ SelectionUnit="FullRow"
+ MouseDoubleClick="TableGrid_OnMouseDoubleClick">
+
+
+
+
+
@@ -252,5 +263,58 @@ namespace SmartSQL.UserControl
});
#endregion
}
+
+ ///
+ /// 右键菜单-字段对比详情
+ ///
+ private void MenuItemFieldDiff_OnClick(object sender, RoutedEventArgs e)
+ {
+ OpenFieldDiffWindow();
+ }
+
+ ///
+ /// 双击打开字段对比详情
+ ///
+ private void TableGrid_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ OpenFieldDiffWindow();
+ }
+
+ ///
+ /// 打开字段对比详情窗口
+ ///
+ private void OpenFieldDiffWindow()
+ {
+ if (TableGrid.SelectedItem == null)
+ {
+ Oops.Oh("请先选择要对比的表");
+ return;
+ }
+ if (_currentSourceConnect == null || _currentTargetConnect == null)
+ {
+ Oops.Oh("请先执行比较操作");
+ return;
+ }
+
+ var selectedItem = (DiffInfoModel)TableGrid.SelectedItem;
+ string sourceTableName = selectedItem.SourceName;
+ string targetTableName = selectedItem.TargetName;
+
+ if (!selectedItem.SourceIsExists && !selectedItem.TargetIsExists)
+ {
+ Oops.Oh("该表在源和目标数据库中都不存在");
+ return;
+ }
+
+ var window = new TableFieldDiffWindow(
+ _currentSourceConnect,
+ _currentSourceDatabase,
+ sourceTableName,
+ _currentTargetConnect,
+ _currentTargetDatabase,
+ targetTableName);
+ window.Owner = Window.GetWindow(this);
+ window.Show();
+ }
}
}
diff --git a/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
new file mode 100644
index 0000000..d342db7
--- /dev/null
+++ b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml.cs b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml.cs
new file mode 100644
index 0000000..fb564b7
--- /dev/null
+++ b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using SmartSQL.Framework;
+using SmartSQL.Framework.PhysicalDataModel;
+using SmartSQL.Framework.SqliteModel;
+using SmartSQL.Models;
+
+namespace SmartSQL.Views
+{
+ public partial class TableFieldDiffWindow
+ {
+ public List ColumnDiffData { get; set; }
+ public string SourceTable { get; set; }
+ public string TargetTable { get; set; }
+ public string DiffCountText { get; set; }
+
+ private readonly ConnectConfigs _sourceConnect;
+ private readonly DataBase _sourceDatabase;
+ private readonly string _sourceTableName;
+ private readonly ConnectConfigs _targetConnect;
+ private readonly DataBase _targetDatabase;
+ private readonly string _targetTableName;
+
+ public TableFieldDiffWindow(
+ ConnectConfigs sourceConnect,
+ DataBase sourceDatabase,
+ string sourceTableName,
+ ConnectConfigs targetConnect,
+ DataBase targetDatabase,
+ string targetTableName)
+ {
+ InitializeComponent();
+ DataContext = this;
+
+ _sourceConnect = sourceConnect;
+ _sourceDatabase = sourceDatabase;
+ _sourceTableName = sourceTableName;
+ _targetConnect = targetConnect;
+ _targetDatabase = targetDatabase;
+ _targetTableName = targetTableName;
+
+ SourceTable = sourceTableName;
+ TargetTable = targetTableName;
+
+ Loaded += TableFieldDiffWindow_Loaded;
+ }
+
+ private void TableFieldDiffWindow_Loaded(object sender, RoutedEventArgs e)
+ {
+ LoadColumnDiffData();
+ }
+
+ private void LoadColumnDiffData()
+ {
+ LoadingLine.Visibility = Visibility.Visible;
+ ColumnDiffData = new List();
+
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ try
+ {
+ var sourceColumns = GetTableColumns(_sourceConnect, _sourceDatabase, _sourceTableName);
+ var targetColumns = GetTableColumns(_targetConnect, _targetDatabase, _targetTableName);
+
+ var diffList = new List();
+
+ var allColumnNames = sourceColumns.Keys.Union(targetColumns.Keys).ToList();
+
+ foreach (var colName in allColumnNames)
+ {
+ var model = new ColumnDiffInfoModel();
+
+ Column sourceCol = null;
+ Column targetCol = null;
+
+ var sourceExists = sourceColumns.TryGetValue(colName, out sourceCol);
+ var targetExists = targetColumns.TryGetValue(colName, out targetCol);
+
+ model.IsColumnExistsInSource = sourceExists;
+ model.IsColumnExistsInTarget = targetExists;
+
+ if (sourceExists)
+ {
+ model.SourceColumnName = sourceCol.DisplayName;
+ model.SourceDataType = sourceCol.DataType;
+ model.SourceLength = sourceCol.LengthName ?? sourceCol.Length.ToString();
+ model.SourceNullable = sourceCol.IsNullable ? "YES" : "NO";
+ model.SourceIsPrimaryKey = sourceCol.IsPrimaryKey ? "PK" : "";
+ model.SourceIsIdentity = sourceCol.IsIdentity ? "AI" : "";
+ model.SourceDefaultValue = sourceCol.DefaultValue ?? "";
+ model.SourceComment = sourceCol.Comment ?? "";
+ }
+
+ if (targetExists)
+ {
+ model.TargetColumnName = targetCol.DisplayName;
+ model.TargetDataType = targetCol.DataType;
+ model.TargetLength = targetCol.LengthName ?? targetCol.Length.ToString();
+ model.TargetNullable = targetCol.IsNullable ? "YES" : "NO";
+ model.TargetIsPrimaryKey = targetCol.IsPrimaryKey ? "PK" : "";
+ model.TargetIsIdentity = targetCol.IsIdentity ? "AI" : "";
+ model.TargetDefaultValue = targetCol.DefaultValue ?? "";
+ model.TargetComment = targetCol.Comment ?? "";
+ }
+
+ model.IsDifferent = sourceExists && targetExists && HasDifferences(sourceCol, targetCol);
+
+ diffList.Add(model);
+ }
+
+ int diffCount = diffList.Count(x => x.IsDifferent);
+ int onlyInSource = diffList.Count(x => x.IsColumnExistsInSource && !x.IsColumnExistsInTarget);
+ int onlyInTarget = diffList.Count(x => !x.IsColumnExistsInSource && x.IsColumnExistsInTarget);
+
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ ColumnDiffData = diffList;
+ ColumnGrid.ItemsSource = ColumnDiffData;
+ DiffCountText = $"差异字段:{diffCount} 项 | 仅源表:{onlyInSource} 项 | 仅目标表:{onlyInTarget} 项";
+ LoadingLine.Visibility = Visibility.Collapsed;
+ }));
+ }
+ catch (Exception ex)
+ {
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ LoadingLine.Visibility = Visibility.Collapsed;
+ MessageBox.Show($"加载字段对比数据失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }));
+ }
+ });
+ }
+
+ private Columns GetTableColumns(ConnectConfigs connect, DataBase database, string tableName)
+ {
+ var dbInstance = ExporterFactory.CreateInstance(connect.DbType, connect.SelectedDbConnectString(database.DbName), database.DbName);
+ return dbInstance.GetColumnInfoById(tableName);
+ }
+
+ private bool HasDifferences(Column source, Column target)
+ {
+ if (source == null || target == null) return false;
+
+ if (!string.Equals(source.DataType, target.DataType, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (source.Length != target.Length)
+ return true;
+
+ if (source.IsNullable != target.IsNullable)
+ return true;
+
+ if (source.IsPrimaryKey != target.IsPrimaryKey)
+ return true;
+
+ if (source.IsIdentity != target.IsIdentity)
+ return true;
+
+ if (!string.Equals(source.DefaultValue ?? "", target.DefaultValue ?? "", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ if (!string.Equals(source.Comment ?? "", target.Comment ?? "", StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ return false;
+ }
+
+ private void BtnClose_OnClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+ }
+}
--
Gitee
From 4a9d0bdc2ae720439b5e6c2faf2d3c8373f2d76c Mon Sep 17 00:00:00 2001
From: zhubanghao <58813184@qq.com>
Date: Tue, 11 Aug 2026 18:43:43 +0800
Subject: [PATCH 2/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E5=AF=B9=E6=AF=94=E9=A2=9C=E8=89=B2=E5=8C=BA=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Exporter/SqlServerExporter.cs | 30 ++-
.../SmartSQL/Models/ColumnDiffInfoModel.cs | 43 ++++
.../SmartSQL/Views/TableFieldDiffWindow.xaml | 196 ++++++++++++++++--
.../Views/TableFieldDiffWindow.xaml.cs | 57 +++--
4 files changed, 287 insertions(+), 39 deletions(-)
diff --git a/SmartSQL/SmartSQL.Framework/Exporter/SqlServerExporter.cs b/SmartSQL/SmartSQL.Framework/Exporter/SqlServerExporter.cs
index 9446b54..eaa6002 100644
--- a/SmartSQL/SmartSQL.Framework/Exporter/SqlServerExporter.cs
+++ b/SmartSQL/SmartSQL.Framework/Exporter/SqlServerExporter.cs
@@ -143,7 +143,7 @@ namespace SmartSQL.Framework.Exporter
}
///
- /// 根据表名获取表信息
+ /// 根据表名获取表信息(支持 schema.tableName 格式)
///
///
///
@@ -151,6 +151,27 @@ namespace SmartSQL.Framework.Exporter
{
#region MyRegion
Tables tables = new Tables(10);
+ string filterSchemaName = null;
+ string pureTableName = tableName;
+
+ if (!string.IsNullOrEmpty(tableName) && tableName.Contains('.'))
+ {
+ var parts = tableName.Split('.');
+ if (parts.Length == 2)
+ {
+ filterSchemaName = parts[0];
+ pureTableName = parts[1];
+ }
+ else
+ {
+ pureTableName = parts[parts.Length - 1];
+ }
+ }
+
+ string schemaFilter = filterSchemaName != null
+ ? $"AND s.name = '{filterSchemaName}'"
+ : "";
+
string sqlCmd = $@"SELECT sy.[name],
[object_id],
CASE
@@ -167,8 +188,9 @@ namespace SmartSQL.Framework.Exporter
AND minor_id = 0
LEFT JOIN sys.schemas s ON s.schema_id = sy.schema_id
WHERE sy.type = 'U'
- AND sy.name = '{tableName}'
+ AND sy.name = '{pureTableName}'
AND sy.name <> 'sysdiagrams'
+ {schemaFilter}
ORDER BY sy.name ASC;";
SqlDataReader dr = SqlHelper.ExecuteReader(DbConnectString, CommandType.Text, sqlCmd);
while (dr.Read())
@@ -387,8 +409,8 @@ namespace SmartSQL.Framework.Exporter
{
#region MyRegion
var columns = new Columns();
- int objId = 0;
- if (!int.TryParse(objectId, out objId))
+ int parsedId = 0;
+ if (!int.TryParse(objectId, out parsedId))
{
var tableInfo = GetTableByName(objectId);
if (!tableInfo.Values.Any())
diff --git a/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs b/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
index 5609670..00a5b6d 100644
--- a/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
+++ b/SmartSQL/SmartSQL/Models/ColumnDiffInfoModel.cs
@@ -28,6 +28,49 @@ namespace SmartSQL.Models
public SolidColorBrush DiffForeground { get; set; }
public SolidColorBrush DiffBackground { get; set; }
+ ///
+ /// 任意一项属性存在差异
+ ///
public bool IsDifferent { get; set; }
+
+ ///
+ /// 字段名是否有差异
+ ///
+ public bool IsColumnNameDiff { get; set; }
+
+ ///
+ /// 数据类型是否有差异
+ ///
+ public bool IsDataTypeDiff { get; set; }
+
+ ///
+ /// 长度是否有差异
+ ///
+ public bool IsLengthDiff { get; set; }
+
+ ///
+ /// 是否可空是否有差异
+ ///
+ public bool IsNullableDiff { get; set; }
+
+ ///
+ /// 是否主键是否有差异
+ ///
+ public bool IsPrimaryKeyDiff { get; set; }
+
+ ///
+ /// 是否自增是否有差异
+ ///
+ public bool IsIdentityDiff { get; set; }
+
+ ///
+ /// 默认值是否有差异
+ ///
+ public bool IsDefaultValueDiff { get; set; }
+
+ ///
+ /// 备注说明是否有差异
+ ///
+ public bool IsCommentDiff { get; set; }
}
}
diff --git a/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
index d342db7..b389f75 100644
--- a/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
+++ b/SmartSQL/SmartSQL/Views/TableFieldDiffWindow.xaml
@@ -15,14 +15,156 @@
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -141,36 +271,43 @@
@@ -187,36 +324,43 @@
@@ -233,16 +377,20 @@
+
+
+
+
-
+
+ Text="整行黄色:字段属性有差异" />
+ Text="红色整行:仅源表存在" />
+ Text="绿色整行:仅目标表存在" />
+
+
+
+
+
diff --git a/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml b/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
index d9c60e4..1298109 100644
--- a/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
+++ b/SmartSQL/SmartSQL/UserControl/Main/UcMainDbCompare.xaml
@@ -112,6 +112,16 @@
Cursor="Hand"
FontSize="13"
Style="{StaticResource ButtonPrimary}" />
+
+ /// 一键对比所有表字段
+ ///
+ private void BtnBatchFieldDiff_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (ComSourceConnect.SelectedItem == null)
+ {
+ Oops.Oh("请先选择源连接和数据库");
+ return;
+ }
+ if (ComSourceDatabase.SelectedItem == null)
+ {
+ Oops.Oh("请选择源数据库");
+ return;
+ }
+ if (ComTargetConnect.SelectedItem == null)
+ {
+ Oops.Oh("请先选择目标连接和数据库");
+ return;
+ }
+ if (ComTargetDatabase.SelectedItem == null)
+ {
+ Oops.Oh("请选择目标数据库");
+ return;
+ }
+
+ var sourceConnect = (ConnectConfigs)ComSourceConnect.SelectedItem;
+ var sourceDatabase = (DataBase)ComSourceDatabase.SelectedItem;
+ var targetConnect = (ConnectConfigs)ComTargetConnect.SelectedItem;
+ var targetDatabase = (DataBase)ComTargetDatabase.SelectedItem;
+
+ var window = new BatchTableFieldDiffWindow(sourceConnect, sourceDatabase, targetConnect, targetDatabase);
+ window.Owner = Window.GetWindow(this);
+ window.Show();
+ }
}
}
diff --git a/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml
new file mode 100644
index 0000000..d08133a
--- /dev/null
+++ b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml
@@ -0,0 +1,410 @@
+
+
+
+ #1976D2
+ #388E3C
+ #7B1FA2
+ #F57C00
+ #C62828
+ #00838F
+ #4527A0
+ #6D4C41
+ #F8D7DA
+ #D4EDDA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs
new file mode 100644
index 0000000..4bbb6a2
--- /dev/null
+++ b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs
@@ -0,0 +1,754 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using SmartSQL.Framework;
+using SmartSQL.Framework.Exporter;
+using SmartSQL.Framework.PhysicalDataModel;
+using SmartSQL.Framework.SqliteModel;
+using SmartSQL.Models;
+using SqlSugar;
+
+namespace SmartSQL.Views
+{
+ public partial class BatchTableFieldDiffWindow
+ {
+ public List DiffItems { get; set; }
+ public string TotalTablesText { get; set; }
+ public string DiffTablesText { get; set; }
+ public string DiffFieldsText { get; set; }
+ public string OnlyInSourceText { get; set; }
+ public string OnlyInTargetText { get; set; }
+
+ private readonly ConnectConfigs _sourceConnect;
+ private readonly DataBase _sourceDatabase;
+ private readonly ConnectConfigs _targetConnect;
+ private readonly DataBase _targetDatabase;
+
+ public BatchTableFieldDiffWindow(
+ ConnectConfigs sourceConnect,
+ DataBase sourceDatabase,
+ ConnectConfigs targetConnect,
+ DataBase targetDatabase)
+ {
+ InitializeComponent();
+ DataContext = this;
+
+ _sourceConnect = sourceConnect;
+ _sourceDatabase = sourceDatabase;
+ _targetConnect = targetConnect;
+ _targetDatabase = targetDatabase;
+
+ Loaded += BatchTableFieldDiffWindow_Loaded;
+ }
+
+ private void BatchTableFieldDiffWindow_Loaded(object sender, RoutedEventArgs e)
+ {
+ RunBatchCompare();
+ }
+
+ private void RunBatchCompare()
+ {
+ LoadingLine.Visibility = Visibility.Visible;
+ ProgressBar.Visibility = Visibility.Visible;
+ DiffItems = new List();
+
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ try
+ {
+ // 1. 获取源库和目标库的表列表
+ var sourceDbInstance = ExporterFactory.CreateInstance(
+ _sourceConnect.DbType,
+ _sourceConnect.SelectedDbConnectString(_sourceDatabase.DbName),
+ _sourceDatabase.DbName);
+ var sourceModel = sourceDbInstance.Init();
+
+ var targetDbInstance = ExporterFactory.CreateInstance(
+ _targetConnect.DbType,
+ _targetConnect.SelectedDbConnectString(_targetDatabase.DbName),
+ _targetDatabase.DbName);
+ var targetModel = targetDbInstance.Init();
+
+ var sourceTables = sourceModel.Tables;
+ var targetTables = targetModel.Tables;
+
+ var commonTableKeys = sourceTables.Keys.Intersect(targetTables.Keys).ToList();
+ var totalTables = commonTableKeys.Count;
+
+ var allDiffItems = new List();
+ int processedCount = 0;
+
+ // 2. 逐表对比字段
+ foreach (var tableKey in commonTableKeys)
+ {
+ processedCount++;
+ UpdateProgress(processedCount, totalTables);
+
+ var sourceTable = sourceTables[tableKey];
+ var targetTable = targetTables[tableKey];
+
+ var sourceColumns = GetTableColumns(sourceDbInstance, tableKey);
+ var targetColumns = GetTableColumns(targetDbInstance, tableKey);
+
+ var colDiffItems = CompareTableColumns(
+ sourceTable.DisplayName,
+ targetTable.DisplayName,
+ sourceColumns,
+ targetColumns,
+ _targetConnect.DbType);
+
+ allDiffItems.AddRange(colDiffItems);
+ }
+
+ // 3. 汇总统计
+ int diffTableCount = allDiffItems.Select(x => x.TableName).Distinct().Count();
+ int diffFieldCount = allDiffItems.Select(x => $"{x.TableName}.{x.ColumnName}").Distinct().Count();
+ int onlyInSourceCount = allDiffItems.Count(x => x.DiffStatus == "仅源表存在");
+ int onlyInTargetCount = allDiffItems.Count(x => x.DiffStatus == "仅目标表存在");
+
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ DiffItems = allDiffItems;
+ DiffGrid.ItemsSource = DiffItems;
+
+ TotalTablesText = $"对比表数:{totalTables}";
+ DiffTablesText = $"有差异的表:{diffTableCount}";
+ DiffFieldsText = $"差异字段:{diffFieldCount}";
+ OnlyInSourceText = $"仅源表存在:{onlyInSourceCount} 项";
+ OnlyInTargetText = $"仅目标表存在:{onlyInTargetCount} 项";
+
+ ProgressBar.Visibility = Visibility.Collapsed;
+ ProgressText.Text = $"完成!共对比 {totalTables} 张表,{diffTableCount} 张表有差异";
+ LoadingLine.Visibility = Visibility.Collapsed;
+ }));
+ }
+ catch (Exception ex)
+ {
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ LoadingLine.Visibility = Visibility.Collapsed;
+ ProgressBar.Visibility = Visibility.Collapsed;
+ ProgressText.Text = "";
+ MessageBox.Show($"批量对比失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }));
+ }
+ });
+ }
+
+ private void UpdateProgress(int processed, int total)
+ {
+ int percent = total > 0 ? (int)((double)processed / total * 100) : 0;
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ ProgressBar.Value = percent;
+ ProgressText.Text = $"正在对比... {processed}/{total} ({percent}%)";
+ }));
+ }
+
+ private Columns GetTableColumns(IExporter dbInstance, string tableName)
+ {
+ try
+ {
+ return dbInstance.GetColumnInfoById(tableName);
+ }
+ catch
+ {
+ return new Columns();
+ }
+ }
+
+ ///
+ /// 对比两张表的字段,返回扁平化的差异明细列表
+ ///
+ private static List CompareTableColumns(
+ string sourceTableName,
+ string targetTableName,
+ Columns sourceColumns,
+ Columns targetColumns,
+ DbType targetDbType)
+ {
+ var result = new List();
+ var allColumnNames = sourceColumns.Keys.Union(targetColumns.Keys).ToList();
+
+ foreach (var colName in allColumnNames)
+ {
+ Column sourceCol = null;
+ Column targetCol = null;
+ var sourceExists = sourceColumns.TryGetValue(colName, out sourceCol);
+ var targetExists = targetColumns.TryGetValue(colName, out targetCol);
+
+ if (!sourceExists && targetExists)
+ {
+ // 仅目标表存在 → 生成 DROP COLUMN SQL
+ result.Add(new TableFieldDiffItemModel
+ {
+ TableName = sourceTableName,
+ ColumnName = targetCol.DisplayName,
+ DiffType = "-",
+ SourceValue = "-",
+ TargetValue = GetColumnDisplay(targetCol),
+ DiffStatus = "仅目标表存在",
+ IsSourceExists = false,
+ IsTargetExists = true,
+ SqlStatement = GenerateDropColumnSql(targetDbType, targetTableName, targetCol.DisplayName)
+ });
+ }
+ else if (sourceExists && !targetExists)
+ {
+ // 仅源表存在 → 生成 ADD COLUMN SQL
+ result.Add(new TableFieldDiffItemModel
+ {
+ TableName = sourceTableName,
+ ColumnName = sourceCol.DisplayName,
+ DiffType = "-",
+ SourceValue = GetColumnDisplay(sourceCol),
+ TargetValue = "-",
+ DiffStatus = "仅源表存在",
+ IsSourceExists = true,
+ IsTargetExists = false,
+ SqlStatement = GenerateAddColumnSql(targetDbType, targetTableName, sourceCol)
+ });
+ }
+ else if (sourceExists && targetExists)
+ {
+ // 逐属性比较,记录每一项差异
+ AddDiffItemsForColumn(result, sourceTableName, targetTableName, sourceCol, targetCol, targetDbType);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// 逐属性比较两个字段,将每个差异作为单独一行添加到结果中
+ ///
+ private static void AddDiffItemsForColumn(
+ List result,
+ string sourceTableName,
+ string targetTableName,
+ Column source,
+ Column target,
+ DbType targetDbType)
+ {
+ var sourceDisplayName = source.DisplayName ?? "";
+ var targetDisplayName = target.DisplayName ?? "";
+
+ // 字段名
+ if (!string.Equals(sourceDisplayName, targetDisplayName, StringComparison.OrdinalIgnoreCase))
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "字段名", sourceDisplayName, targetDisplayName,
+ GenerateRenameColumnSql(targetDbType, targetTableName, targetDisplayName, sourceDisplayName)));
+ }
+
+ // 数据类型
+ if (!string.Equals(source.DataType ?? "", target.DataType ?? "", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "数据类型", source.DataType ?? "", target.DataType ?? "",
+ GenerateAlterColumnSql(targetDbType, targetTableName, source)));
+ }
+
+ // 长度
+ var sourceLenDisplay = source.LengthName ?? source.Length.ToString();
+ var targetLenDisplay = target.LengthName ?? target.Length.ToString();
+ if (source.Length != target.Length ||
+ !string.Equals(sourceLenDisplay, targetLenDisplay, StringComparison.OrdinalIgnoreCase))
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "长度", sourceLenDisplay, targetLenDisplay,
+ GenerateAlterColumnSql(targetDbType, targetTableName, source)));
+ }
+
+ // 是否可空
+ if (source.IsNullable != target.IsNullable)
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "是否可空",
+ source.IsNullable ? "YES" : "NO",
+ target.IsNullable ? "YES" : "NO",
+ GenerateAlterNullableSql(targetDbType, targetTableName, source)));
+ }
+
+ // 是否主键
+ if (source.IsPrimaryKey != target.IsPrimaryKey)
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "是否主键",
+ source.IsPrimaryKey ? "PK" : "-",
+ target.IsPrimaryKey ? "PK" : "-",
+ GeneratePrimaryKeySql(targetDbType, targetTableName, source.DisplayName, source.IsPrimaryKey)));
+ }
+
+ // 是否自增
+ if (source.IsIdentity != target.IsIdentity)
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "是否自增",
+ source.IsIdentity ? "AI" : "-",
+ target.IsIdentity ? "AI" : "-",
+ GenerateAlterColumnSql(targetDbType, targetTableName, source)));
+ }
+
+ // 默认值
+ if (!string.Equals(source.DefaultValue ?? "", target.DefaultValue ?? "", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "默认值",
+ source.DefaultValue ?? "", target.DefaultValue ?? "",
+ GenerateDefaultValueSql(targetDbType, targetTableName, source)));
+ }
+
+ // 备注
+ if (!string.Equals(source.Comment ?? "", target.Comment ?? "", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Add(CreateDiffItem(sourceTableName, sourceDisplayName, "备注",
+ source.Comment ?? "", target.Comment ?? "",
+ GenerateCommentSql(targetDbType, targetTableName, source)));
+ }
+ }
+
+ private static TableFieldDiffItemModel CreateDiffItem(
+ string tableName, string columnName, string diffType,
+ string sourceValue, string targetValue, string sqlStatement)
+ {
+ return new TableFieldDiffItemModel
+ {
+ TableName = tableName,
+ ColumnName = columnName,
+ DiffType = diffType,
+ SourceValue = sourceValue,
+ TargetValue = targetValue,
+ DiffStatus = "属性有差异",
+ IsSourceExists = true,
+ IsTargetExists = true,
+ SqlStatement = sqlStatement
+ };
+ }
+
+ #region SQL 生成
+
+ ///
+ /// 获取带引号的标识符(MySQL用反引号,SqlServer/PostgreSQL用方括号/双引号)
+ ///
+ private static string QuoteIdent(DbType dbType, string name)
+ {
+ if (string.IsNullOrEmpty(name)) return name;
+ switch (dbType)
+ {
+ case DbType.MySql:
+ return $"`{name}`";
+ case DbType.SqlServer:
+ return $"[{name}]";
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"\"{name}\"";
+ default:
+ return $"`{name}`";
+ }
+ }
+
+ ///
+ /// 获取带引号的表名
+ ///
+ private static string QuoteTable(DbType dbType, string tableName)
+ {
+ if (string.IsNullOrEmpty(tableName)) return tableName;
+ // 如果表名中含 schema,分别处理
+ if (tableName.Contains("."))
+ {
+ var parts = tableName.Split('.');
+ return string.Join(".", parts.Select(p => QuoteIdent(dbType, p)));
+ }
+ return QuoteIdent(dbType, tableName);
+ }
+
+ ///
+ /// 构造列类型字符串(含长度)
+ ///
+ private static string GetColumnTypeDef(Column col)
+ {
+ var dataType = col.DataType ?? "varchar";
+ var len = col.LengthName;
+ if (string.IsNullOrEmpty(len) || len == "0")
+ {
+ // 某些类型不需要长度
+ var noLenTypes = new HashSet(StringComparer.OrdinalIgnoreCase)
+ { "int", "bigint", "smallint", "tinyint", "bit", "datetime", "date",
+ "time", "timestamp", "text", "longtext", "mediumtext", "tinytext",
+ "blob", "longblob", "mediumblob", "tinyblob", "boolean", "bool",
+ "money", "smallmoney", "image", "uniqueidentifier", "ntext" };
+ if (noLenTypes.Contains(dataType))
+ return dataType;
+ return dataType;
+ }
+ return $"{dataType}({len})";
+ }
+
+ ///
+ /// 构造完整列定义(类型 + 可空 + 默认值 + 自增 + 备注)
+ ///
+ private static string GetFullColumnDef(DbType dbType, Column col)
+ {
+ var sb = new System.Text.StringBuilder();
+ sb.Append(GetColumnTypeDef(col));
+
+ // 自增
+ if (col.IsIdentity)
+ {
+ switch (dbType)
+ {
+ case DbType.MySql:
+ sb.Append(" AUTO_INCREMENT");
+ break;
+ case DbType.SqlServer:
+ sb.Append(" IDENTITY(1,1)");
+ break;
+ }
+ }
+
+ // 可空
+ sb.Append(col.IsNullable ? " NULL" : " NOT NULL");
+
+ // 默认值
+ if (!string.IsNullOrEmpty(col.DefaultValue))
+ {
+ sb.Append($" DEFAULT {col.DefaultValue}");
+ }
+
+ // MySQL 备注
+ if (dbType == DbType.MySql && !string.IsNullOrEmpty(col.Comment))
+ {
+ sb.Append($" COMMENT '{EscapeSqlValue(col.Comment)}'");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// 生成 ADD COLUMN 语句(仅源表存在)
+ ///
+ private static string GenerateAddColumnSql(DbType dbType, string tableName, Column col)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, col.DisplayName);
+ var colDef = GetFullColumnDef(dbType, col);
+
+ switch (dbType)
+ {
+ case DbType.SqlServer:
+ return $"ALTER TABLE {table} ADD {column} {colDef};";
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} ADD COLUMN {column} {colDef};";
+ default: // MySql
+ return $"ALTER TABLE {table} ADD COLUMN {column} {colDef};";
+ }
+ }
+
+ ///
+ /// 生成 DROP COLUMN 语句(仅目标表存在)
+ ///
+ private static string GenerateDropColumnSql(DbType dbType, string tableName, string columnName)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, columnName);
+ return $"ALTER TABLE {table} DROP COLUMN {column};";
+ }
+
+ ///
+ /// 生成 RENAME COLUMN 语句(字段名差异)
+ ///
+ private static string GenerateRenameColumnSql(DbType dbType, string tableName, string oldName, string newName)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var oldCol = QuoteIdent(dbType, oldName);
+ var newCol = QuoteIdent(dbType, newName);
+
+ switch (dbType)
+ {
+ case DbType.MySql:
+ return $"ALTER TABLE {table} RENAME COLUMN {oldCol} TO {newCol};";
+ case DbType.SqlServer:
+ return $"EXEC sp_rename '{tableName}.{oldName}', '{newName}', 'COLUMN';";
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} RENAME COLUMN {oldCol} TO {newCol};";
+ default:
+ return $"ALTER TABLE {table} RENAME COLUMN {oldCol} TO {newCol};";
+ }
+ }
+
+ ///
+ /// 生成 ALTER COLUMN / MODIFY COLUMN 语句(数据类型、长度、自增差异)
+ /// 以源表为基准,将目标表列修改为与源表一致
+ ///
+ private static string GenerateAlterColumnSql(DbType dbType, string tableName, Column sourceCol)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, sourceCol.DisplayName);
+ var colDef = GetFullColumnDef(dbType, sourceCol);
+
+ switch (dbType)
+ {
+ case DbType.SqlServer:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} {colDef};";
+ case DbType.PostgreSQL:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} TYPE {GetColumnTypeDef(sourceCol)};";
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} MODIFY ({column} {colDef});";
+ default: // MySql
+ return $"ALTER TABLE {table} MODIFY COLUMN {column} {colDef};";
+ }
+ }
+
+ ///
+ /// 生成修改可空性语句
+ ///
+ private static string GenerateAlterNullableSql(DbType dbType, string tableName, Column sourceCol)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, sourceCol.DisplayName);
+
+ if (sourceCol.IsNullable)
+ {
+ // 源表可空 → 目标表也改为可空
+ switch (dbType)
+ {
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} DROP NOT NULL;";
+ default:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} {GetColumnTypeDef(sourceCol)} NULL;";
+ }
+ }
+ else
+ {
+ // 源表不可空 → 目标表改为不可空
+ switch (dbType)
+ {
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} SET NOT NULL;";
+ default:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} {GetColumnTypeDef(sourceCol)} NOT NULL;";
+ }
+ }
+ }
+
+ ///
+ /// 生成主键变更语句
+ ///
+ private static string GeneratePrimaryKeySql(DbType dbType, string tableName, string columnName, bool isSourcePK)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, columnName);
+ var constraintName = $"PK_{tableName.Replace(".", "_").Replace("[", "").Replace("]", "")}_{columnName}";
+
+ if (isSourcePK)
+ {
+ // 源表有主键 → 目标表添加主键
+ return $"ALTER TABLE {table} ADD CONSTRAINT {QuoteIdent(dbType, constraintName)} PRIMARY KEY ({column});";
+ }
+ else
+ {
+ // 源表无主键 → 目标表删除主键
+ switch (dbType)
+ {
+ case DbType.MySql:
+ return $"ALTER TABLE {table} DROP PRIMARY KEY;";
+ default:
+ return $"ALTER TABLE {table} DROP CONSTRAINT {QuoteIdent(dbType, constraintName)};";
+ }
+ }
+ }
+
+ ///
+ /// 生成默认值变更语句
+ ///
+ private static string GenerateDefaultValueSql(DbType dbType, string tableName, Column sourceCol)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, sourceCol.DisplayName);
+
+ if (string.IsNullOrEmpty(sourceCol.DefaultValue))
+ {
+ // 源表无默认值 → 目标表删除默认值
+ switch (dbType)
+ {
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} DROP DEFAULT;";
+ case DbType.MySql:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} DROP DEFAULT;";
+ default:
+ return $"-- SqlServer 需先查找默认值约束名再删除\n-- ALTER TABLE {table} DROP CONSTRAINT [DF_constraint_name];";
+ }
+ }
+ else
+ {
+ var defaultValue = sourceCol.DefaultValue;
+ switch (dbType)
+ {
+ case DbType.PostgreSQL:
+ case DbType.Oracle:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} SET DEFAULT {defaultValue};";
+ case DbType.MySql:
+ return $"ALTER TABLE {table} ALTER COLUMN {column} SET DEFAULT {defaultValue};";
+ default:
+ return $"-- SqlServer 需先删除旧默认值约束,再添加\n-- ALTER TABLE {table} ADD CONSTRAINT [DF_{sourceCol.DisplayName}_default] DEFAULT {defaultValue} FOR {column};";
+ }
+ }
+ }
+
+ ///
+ /// 生成备注变更语句
+ ///
+ private static string GenerateCommentSql(DbType dbType, string tableName, Column sourceCol)
+ {
+ var table = QuoteTable(dbType, tableName);
+ var column = QuoteIdent(dbType, sourceCol.DisplayName);
+ var comment = EscapeSqlValue(sourceCol.Comment ?? "");
+ var schema = "public";
+ var pureTable = tableName;
+ if (tableName.Contains("."))
+ {
+ var parts = tableName.Split('.');
+ schema = parts[0];
+ pureTable = parts[parts.Length - 1];
+ }
+
+ switch (dbType)
+ {
+ case DbType.MySql:
+ return $"ALTER TABLE {table} MODIFY COLUMN {column} {GetFullColumnDef(dbType, sourceCol)} COMMENT '{comment}';";
+ case DbType.PostgreSQL:
+ return $"COMMENT ON COLUMN {table}.{column} IS '{comment}';";
+ case DbType.Oracle:
+ return $"COMMENT ON COLUMN {table}.{column} IS '{comment}';";
+ case DbType.SqlServer:
+ return $"-- SqlServer 添加/更新扩展属性\nEXEC sp_addextendedproperty 'MS_Description', N'{comment}', 'SCHEMA', '{schema}', 'TABLE', '{pureTable}', 'COLUMN', '{sourceCol.DisplayName}';";
+ default:
+ return $"COMMENT ON COLUMN {table}.{column} IS '{comment}';";
+ }
+ }
+
+ private static string EscapeSqlValue(string value)
+ {
+ if (string.IsNullOrEmpty(value)) return "";
+ return value.Replace("'", "''");
+ }
+
+ #endregion
+
+ private static string GetColumnDisplay(Column col)
+ {
+ if (col == null) return "-";
+ var display = $"{col.DataType}";
+ var len = col.LengthName ?? col.Length.ToString();
+ if (!string.IsNullOrEmpty(len) && len != "0")
+ {
+ display += len;
+ }
+ return display;
+ }
+
+ private void BtnExport_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (DiffItems == null || !DiffItems.Any())
+ {
+ MessageBox.Show("没有可导出的差异数据", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ try
+ {
+ var saveFileDialog = new Microsoft.Win32.SaveFileDialog
+ {
+ Filter = "CSV 文件|*.csv",
+ FileName = $"表字段对比结果_{DateTime.Now:yyyyMMdd_HHmmss}",
+ Title = "导出对比结果"
+ };
+
+ if (saveFileDialog.ShowDialog() == true)
+ {
+ using (var writer = new StreamWriter(saveFileDialog.FileName, false, System.Text.Encoding.UTF8))
+ {
+ // 写 BOM,确保 Excel 正确显示中文
+ writer.Write('\uFEFF');
+ writer.WriteLine("表名,字段名,差异类型,源表值,目标表值,状态,修复SQL语句");
+
+ foreach (var item in DiffItems)
+ {
+ writer.WriteLine($"{EscapeCsv(item.TableName)},{EscapeCsv(item.ColumnName)},{EscapeCsv(item.DiffType)},{EscapeCsv(item.SourceValue)},{EscapeCsv(item.TargetValue)},{EscapeCsv(item.DiffStatus)},{EscapeCsv(item.SqlStatement)}");
+ }
+ }
+ MessageBox.Show($"导出成功!共 {DiffItems.Count} 条差异记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private static string EscapeCsv(string value)
+ {
+ if (string.IsNullOrEmpty(value)) return "";
+ if (value.IndexOfAny(new[] { ',', '"', '\n', '\r' }) >= 0)
+ {
+ return $"\"{value.Replace("\"", "\"\"")}\"";
+ }
+ return value;
+ }
+
+ ///
+ /// 复制选中行的SQL到剪贴板
+ ///
+ private void BtnCopySql_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (DiffGrid.SelectedItem == null)
+ {
+ MessageBox.Show("请先选择一行差异记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var selectedItem = DiffGrid.SelectedItem as TableFieldDiffItemModel;
+ if (selectedItem == null || string.IsNullOrEmpty(selectedItem.SqlStatement))
+ {
+ MessageBox.Show("该记录没有可复制的SQL语句", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ Clipboard.SetText(selectedItem.SqlStatement);
+ MessageBox.Show("SQL已复制到剪贴板", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+
+ ///
+ /// 复制所有SQL到剪贴板
+ ///
+ private void BtnCopyAllSql_OnClick(object sender, RoutedEventArgs e)
+ {
+ if (DiffItems == null || !DiffItems.Any())
+ {
+ MessageBox.Show("没有可复制的SQL语句", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ var sqlList = DiffItems
+ .Where(x => !string.IsNullOrEmpty(x.SqlStatement))
+ .Select(x => x.SqlStatement)
+ .ToList();
+
+ if (!sqlList.Any())
+ {
+ MessageBox.Show("没有可复制的SQL语句", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ Clipboard.SetText(string.Join("\n", sqlList));
+ MessageBox.Show($"已复制 {sqlList.Count} 条SQL语句到剪贴板", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+
+ private void BtnClose_OnClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+ }
+}
--
Gitee
From 97fd2aa80e03c9600030b46d4b58bb9530c9c0a2 Mon Sep 17 00:00:00 2001
From: zhubanghao <58813184@qq.com>
Date: Thu, 13 Aug 2026 15:39:37 +0800
Subject: [PATCH 4/4] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs
index 4bbb6a2..80a5793 100644
--- a/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs
+++ b/SmartSQL/SmartSQL/Views/BatchTableFieldDiffWindow.xaml.cs
@@ -378,7 +378,8 @@ namespace SmartSQL.Views
return dataType;
return dataType;
}
- return $"{dataType}({len})";
+ // LengthName 已包含括号(如 "(50)" 或 "(18,2)"),直接拼接即可
+ return $"{dataType}{len}";
}
///
@@ -623,7 +624,10 @@ namespace SmartSQL.Views
case DbType.Oracle:
return $"COMMENT ON COLUMN {table}.{column} IS '{comment}';";
case DbType.SqlServer:
- return $"-- SqlServer 添加/更新扩展属性\nEXEC sp_addextendedproperty 'MS_Description', N'{comment}', 'SCHEMA', '{schema}', 'TABLE', '{pureTable}', 'COLUMN', '{sourceCol.DisplayName}';";
+ // 先尝试删除旧备注,再添加新备注(不管备注是否已存在都能执行成功)
+ return $"-- SqlServer 先删除旧备注再添加新备注\n" +
+ $"EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', '{schema}', 'TABLE', '{pureTable}', 'COLUMN', '{sourceCol.DisplayName}';\n" +
+ $"EXEC sp_addextendedproperty 'MS_Description', N'{comment}', 'SCHEMA', '{schema}', 'TABLE', '{pureTable}', 'COLUMN', '{sourceCol.DisplayName}';";
default:
return $"COMMENT ON COLUMN {table}.{column} IS '{comment}';";
}
--
Gitee