사용 사례

/

코드 유지 관리

레거시 코드 현대화

모델

클로드 3.7 소네트

WPF를 사용하여 VB6 인벤토리 데스크톱 앱을 C# .NET 8로 현대화—마이그레이션 계획과 단일 양식에 대한 샘플 재작성 코드를 제공합니다.

쿼리 사용해보기
클로드 3.7 소네트
/
코더

WPF를 사용하여 VB6 인벤토리 응용 프로그램을 C#.NET 8로 마이그레이션

마이그레이션 계획

1단계: 평가 및 계획

  1. 기존 기능 인벤토리
    • 모든 양식, 모듈 및 클래스를 문서화합니다.
    • 데이터베이스 연결 및 데이터 액세스 방법 식별
    • 비즈니스 로직 및 워크플로우 매핑
    • 서드파티 종속성 파악
  2. 아키텍처 계획
    • MVVM 패턴을 사용한 새로운 솔루션 구조 설계
    • 데이터베이스 마이그레이션 전략 계획
    • 기술 스택 정의 (EF Core 등)
    • 재사용 가능한 구성 요소 식별

2단계: 환경 설정

  1. 개발 환경
    • 비주얼 스튜디오 2022 설정
    • .NET 8을 사용하여 새 WPF 솔루션 만들기
    • 소스 컨트롤 설정
    • CI/CD 파이프라인 설정
  2. 프로젝트 구조 생성
    • 핵심 프로젝트 생성 (모델, 뷰 모델, 서비스)
    • 의존성 주입 설정
    • 로깅 및 오류 처리 구성

3단계: 데이터베이스 마이그레이션

  1. 데이터베이스 평가
    • 기존 데이터베이스 스키마 분석
    • 필요한 경우 최신 데이터베이스로의 마이그레이션 계획
    • 디자인 엔티티 프레임워크 코어 모델
  2. 데이터 액세스 계층 구현
    • 데이터베이스 컨텍스트 및 엔티티 구성 생성
    • 리포지토리 구현
    • 데이터 마이그레이션 스크립트 생성

4단계: UI 마이그레이션

  1. UI 디자인
    • WPF 스타일 및 테마 만들기
    • 재사용 가능한 컨트롤 설계
    • 기본 MVVM 인프라 구현
  2. 양식 마이그레이션
    • 복잡성/중요도에 따라 양식의 우선 순위 지정
    • 한 번에 한 양식씩 구현
    • 각 양식에 대한 단위 테스트 생성

5단계: 비즈니스 로직 마이그레이션

  1. 서비스 계층 구현
    • 비즈니스 로직을 서비스 클래스로 마이그레이션
    • 검증 규칙 구현
    • 단위 테스트 생성
  2. 통합
    • UI를 서비스에 연결
    • 오류 처리 구현
    • 로깅 추가

6단계: 테스트 및 배포

  1. 테스팅
    • 유닛 테스팅
    • 통합 테스트
    • 사용자 승인 테스트
  2. 배포
    • 인스톨러 생성
    • 문서 배포 프로세스
    • 계획 롤아웃 전략

샘플 마이그레이션: 재고 품목 양식

원본 VB6 양식 (가정)

1' frmInventoryItem.frm
2Option Explicit
3
4Private Sub Form_Load()
5    ' Initialize form
6    txtItemID.Text = ""
7    txtName.Text = ""
8    txtDescription.Text = ""
9    txtQuantity.Text = "0"
10    txtPrice.Text = "0.00"
11    cboCategory.Clear
12    
13    ' Load categories
14    Dim rs As ADODB.Recordset
15    Set rs = New ADODB.Recordset
16    rs.Open "SELECT CategoryID, CategoryName FROM Categories", conn, adOpenStatic
17    
18    Do Until rs.EOF
19        cboCategory.AddItem rs!CategoryName
20        cboCategory.ItemData(cboCategory.NewIndex) = rs!CategoryID
21        rs.MoveNext
22    Loop
23    rs.Close
24End Sub
25
26Private Sub btnSave_Click()
27    ' Validate input
28    If Trim(txtName.Text) = "" Then
29        MsgBox "Please enter item name", vbExclamation
30        txtName.SetFocus
31        Exit Sub
32    End If
33    
34    If Not IsNumeric(txtQuantity.Text) Then
35        MsgBox "Quantity must be numeric", vbExclamation
36        txtQuantity.SetFocus
37        Exit Sub
38    End If
39    
40    If Not IsNumeric(txtPrice.Text) Then
41        MsgBox "Price must be numeric", vbExclamation
42        txtPrice.SetFocus
43        Exit Sub
44    End If
45    
46    ' Save to database
47    Dim sql As String
48    If Trim(txtItemID.Text) = "" Then
49        ' Insert new record
50        sql = "INSERT INTO Inventory (Name, Description, CategoryID, Quantity, Price) VALUES ('" & _
51              Replace(txtName.Text, "'", "''") & "', '" & _
52              Replace(txtDescription.Text, "'", "''") & "', " & _
53              cboCategory.ItemData(cboCategory.ListIndex) & ", " & _
54              Val(txtQuantity.Text) & ", " & _
55              Val(txtPrice.Text) & ")"
56        conn.Execute sql
57        MsgBox "Item added successfully", vbInformation
58    Else
59        ' Update existing record
60        sql = "UPDATE Inventory SET Name = '" & Replace(txtName.Text, "'", "''") & _
61              "', Description = '" & Replace(txtDescription.Text, "'", "''") & _
62              "', CategoryID = " & cboCategory.ItemData(cboCategory.ListIndex) & _
63              ", Quantity = " & Val(txtQuantity.Text) & _
64              ", Price = " & Val(txtPrice.Text) & _
65              " WHERE ItemID = " & Val(txtItemID.Text)
66        conn.Execute sql
67        MsgBox "Item updated successfully", vbInformation
68    End If
69    
70    Unload Me
71End Sub
72
73Private Sub btnCancel_Click()
74    Unload Me
75End Sub

C# .NET 8 WPF 마이그레이션

1.모델들

1// Models/InventoryItem.cs
2namespace InventoryApp.Models
3{
4    public class InventoryItem
5    {
6        public int ItemId { get; set; }
7        public string Name { get; set; } = string.Empty;
8        public string Description { get; set; } = string.Empty;
9        public int CategoryId { get; set; }
10        public string CategoryName { get; set; } = string.Empty;
11        public int Quantity { get; set; }
12        public decimal Price { get; set; }
13    }
14
15    public class Category
16    {
17        public int CategoryId { get; set; }
18        public string CategoryName { get; set; } = string.Empty;
19    }
20}

2.데이터 액세스

1// Data/AppDbContext.cs
2using InventoryApp.Models;
3using Microsoft.EntityFrameworkCore;
4
5namespace InventoryApp.Data
6{
7    public class AppDbContext : DbContext
8    {
9        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
10
11        public DbSet<InventoryItem> InventoryItems { get; set; }
12        public DbSet<Category> Categories { get; set; }
13
14        protected override void OnModelCreating(ModelBuilder modelBuilder)
15        {
16            modelBuilder.Entity<InventoryItem>().ToTable("Inventory");
17            modelBuilder.Entity<Category>().ToTable("Categories");
18        }
19    }
20}

3.서비스

1// Services/IInventoryService.cs
2using InventoryApp.Models;
3using System.Collections.Generic;
4using System.Threading.Tasks;
5
6namespace InventoryApp.Services
7{
8    public interface IInventoryService
9    {
10        Task<List<Category>> GetCategoriesAsync();
11        Task<InventoryItem?> GetInventoryItemAsync(int id);
12        Task<bool> SaveInventoryItemAsync(InventoryItem item);
13    }
14}
15
16// Services/InventoryService.cs
17using InventoryApp.Data;
18using InventoryApp.Models;
19using Microsoft.EntityFrameworkCore;
20using System.Collections.Generic;
21using System.Threading.Tasks;
22
23namespace InventoryApp.Services
24{
25    public class InventoryService : IInventoryService
26    {
27        private readonly AppDbContext _dbContext;
28
29        public InventoryService(AppDbContext dbContext)
30        {
31            _dbContext = dbContext;
32        }
33
34        public async Task<List<Category>> GetCategoriesAsync()
35        {
36            return await _dbContext.Categories.ToListAsync();
37        }
38
39        public async Task<InventoryItem?> GetInventoryItemAsync(int id)
40        {
41            return await _dbContext.InventoryItems
42                .FirstOrDefaultAsync(i => i.ItemId == id);
43        }
44
45        public async Task<bool> SaveInventoryItemAsync(InventoryItem item)
46        {
47            try
48            {
49                if (item.ItemId == 0)
50                {
51                    await _dbContext.InventoryItems.AddAsync(item);
52                }
53                else
54                {
55                    _dbContext.InventoryItems.Update(item);
56                }
57                
58                await _dbContext.SaveChangesAsync();
59                return true;
60            }
61            catch
62            {
63                return false;
64            }
65        }
66    }
67}

4.모델 보기

1// ViewModels/InventoryItemViewModel.cs
2using CommunityToolkit.Mvvm.ComponentModel;
3using CommunityToolkit.Mvvm.Input;
4using InventoryApp.Models;
5using InventoryApp.Services;
6using System;
7using System.Collections.ObjectModel;
8using System.ComponentModel.DataAnnotations;
9using System.Threading.Tasks;
10using System.Windows;
11
12namespace InventoryApp.ViewModels
13{
14    public partial class InventoryItemViewModel : ObservableObject
15    {
16        private readonly IInventoryService _inventoryService;
17
18        [ObservableProperty]
19        private int _itemId;
20
21        [ObservableProperty]
22        [NotifyDataErrorInfo]
23        [Required(ErrorMessage = "Name is required")]
24        private string _name = string.Empty;
25
26        [ObservableProperty]
27        private string _description = string.Empty;
28
29        [ObservableProperty]
30        [NotifyDataErrorInfo]
31        [Range(0, int.MaxValue, ErrorMessage = "Quantity must be a positive number")]
32        private int _quantity;
33
34        [ObservableProperty]
35        [NotifyDataErrorInfo]
36        [Range(0, double.MaxValue, ErrorMessage = "Price must be a positive number")]
37        private decimal _price;
38
39        [ObservableProperty]
40        private Category? _selectedCategory;
41
42        [ObservableProperty]
43        private ObservableCollection<Category> _categories = new();
44
45        [ObservableProperty]
46        private bool _isLoading;
47
48        [ObservableProperty]
49        private string _windowTitle = "New Inventory Item";
50
51        public InventoryItemViewModel(IInventoryService inventoryService)
52        {
53            _inventoryService = inventoryService;
54        }
55
56        public async Task LoadDataAsync(int? itemId = null)
57        {
58            try
59            {
60                IsLoading = true;
61                
62                // Load categories
63                var categoryList = await _inventoryService.GetCategoriesAsync();
64                Categories = new ObservableCollection<Category>(categoryList);
65
66                // If editing existing item
67                if (itemId.HasValue && itemId.Value > 0)
68                {
69                    var item = await _inventoryService.GetInventoryItemAsync(itemId.Value);
70                    if (item != null)
71                    {
72                        ItemId = item.ItemId;
73                        Name = item.Name;
74                        Description = item.Description;
75                        Quantity = item.Quantity;
76                        Price = item.Price;
77                        SelectedCategory = Categories.FirstOrDefault(c => c.CategoryId == item.CategoryId);
78                        WindowTitle = "Edit Inventory Item";
79                    }
80                }
81            }
82            finally
83            {
84                IsLoading = false;
85            }
86        }
87
88        [RelayCommand]
89        private async Task SaveAsync()
90        {
91            if (SelectedCategory == null)
92            {
93                MessageBox.Show("Please select a category", "Validation Error", MessageBoxButton.OK, MessageBoxImage.Warning);
94                return;
95            }
96
97            var item = new InventoryItem
98            {
99                ItemId = ItemId,
100                Name = Name,
101                Description = Description,
102                CategoryId = SelectedCategory.CategoryId,
103                Quantity = Quantity,
104                Price = Price
105            };
106
107            var result = await _inventoryService.SaveInventoryItemAsync(item);
108            if (result)
109            {
110                MessageBox.Show(ItemId == 0 ? "Item added successfully" : "Item updated successfully", 
111                    "Success", MessageBoxButton.OK, MessageBoxImage.Information);
112                CloseWindow?.Invoke();
113            }
114            else
115            {
116                MessageBox.Show("Error saving item", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
117            }
118        }
119
120        [RelayCommand]
121        private void Cancel()
122        {
123            CloseWindow?.Invoke();
124        }
125
126        // Event to close the window
127        public Action? CloseWindow { get; set; }
128    }
129}

5.보기

1<!-- Views/InventoryItemView.xaml -->
2<Window x:Class="InventoryApp.Views.InventoryItemView"
3        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7        xmlns:vm="clr-namespace:InventoryApp.ViewModels"
8        mc:Ignorable="d"
9        Title="{Binding WindowTitle}"
10        Height="450" Width="500"
11        WindowStartupLocation="CenterOwner">
12    
13    <Window.Resources>
14        <Style TargetType="TextBlock">
15            <Setter Property="Margin" Value="0,5,0,2" />
16        </Style>
17        <Style TargetType="TextBox">
18            <Setter Property="Padding" Value="5" />
19            <Setter Property="Margin" Value="0,0,0,10" />
20        </Style>
21        <Style TargetType="ComboBox">
22            <Setter Property="Padding" Value="5" />
23            <Setter Property="Margin" Value="0,0,0,10" />
24        </Style>
25        <Style TargetType="Button">
26            <Setter Property="Padding" Value="15,5" />
27            <Setter Property="Margin" Value="5" />
28            <Setter Property="MinWidth" Value="80" />
29        </Style>
30    </Window.Resources>
31    
32    <Grid Margin="20">
33        <Grid.RowDefinitions>
34            <RowDefinition Height="*" />
35            <RowDefinition Height="Auto" />
36        </Grid.RowDefinitions>
37        
38        <StackPanel Grid.Row="0">
39            <TextBlock Text="Item Name:" />
40            <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
41            
42            <TextBlock Text="Description:" />
43            <TextBox Text="{Binding Description}" Height="60" TextWrapping="Wrap" AcceptsReturn="True" />
44            
45            <TextBlock Text="Category:" />
46            <ComboBox ItemsSource="{Binding Categories}" 
47                      SelectedItem="{Binding SelectedCategory}"
48                      DisplayMemberPath="CategoryName" />
49            
50            <TextBlock Text="Quantity:" />
51            <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
52            
53            <TextBlock Text="Price:" />
54            <TextBox Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
55        </StackPanel>
56        
57        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
58            <Button Content="Save" Command="{Binding SaveCommand}" />
59            <Button Content="Cancel" Command="{Binding CancelCommand}" />
60        </StackPanel>
61        
62        <Grid Grid.RowSpan="2" Background="#80000000" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
63            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
64                <TextBlock Text="Loading..." Foreground="White" FontSize="16" HorizontalAlignment="Center" />
65                <ProgressBar IsIndeterminate="True" Width="200" Height="20" Margin="0,10,0,0" />
66            </StackPanel>
67        </Grid>
68    </Grid>
69</Window>
1// Views/InventoryItemView.xaml.cs
2using InventoryApp.ViewModels;
3using System.Windows;
4
5namespace InventoryApp.Views
6{
7    public partial class InventoryItemView : Window
8    {
9        private readonly InventoryItemViewModel _viewModel;
10
11        public InventoryItemView(InventoryItemViewModel viewModel, int? itemId = null)
12        {
13            InitializeComponent();
14            _viewModel = viewModel;
15            DataContext = _viewModel;
16            
17            _viewModel.CloseWindow = () => Close();
18            
19            Loaded += async (s, e) => 
20            {
21                await _viewModel.LoadDataAsync(itemId);
22            };
23        }
24    }
25}

6.앱 시작 및 DI 구성

1// App.xaml.cs
2using InventoryApp.Data;
3using InventoryApp.Services;
4using InventoryApp.ViewModels;
5using InventoryApp.Views;
6using Microsoft.EntityFrameworkCore;
7using Microsoft.Extensions.DependencyInjection;
8using Microsoft.Extensions.Hosting;
9using System.Windows;
10
11namespace InventoryApp
12{
13    public partial class App : Application
14    {
15        private readonly IHost _host;
16
17        public App()
18        {
19            _host = Host.CreateDefaultBuilder()
20                .ConfigureServices((context, services) =>
21                {
22                    // Register DbContext
23                    services.AddDbContext<AppDbContext>(options =>
24                        options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=InventoryApp;Trusted_Connection=True;"));
25
26                    // Register services
27                    services.AddScoped<IInventoryService, InventoryService>();
28
29                    // Register ViewModels
30                    services.AddTransient<InventoryItemViewModel>();
31
32                    // Register Views
33                    services.AddTransient<InventoryItemView>();
34                    services.AddTransient<MainWindow>();
35                })
36                .Build();
37        }
38
39        protected override void OnStartup(StartupEventArgs e)
40        {
41            _host.Start();
42
43            var mainWindow = _host.Services.GetRequiredService<MainWindow>();
44            mainWindow.Show();
45
46            base.OnStartup(e);
47        }
48
49        protected override void OnExit(ExitEventArgs e)
50        {
51            _host.Dispose();
52            base.OnExit(e);
53        }
54    }
55}

마이그레이션의 주요 개선 사항

  1. 모던 아키텍처
    • 우려 사항이 명확하게 구분된 MVVM 패턴
    • 더 나은 테스트 가능성을 위한 의존성 주입
    • 비동기/반응형 UI 대기
  2. 데이터 액세스
    • ADO.NET 대신 엔티티 프레임워크 코어
    • SQL 삽입을 방지하기 위한 매개변수화된 쿼리
    • 데이터 액세스를 위한 리포지토리 패턴
  3. UI 개선
    • WPF 스타일 및 템플릿
    • 검증을 통한 데이터 바인딩
    • 비동기 작업을 위한 로드 표시기
  4. 코드 품질
    • 변형 유형 대신 강력한 타이핑
    • 적절한 오류 처리
    • 데이터 주석을 사용한 입력 검증
  5. 유지보수성
    • 체계적인 프로젝트 구조
    • 테스트 가능한 구성 요소
    • 최신 C# 언어 기능

이 마이그레이션 접근 방식은 VB6 인벤토리 애플리케이션을 현대화하는 동시에 핵심 기능을 보존하고 최신 개발 방식으로 개선하기 위한 견고한 토대를 제공합니다.

닌자의 AI 어시스턴트를 경험하세요

지금 무료로 사용해 보십시오.요금제는 월 19달러부터 시작합니다.