Use Case

/

代码维护

对旧版代码进行现代化改造

Model

Claude 3.7 十四行诗

使用 WPF 将 VB6 库存桌面应用程序现代化为 C# .NET 8 — 为一个表单提供迁移计划和示例重写代码。

Try Query
Claude 3.7 十四行诗
/
编码员

使用 WPF 将 VB6 清单应用程序迁移到 C# .NET 8

移民计划

第 1 阶段:评估和规划

  1. 清点现有功能
    • 记录所有表单、模块和类
    • 确定数据库连接和数据访问方法
    • 映射业务逻辑和工作流程
    • 识别第三方依赖关系
  2. 架构规划
    • 使用 MVVM 模式设计新的解决方案结构
    • 规划数据库迁移策略
    • 定义技术堆栈(EF Core 等)
    • 识别可重复使用的组件

第 2 阶段:环境设置

  1. 开发环境
    • 设置 Visual Studio 2022
    • 使用 .NET 8 创建新的 WPF 解决方案
    • 配置源代码控制
    • 设置 CI/CD 管道
  2. 创建项目结构
    • 创建核心项目(模型、视图模型、服务)
    • 设置依赖注入
    • 配置日志记录和错误处理

第 3 阶段:数据库迁移

  1. 数据库评估
    • 分析现有数据库架构
    • 如果需要,计划迁移到现代数据库
    • 设计实体框架核心模型
  2. 数据接入层的实现
    • 创建 DbContext 和实体配置
    • 实现存储库
    • 创建数据迁移脚本

第 4 阶段:用户界面迁移

  1. 用户界面设计
    • 创建 WPF 样式和主题
    • 设计可重复使用的控件
    • 实现基本 MVVM 基础架构
  2. 表单迁移
    • 按复杂性/重要性对表单进行优先级排序
    • 一次实现一个表单
    • 为每个表单创建单元测试

第 5 阶段:业务逻辑迁移

  1. 服务层实现
    • 将业务逻辑迁移到服务类别
    • 实施验证规则
    • 创建单元测试
  2. 整合
    • 将用户界面连接到服务
    • 实现错误处理
    • 添加日志

第 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 模式,关注点分明确
    • 依赖注入以提高可测试性
    • 异步/等待响应式用户界面
  2. 数据存取
    • 实体框架核心而不是 ADO.NET
    • 参数化查询以防止 SQL 注入
    • 数据访问的存储库模式
  3. 用户界面改进
    • WPF 样式和模板
    • 带验证的数据绑定
    • 正在加载异步操作的指标
  4. 代码质量
    • 用强打字代替变体类型
    • 正确的错误处理
    • 使用数据注释进行输入验证
  5. 可维护性
    • 有组织的项目结构
    • 可测试的组件
    • 现代 C# 语言功能

这种迁移方法为实现VB6库存应用程序的现代化提供了坚实的基础,同时保留了其核心功能并通过现代开发实践对其进行改进。

Experience Ninja's AI Assistant

Try for free today. Plans starting at $19/month.