entity 오류로인해 제거해야해서 . 제거전 백업

This commit is contained in:
chi
2025-04-06 01:54:17 +09:00
parent a82471915e
commit 2b22ca9c51
65 changed files with 8031 additions and 1945 deletions

View File

@@ -0,0 +1,131 @@
using DBMigration.Models;
namespace DBMigration.Forms
{
public partial class ConnectionForm : Form
{
public ConnectionInfo ConnectionInfo { get; private set; }
public ConnectionForm()
{
InitializeComponent();
this.Load += ConnectionForm_Load;
}
private void ConnectionForm_Load(object? sender, EventArgs e)
{
UpdateCredentialsFields();
this.txtServer.Text = "10.131.15.18";
this.txtDatabase.Text = "EE";
this.txtUserId.Text = "eeuser";
this.txtPassword.Text = "Amkor123!";
this.chkWindowsAuth.Checked = false;
}
private void UpdateCredentialsFields()
{
bool isWindowsAuth = chkWindowsAuth.Checked;
txtUserId.Enabled = !isWindowsAuth;
txtPassword.Enabled = !isWindowsAuth;
}
private void InitializeComponent()
{
this.txtServer = new TextBox();
this.txtDatabase = new TextBox();
this.txtUserId = new TextBox();
this.txtPassword = new TextBox();
this.chkWindowsAuth = new CheckBox();
this.btnConnect = new Button();
this.btnCancel = new Button();
this.SuspendLayout();
// txtServer
this.txtServer.Location = new Point(12, 12);
this.txtServer.Size = new Size(200, 23);
this.txtServer.PlaceholderText = "서버 이름";
// txtDatabase
this.txtDatabase.Location = new Point(12, 41);
this.txtDatabase.Size = new Size(200, 23);
this.txtDatabase.PlaceholderText = "데이터베이스 이름";
// txtUserId
this.txtUserId.Location = new Point(12, 70);
this.txtUserId.Size = new Size(200, 23);
this.txtUserId.PlaceholderText = "사용자 ID";
// txtPassword
this.txtPassword.Location = new Point(12, 99);
this.txtPassword.Size = new Size(200, 23);
this.txtPassword.PasswordChar = '*';
this.txtPassword.PlaceholderText = "비밀번호";
// chkWindowsAuth
this.chkWindowsAuth.Location = new Point(12, 128);
this.chkWindowsAuth.Size = new Size(200, 23);
this.chkWindowsAuth.Text = "Windows 인증 사용";
this.chkWindowsAuth.CheckedChanged += (s, e) => UpdateCredentialsFields();
// btnConnect
this.btnConnect.Location = new Point(12, 157);
this.btnConnect.Size = new Size(95, 23);
this.btnConnect.Text = "연결";
this.btnConnect.Click += BtnConnect_Click;
// btnCancel
this.btnCancel.Location = new Point(117, 157);
this.btnCancel.Size = new Size(95, 23);
this.btnCancel.Text = "취소";
this.btnCancel.Click += BtnCancel_Click;
// ConnectionForm
this.ClientSize = new Size(224, 192);
this.Controls.AddRange(new Control[] {
this.txtServer,
this.txtDatabase,
this.txtUserId,
this.txtPassword,
this.chkWindowsAuth,
this.btnConnect,
this.btnCancel
});
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterParent;
this.Text = "데이터베이스 연결";
this.ResumeLayout(false);
this.PerformLayout();
}
private void BtnConnect_Click(object? sender, EventArgs e)
{
ConnectionInfo = new ConnectionInfo
{
ServerName = txtServer.Text,
DatabaseName = txtDatabase.Text,
UserId = txtUserId.Text,
Password = txtPassword.Text,
UseWindowsAuthentication = chkWindowsAuth.Checked
};
DialogResult = DialogResult.OK;
Close();
}
private void BtnCancel_Click(object? sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private TextBox txtServer;
private TextBox txtDatabase;
private TextBox txtUserId;
private TextBox txtPassword;
private CheckBox chkWindowsAuth;
private Button btnConnect;
private Button btnCancel;
}
}

67
DBMigration/Forms/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,67 @@
namespace DBMigration.Forms
{
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.treeObjects = new System.Windows.Forms.TreeView();
this.btnConnectSource = new System.Windows.Forms.Button();
this.btnMigrate = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// treeObjects
//
this.treeObjects.Location = new System.Drawing.Point(12, 12);
this.treeObjects.Name = "treeObjects";
this.treeObjects.Size = new System.Drawing.Size(300, 400);
this.treeObjects.TabIndex = 0;
//
// btnConnectSource
//
this.btnConnectSource.Location = new System.Drawing.Point(12, 418);
this.btnConnectSource.Name = "btnConnectSource";
this.btnConnectSource.Size = new System.Drawing.Size(150, 30);
this.btnConnectSource.TabIndex = 1;
this.btnConnectSource.Text = "소스 DB 연결";
this.btnConnectSource.UseVisualStyleBackColor = true;
this.btnConnectSource.Click += new System.EventHandler(this.btnConnectSource_Click);
//
// btnMigrate
//
this.btnMigrate.Location = new System.Drawing.Point(168, 418);
this.btnMigrate.Name = "btnMigrate";
this.btnMigrate.Size = new System.Drawing.Size(144, 30);
this.btnMigrate.TabIndex = 2;
this.btnMigrate.Text = "마이그레이션 시작";
this.btnMigrate.UseVisualStyleBackColor = true;
this.btnMigrate.Click += new System.EventHandler(this.btnMigrate_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(324, 461);
this.Controls.Add(this.btnMigrate);
this.Controls.Add(this.btnConnectSource);
this.Controls.Add(this.treeObjects);
this.Name = "MainForm";
this.Text = "DB Migration Tool";
this.ResumeLayout(false);
}
private System.Windows.Forms.TreeView treeObjects;
private System.Windows.Forms.Button btnConnectSource;
private System.Windows.Forms.Button btnMigrate;
}
}

View File

@@ -0,0 +1,184 @@
using DBMigration.Models;
using DBMigration.Services;
namespace DBMigration.Forms
{
public partial class MainForm : Form
{
private readonly DatabaseService _databaseService;
private readonly MigrationService _migrationService;
private ConnectionInfo? _sourceConnection;
private ConnectionInfo? _targetConnection;
private List<DatabaseObject>? _databaseObjects;
private readonly CancellationTokenSource _cancellationTokenSource;
public MainForm()
{
InitializeComponent();
_databaseService = new DatabaseService();
_migrationService = new MigrationService();
_cancellationTokenSource = new CancellationTokenSource();
_databaseObjects = new List<DatabaseObject>();
}
private async void btnConnectSource_Click(object sender, EventArgs e)
{
using (var form = new ConnectionForm())
{
if (form.ShowDialog() == DialogResult.OK)
{
_sourceConnection = form.ConnectionInfo;
await LoadDatabaseObjectsAsync();
}
}
}
private async Task LoadDatabaseObjectsAsync()
{
try
{
btnConnectSource.Enabled = false;
treeObjects.Nodes.Clear();
// 테이블, 뷰, 프로시저 노드 생성
var tableNode = treeObjects.Nodes.Add("Tables");
var viewNode = treeObjects.Nodes.Add("Views");
var procNode = treeObjects.Nodes.Add("Stored Procedures");
// 테이블 로드
tableNode.Nodes.Add("Loading...");
treeObjects.ExpandAll();
await LoadTablesAsync(tableNode);
// 뷰 로드
viewNode.Nodes.Add("Loading...");
await LoadViewsAsync(viewNode);
// 프로시저 로드
procNode.Nodes.Add("Loading...");
await LoadProceduresAsync(procNode);
btnConnectSource.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show($"데이터베이스 객체 로드 중 오류 발생: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
btnConnectSource.Enabled = true;
}
}
private async Task LoadTablesAsync(TreeNode parentNode)
{
try
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add("Loading...");
await foreach (var table in _databaseService.GetTables(_sourceConnection!))
{
if (_cancellationTokenSource.Token.IsCancellationRequested)
return;
var node = new TreeNode($"{table.Schema}.{table.Name}")
{
Tag = table,
Checked = table.IsSelected
};
parentNode.Nodes.Add(node);
await Task.Yield();
}
}
catch (Exception ex)
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add($"Error: {ex.Message}");
}
}
private async Task LoadViewsAsync(TreeNode parentNode)
{
try
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add("Loading...");
await foreach (var view in _databaseService.GetViews(_sourceConnection!))
{
if (_cancellationTokenSource.Token.IsCancellationRequested)
return;
var node = new TreeNode($"{view.Schema}.{view.Name}")
{
Tag = view,
Checked = view.IsSelected
};
parentNode.Nodes.Add(node);
await Task.Yield();
}
}
catch (Exception ex)
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add($"Error: {ex.Message}");
}
}
private async Task LoadProceduresAsync(TreeNode parentNode)
{
try
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add("Loading...");
await foreach (var proc in _databaseService.GetProcedures(_sourceConnection!))
{
if (_cancellationTokenSource.Token.IsCancellationRequested)
return;
var node = new TreeNode($"{proc.Schema}.{proc.Name}")
{
Tag = proc,
Checked = proc.IsSelected
};
parentNode.Nodes.Add(node);
await Task.Yield();
}
}
catch (Exception ex)
{
parentNode.Nodes.Clear();
parentNode.Nodes.Add($"Error: {ex.Message}");
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_cancellationTokenSource.Cancel();
base.OnFormClosing(e);
}
private void btnMigrate_Click(object sender, EventArgs e)
{
if (_targetConnection == null)
{
MessageBox.Show("대상 데이터베이스 연결 정보를 먼저 설정하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var selectedObjects = _databaseObjects.Where(o => o.IsSelected).ToList();
if (selectedObjects.Count == 0)
{
MessageBox.Show("마이그레이션할 객체를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var form = new ProgressForm(selectedObjects, _sourceConnection, _targetConnection))
{
form.ShowDialog();
}
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,56 @@
namespace DBMigration.Forms
{
partial class ProgressForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.progressBar = new System.Windows.Forms.ProgressBar();
this.logTextBox = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// progressBar
//
this.progressBar.Location = new System.Drawing.Point(12, 12);
this.progressBar.Name = "progressBar";
this.progressBar.Size = new System.Drawing.Size(300, 23);
this.progressBar.TabIndex = 0;
//
// logTextBox
//
this.logTextBox.Location = new System.Drawing.Point(12, 41);
this.logTextBox.Multiline = true;
this.logTextBox.Name = "logTextBox";
this.logTextBox.ReadOnly = true;
this.logTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.logTextBox.Size = new System.Drawing.Size(300, 200);
this.logTextBox.TabIndex = 1;
//
// ProgressForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(324, 253);
this.Controls.Add(this.logTextBox);
this.Controls.Add(this.progressBar);
this.Name = "ProgressForm";
this.Text = "마이그레이션 진행 상황";
this.Load += new System.EventHandler(this.ProgressForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.ProgressBar progressBar;
private System.Windows.Forms.TextBox logTextBox;
}
}

View File

@@ -0,0 +1,107 @@
using DBMigration.Models;
using DBMigration.Services;
using Microsoft.Data.SqlClient;
namespace DBMigration.Forms
{
public partial class ProgressForm : Form
{
private readonly List<DatabaseObject> _objects;
private readonly ConnectionInfo _source;
private readonly ConnectionInfo _target;
private readonly MigrationService _migrationService;
public ProgressForm(List<DatabaseObject> objects, ConnectionInfo source, ConnectionInfo target)
{
InitializeComponent();
_objects = objects;
_source = source;
_target = target;
_migrationService = new MigrationService();
progressBar.Maximum = _objects.Count;
}
private async void ProgressForm_Load(object sender, EventArgs e)
{
await Task.Run(() => MigrateObjects());
}
private void MigrateObjects()
{
foreach (var obj in _objects)
{
try
{
UpdateProgress($"마이그레이션 시작: {obj.Type} {obj.Schema}.{obj.Name}");
if (obj.Type == "TABLE")
{
_migrationService.MigrateTable(obj, _source, _target);
}
else
{
// 뷰나 프로시저의 경우 스크립트만 실행
ExecuteScript(obj.Definition, _target);
}
UpdateProgress($"완료: {obj.Type} {obj.Schema}.{obj.Name}");
UpdateProgressBar();
}
catch (Exception ex)
{
UpdateProgress($"오류 발생: {obj.Type} {obj.Schema}.{obj.Name}");
UpdateProgress($"에러 메시지: {ex.Message}");
if (MessageBox.Show(
$"{obj.Type} {obj.Schema}.{obj.Name} 마이그레이션 중 오류가 발생했습니다.\n계속 진행하시겠습니까?",
"오류",
MessageBoxButtons.YesNo,
MessageBoxIcon.Error) == DialogResult.No)
{
break;
}
}
}
MessageBox.Show("마이그레이션이 완료되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
DialogResult = DialogResult.OK;
}
private void UpdateProgress(string message)
{
if (InvokeRequired)
{
Invoke(new Action<string>(UpdateProgress), message);
return;
}
logTextBox.AppendText(message + Environment.NewLine);
logTextBox.SelectionStart = logTextBox.TextLength;
logTextBox.ScrollToCaret();
}
private void UpdateProgressBar()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateProgressBar));
return;
}
progressBar.Value++;
}
private void ExecuteScript(string script, ConnectionInfo connection)
{
using (var conn = new SqlConnection(connection.GetConnectionString()))
{
conn.Open();
using (var cmd = new SqlCommand(script, conn))
{
cmd.ExecuteNonQuery();
}
}
}
}
}