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,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" Version="172.64.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="Forms\ConnectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="Forms\ProgressForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

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();
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
namespace DBMigration.Models
{
public class ConnectionInfo
{
public string ServerName { get; set; } = string.Empty;
public string DatabaseName { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool UseWindowsAuthentication { get; set; }
public string GetConnectionString()
{
var builder = new Microsoft.Data.SqlClient.SqlConnectionStringBuilder
{
DataSource = ServerName,
InitialCatalog = DatabaseName,
IntegratedSecurity = UseWindowsAuthentication,
TrustServerCertificate = true
};
if (!UseWindowsAuthentication)
{
builder.UserID = UserId;
builder.Password = Password;
}
return builder.ConnectionString;
}
}
}

View File

@@ -0,0 +1,13 @@
namespace DBMigration.Models
{
public class DatabaseObject
{
public string Name { get; set; } = string.Empty;
public string Schema { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public bool IsSelected { get; set; }
public string Definition { get; set; } = string.Empty;
public string FullName => $"[{Schema}].[{Name}]";
}
}

18
DBMigration/Program.cs Normal file
View File

@@ -0,0 +1,18 @@
using DBMigration.Forms;
namespace DBMigration;
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}

View File

@@ -0,0 +1,123 @@
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Smo;
using DBMigration.Models;
using System.Threading.Tasks;
namespace DBMigration.Services
{
public class DatabaseService
{
public async IAsyncEnumerable<DatabaseObject> GetTables(ConnectionInfo connection)
{
await foreach (var obj in GetDatabaseObjectsAsync(connection, "TABLE"))
{
yield return obj;
}
}
public async IAsyncEnumerable<DatabaseObject> GetViews(ConnectionInfo connection)
{
await foreach (var obj in GetDatabaseObjectsAsync(connection, "VIEW"))
{
yield return obj;
}
}
public async IAsyncEnumerable<DatabaseObject> GetProcedures(ConnectionInfo connection)
{
await foreach (var obj in GetDatabaseObjectsAsync(connection, "PROCEDURE"))
{
yield return obj;
}
}
private async IAsyncEnumerable<DatabaseObject> GetDatabaseObjectsAsync(ConnectionInfo connection, string objectType)
{
using (var conn = new SqlConnection(connection.GetConnectionString()))
{
await conn.OpenAsync();
var server = new Server(new Microsoft.SqlServer.Management.Common.ServerConnection(conn));
var database = server.Databases[connection.DatabaseName];
switch (objectType)
{
case "TABLE":
foreach (Table table in database.Tables)
{
if (table.IsSystemObject || table.Name.StartsWith("_")) continue;
yield return CreateDatabaseObject(table);
await Task.Yield();
}
break;
case "VIEW":
foreach (Microsoft.SqlServer.Management.Smo.View view in database.Views)
{
if (view.IsSystemObject) continue;
yield return CreateDatabaseObject(view);
await Task.Yield();
}
break;
case "PROCEDURE":
foreach (StoredProcedure sp in database.StoredProcedures)
{
if (sp.IsSystemObject) continue;
yield return CreateDatabaseObject(sp);
await Task.Yield();
}
break;
}
}
}
private DatabaseObject CreateDatabaseObject(Table table)
{
return new DatabaseObject
{
Name = table.Name,
Schema = table.Schema,
Type = "TABLE",
Definition = GetTableDefinition(table)
};
}
private DatabaseObject CreateDatabaseObject(Microsoft.SqlServer.Management.Smo.View view)
{
return new DatabaseObject
{
Name = view.Name,
Schema = view.Schema,
Type = "VIEW",
Definition = view.TextBody
};
}
private DatabaseObject CreateDatabaseObject(StoredProcedure sp)
{
return new DatabaseObject
{
Name = sp.Name,
Schema = sp.Schema,
Type = "PROCEDURE",
Definition = sp.TextBody
};
}
private string GetTableDefinition(Table table)
{
var options = new ScriptingOptions
{
IncludeIfNotExists = true,
ScriptDrops = false,
WithDependencies = true,
Indexes = true,
Triggers = true,
ClusteredIndexes = true,
NonClusteredIndexes = true
};
return table.Script(options).ToString();
}
}
}

View File

@@ -0,0 +1,92 @@
using Microsoft.Data.SqlClient;
using Microsoft.SqlServer.Management.Smo;
using DBMigration.Models;
namespace DBMigration.Services
{
public class MigrationService
{
private readonly DatabaseService _databaseService;
public MigrationService()
{
_databaseService = new DatabaseService();
}
public void MigrateTable(DatabaseObject table, ConnectionInfo source, ConnectionInfo target)
{
using (var sourceConn = new SqlConnection(source.GetConnectionString()))
using (var targetConn = new SqlConnection(target.GetConnectionString()))
{
sourceConn.Open();
targetConn.Open();
// 1. 테이블 생성 (인덱스 포함)
ExecuteScript(table.Definition, targetConn);
// 2. IDENTITY와 트리거 비활성화
DisableIdentityAndTriggers(table, targetConn);
// 3. 데이터 복사
CopyData(table, sourceConn, targetConn);
// 4. IDENTITY와 트리거 재활성화
EnableIdentityAndTriggers(table, targetConn);
// 5. 통계 업데이트
UpdateStatistics(table, targetConn);
}
}
private void ExecuteScript(string script, SqlConnection connection)
{
using (var cmd = new SqlCommand(script, connection))
{
cmd.ExecuteNonQuery();
}
}
private void DisableIdentityAndTriggers(DatabaseObject table, SqlConnection connection)
{
var disableScript = $@"
-- IDENTITY 비활성화
SET IDENTITY_INSERT {table.FullName} ON;
-- 트리거 비활성화
DISABLE TRIGGER ALL ON {table.FullName};";
ExecuteScript(disableScript, connection);
}
private void EnableIdentityAndTriggers(DatabaseObject table, SqlConnection connection)
{
var enableScript = $@"
-- IDENTITY 활성화
SET IDENTITY_INSERT {table.FullName} OFF;
-- 트리거 활성화
ENABLE TRIGGER ALL ON {table.FullName};";
ExecuteScript(enableScript, connection);
}
private void CopyData(DatabaseObject table, SqlConnection source, SqlConnection target)
{
using (var cmd = new SqlCommand($"SELECT * FROM {table.FullName}", source))
using (var reader = cmd.ExecuteReader())
{
using (var bulkCopy = new SqlBulkCopy(target))
{
bulkCopy.DestinationTableName = table.FullName;
bulkCopy.WriteToServer(reader);
}
}
}
private void UpdateStatistics(DatabaseObject table, SqlConnection connection)
{
var updateStatsScript = $"UPDATE STATISTICS {table.FullName} WITH FULLSCAN;";
ExecuteScript(updateStatsScript, connection);
}
}
}