mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 将所有小写的命名空间导入更正为首字母大写格式 - 统一 GFramework 框架的命名空间引用规范 - 修复 core、ecs、godot 等模块的命名空间导入错误 - 标准化文档示例代码中的 using 语句格式 - 确保所有文档中的命名空间引用保持一致性 - 更新 global using 语句以匹配正确的命名空间格式
786 lines
23 KiB
Markdown
786 lines
23 KiB
Markdown
---
|
||
title: 实现数据版本迁移
|
||
description: 学习如何实现数据版本迁移系统,处理不同版本间的数据升级
|
||
---
|
||
|
||
# 实现数据版本迁移
|
||
|
||
## 学习目标
|
||
|
||
完成本教程后,你将能够:
|
||
|
||
- 理解数据版本迁移的重要性和应用场景
|
||
- 定义版本化数据结构
|
||
- 实现数据迁移接口
|
||
- 注册和管理迁移策略
|
||
- 处理多版本连续升级
|
||
- 测试迁移流程的正确性
|
||
|
||
## 前置条件
|
||
|
||
- 已安装 GFramework.Game NuGet 包
|
||
- 了解 C# 基础语法和接口实现
|
||
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
|
||
- 了解[数据与存档系统](/zh-CN/game/data)
|
||
- 建议先完成[实现存档系统](/zh-CN/tutorials/save-system)教程
|
||
|
||
## 为什么需要数据迁移
|
||
|
||
在游戏开发过程中,数据结构经常会发生变化:
|
||
|
||
- **新增功能**:添加新的游戏系统需要新的数据字段
|
||
- **重构优化**:改进数据结构以提升性能或可维护性
|
||
- **修复问题**:修正早期设计的缺陷
|
||
- **平衡调整**:调整游戏数值和配置
|
||
|
||
数据迁移系统能够:
|
||
|
||
- 自动将旧版本数据升级到新版本
|
||
- 保证玩家存档的兼容性
|
||
- 避免数据丢失和游戏崩溃
|
||
- 提供平滑的版本过渡体验
|
||
|
||
## 步骤 1:定义版本化数据结构
|
||
|
||
首先,让我们定义一个支持版本控制的游戏数据结构。
|
||
|
||
```csharp
|
||
using GFramework.Game.Abstractions.Data;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
|
||
namespace MyGame.Data
|
||
{
|
||
/// <summary>
|
||
/// 玩家数据 - 版本 1(初始版本)
|
||
/// </summary>
|
||
public class PlayerSaveData : IVersionedData
|
||
{
|
||
// IVersionedData 接口要求的属性
|
||
public int Version { get; set; } = 1;
|
||
public DateTime LastModified { get; set; } = DateTime.Now;
|
||
|
||
// 基础数据
|
||
public string PlayerName { get; set; } = "Player";
|
||
public int Level { get; set; } = 1;
|
||
public int Gold { get; set; } = 0;
|
||
|
||
// 版本 1 的简单位置数据
|
||
public float PositionX { get; set; }
|
||
public float PositionY { get; set; }
|
||
}
|
||
}
|
||
```
|
||
|
||
**代码说明**:
|
||
|
||
- 实现 `IVersionedData` 接口以支持版本管理
|
||
- `Version` 属性标识当前数据版本(从 1 开始)
|
||
- `LastModified` 记录最后修改时间
|
||
- 初始版本使用简单的 X、Y 坐标表示位置
|
||
|
||
## 步骤 2:定义新版本数据结构
|
||
|
||
随着游戏开发,我们需要添加新功能,数据结构也需要升级。
|
||
|
||
```csharp
|
||
namespace MyGame.Data
|
||
{
|
||
/// <summary>
|
||
/// 玩家数据 - 版本 2(添加 Z 轴和经验值)
|
||
/// </summary>
|
||
public class PlayerSaveDataV2 : IVersionedData
|
||
{
|
||
public int Version { get; set; } = 2;
|
||
public DateTime LastModified { get; set; } = DateTime.Now;
|
||
|
||
// 基础数据
|
||
public string PlayerName { get; set; } = "Player";
|
||
public int Level { get; set; } = 1;
|
||
public int Gold { get; set; } = 0;
|
||
|
||
// 版本 2:添加 Z 轴支持 3D 游戏
|
||
public float PositionX { get; set; }
|
||
public float PositionY { get; set; }
|
||
public float PositionZ { get; set; } // 新增
|
||
|
||
// 版本 2:添加经验值系统
|
||
public int Experience { get; set; } // 新增
|
||
public int ExperienceToNextLevel { get; set; } = 100; // 新增
|
||
}
|
||
|
||
/// <summary>
|
||
/// 玩家数据 - 版本 3(重构为结构化数据)
|
||
/// </summary>
|
||
public class PlayerSaveDataV3 : IVersionedData
|
||
{
|
||
public int Version { get; set; } = 3;
|
||
public DateTime LastModified { get; set; } = DateTime.Now;
|
||
|
||
// 基础数据
|
||
public string PlayerName { get; set; } = "Player";
|
||
public int Level { get; set; } = 1;
|
||
public int Gold { get; set; } = 0;
|
||
|
||
// 版本 3:使用结构化的位置数据
|
||
public Vector3Data Position { get; set; } = new();
|
||
|
||
// 版本 3:使用结构化的经验值数据
|
||
public ExperienceData Experience { get; set; } = new();
|
||
|
||
// 版本 3:新增技能系统
|
||
public List<string> UnlockedSkills { get; set; } = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3D 位置数据
|
||
/// </summary>
|
||
public class Vector3Data
|
||
{
|
||
public float X { get; set; }
|
||
public float Y { get; set; }
|
||
public float Z { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 经验值数据
|
||
/// </summary>
|
||
public class ExperienceData
|
||
{
|
||
public int Current { get; set; }
|
||
public int ToNextLevel { get; set; } = 100;
|
||
}
|
||
}
|
||
```
|
||
|
||
**代码说明**:
|
||
|
||
- **版本 2**:添加 Z 轴坐标和经验值系统
|
||
- **版本 3**:重构为更清晰的结构化数据
|
||
- 每个版本的 `Version` 属性递增
|
||
- 保持向后兼容,新字段提供默认值
|
||
|
||
## 步骤 3:实现数据迁移器
|
||
|
||
创建迁移器来处理版本间的数据转换。
|
||
|
||
```csharp
|
||
using GFramework.Game.Abstractions.Setting;
|
||
using System;
|
||
|
||
namespace MyGame.Data.Migrations
|
||
{
|
||
/// <summary>
|
||
/// 从版本 1 迁移到版本 2
|
||
/// </summary>
|
||
public class PlayerDataMigration_V1_to_V2 : ISettingsMigration
|
||
{
|
||
public Type SettingsType => typeof(PlayerSaveData);
|
||
public int FromVersion => 1;
|
||
public int ToVersion => 2;
|
||
|
||
public ISettingsSection Migrate(ISettingsSection oldData)
|
||
{
|
||
if (oldData is not PlayerSaveData v1)
|
||
{
|
||
throw new ArgumentException($"Expected PlayerSaveData, got {oldData.GetType().Name}");
|
||
}
|
||
|
||
Console.WriteLine($"[迁移] 版本 1 -> 2: {v1.PlayerName}");
|
||
|
||
// 创建版本 2 数据
|
||
var v2 = new PlayerSaveDataV2
|
||
{
|
||
Version = 2,
|
||
LastModified = DateTime.Now,
|
||
|
||
// 复制现有数据
|
||
PlayerName = v1.PlayerName,
|
||
Level = v1.Level,
|
||
Gold = v1.Gold,
|
||
PositionX = v1.PositionX,
|
||
PositionY = v1.PositionY,
|
||
|
||
// 新字段:Z 轴默认为 0
|
||
PositionZ = 0f,
|
||
|
||
// 新字段:根据等级计算经验值
|
||
Experience = 0,
|
||
ExperienceToNextLevel = 100 * v1.Level
|
||
};
|
||
|
||
Console.WriteLine($" - 添加 Z 轴坐标: {v2.PositionZ}");
|
||
Console.WriteLine($" - 初始化经验值系统: {v2.Experience}/{v2.ExperienceToNextLevel}");
|
||
|
||
return v2;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从版本 2 迁移到版本 3
|
||
/// </summary>
|
||
public class PlayerDataMigration_V2_to_V3 : ISettingsMigration
|
||
{
|
||
public Type SettingsType => typeof(PlayerSaveDataV2);
|
||
public int FromVersion => 2;
|
||
public int ToVersion => 3;
|
||
|
||
public ISettingsSection Migrate(ISettingsSection oldData)
|
||
{
|
||
if (oldData is not PlayerSaveDataV2 v2)
|
||
{
|
||
throw new ArgumentException($"Expected PlayerSaveDataV2, got {oldData.GetType().Name}");
|
||
}
|
||
|
||
Console.WriteLine($"[迁移] 版本 2 -> 3: {v2.PlayerName}");
|
||
|
||
// 创建版本 3 数据
|
||
var v3 = new PlayerSaveDataV3
|
||
{
|
||
Version = 3,
|
||
LastModified = DateTime.Now,
|
||
|
||
// 复制基础数据
|
||
PlayerName = v2.PlayerName,
|
||
Level = v2.Level,
|
||
Gold = v2.Gold,
|
||
|
||
// 迁移位置数据到结构化格式
|
||
Position = new Vector3Data
|
||
{
|
||
X = v2.PositionX,
|
||
Y = v2.PositionY,
|
||
Z = v2.PositionZ
|
||
},
|
||
|
||
// 迁移经验值数据到结构化格式
|
||
Experience = new ExperienceData
|
||
{
|
||
Current = v2.Experience,
|
||
ToNextLevel = v2.ExperienceToNextLevel
|
||
},
|
||
|
||
// 新字段:根据等级解锁基础技能
|
||
UnlockedSkills = GenerateDefaultSkills(v2.Level)
|
||
};
|
||
|
||
Console.WriteLine($" - 重构位置数据: ({v3.Position.X}, {v3.Position.Y}, {v3.Position.Z})");
|
||
Console.WriteLine($" - 重构经验值数据: {v3.Experience.Current}/{v3.Experience.ToNextLevel}");
|
||
Console.WriteLine($" - 初始化技能系统: {v3.UnlockedSkills.Count} 个技能");
|
||
|
||
return v3;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据等级生成默认技能
|
||
/// </summary>
|
||
private List<string> GenerateDefaultSkills(int level)
|
||
{
|
||
var skills = new List<string> { "basic_attack" };
|
||
|
||
if (level >= 5)
|
||
skills.Add("power_strike");
|
||
|
||
if (level >= 10)
|
||
skills.Add("shield_block");
|
||
|
||
if (level >= 15)
|
||
skills.Add("ultimate_skill");
|
||
|
||
return skills;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**代码说明**:
|
||
|
||
- 实现 `ISettingsMigration` 接口
|
||
- `SettingsType` 指定要迁移的数据类型
|
||
- `FromVersion` 和 `ToVersion` 定义迁移的版本范围
|
||
- `Migrate` 方法执行实际的数据转换
|
||
- 为新字段提供合理的默认值或计算值
|
||
- 添加日志输出便于调试
|
||
|
||
## 步骤 4:注册迁移策略
|
||
|
||
创建迁移管理器来注册和执行迁移。
|
||
|
||
```csharp
|
||
using GFramework.Game.Abstractions.Setting;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
|
||
namespace MyGame.Data.Migrations
|
||
{
|
||
/// <summary>
|
||
/// 数据迁移管理器
|
||
/// </summary>
|
||
public class DataMigrationManager
|
||
{
|
||
private readonly Dictionary<(Type type, int from), ISettingsMigration> _migrations = new();
|
||
|
||
/// <summary>
|
||
/// 注册迁移器
|
||
/// </summary>
|
||
public void RegisterMigration(ISettingsMigration migration)
|
||
{
|
||
var key = (migration.SettingsType, migration.FromVersion);
|
||
|
||
if (_migrations.ContainsKey(key))
|
||
{
|
||
Console.WriteLine($"警告: 迁移器已存在 {migration.SettingsType.Name} " +
|
||
$"v{migration.FromVersion}->v{migration.ToVersion}");
|
||
return;
|
||
}
|
||
|
||
_migrations[key] = migration;
|
||
Console.WriteLine($"注册迁移器: {migration.SettingsType.Name} " +
|
||
$"v{migration.FromVersion} -> v{migration.ToVersion}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行迁移(支持跨多个版本)
|
||
/// </summary>
|
||
public ISettingsSection MigrateToLatest(ISettingsSection data, int targetVersion)
|
||
{
|
||
if (data is not IVersionedData versioned)
|
||
{
|
||
Console.WriteLine("数据不支持版本控制,跳过迁移");
|
||
return data;
|
||
}
|
||
|
||
var currentVersion = versioned.Version;
|
||
|
||
if (currentVersion == targetVersion)
|
||
{
|
||
Console.WriteLine($"数据已是最新版本 v{targetVersion}");
|
||
return data;
|
||
}
|
||
|
||
if (currentVersion > targetVersion)
|
||
{
|
||
Console.WriteLine($"警告: 数据版本 v{currentVersion} 高于目标版本 v{targetVersion}");
|
||
return data;
|
||
}
|
||
|
||
Console.WriteLine($"\n开始迁移: v{currentVersion} -> v{targetVersion}");
|
||
|
||
var current = data;
|
||
var currentVer = currentVersion;
|
||
|
||
// 逐步迁移到目标版本
|
||
while (currentVer < targetVersion)
|
||
{
|
||
var key = (current.GetType(), currentVer);
|
||
|
||
if (!_migrations.TryGetValue(key, out var migration))
|
||
{
|
||
Console.WriteLine($"错误: 找不到迁移器 {current.GetType().Name} v{currentVer}");
|
||
break;
|
||
}
|
||
|
||
current = migration.Migrate(current);
|
||
currentVer = migration.ToVersion;
|
||
Console.WriteLine($"迁移完成: v{currentVersion} -> v{currentVer}\n");
|
||
return current;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取迁移路径
|
||
/// </summary>
|
||
public List<string> GetMigrationPath(Type dataType, int fromVersion, int toVersion)
|
||
{
|
||
var path = new List<string>();
|
||
var currentVer = fromVersion;
|
||
var currentType = dataType;
|
||
|
||
while (currentVer < toVersion)
|
||
{
|
||
var key = (currentType, currentVer);
|
||
|
||
if (!_migrations.TryGetValue(key, out var migration))
|
||
{
|
||
path.Add($"v{currentVer} -> ? (缺失迁移器)");
|
||
break;
|
||
}
|
||
|
||
path.Add($"v{migration.FromVersion} -> v{migration.ToVersion}");
|
||
currentVer = migration.ToVersion;
|
||
currentType = migration.SettingsType;
|
||
}
|
||
|
||
return path;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**代码说明**:
|
||
|
||
- 使用字典存储迁移器,键为 (类型, 源版本)
|
||
- `RegisterMigration` 注册单个迁移器
|
||
- `MigrateToLatest` 自动执行多步迁移
|
||
- `GetMigrationPath` 显示迁移路径,便于调试
|
||
- 支持跨多个版本的连续迁移
|
||
|
||
## 步骤 5:测试迁移流程
|
||
|
||
创建完整的测试程序验证迁移功能。
|
||
|
||
```csharp
|
||
using MyGame.Data;
|
||
using MyGame.Data.Migrations;
|
||
using System;
|
||
|
||
namespace MyGame
|
||
{
|
||
class Program
|
||
{
|
||
static void Main(string[] args)
|
||
{
|
||
Console.WriteLine("=== 数据迁移系统测试 ===\n");
|
||
|
||
// 1. 创建迁移管理器
|
||
var migrationManager = new DataMigrationManager();
|
||
|
||
// 2. 注册所有迁移器
|
||
Console.WriteLine("--- 注册迁移器 ---");
|
||
migrationManager.RegisterMigration(new PlayerDataMigration_V1_to_V2());
|
||
migrationManager.RegisterMigration(new PlayerDataMigration_V2_to_V3());
|
||
Console.WriteLine();
|
||
|
||
// 3. 测试场景 1:从版本 1 迁移到版本 3
|
||
Console.WriteLine("--- 测试 1: V1 -> V3 迁移 ---");
|
||
var v1Data = new PlayerSaveData
|
||
{
|
||
Version = 1,
|
||
PlayerName = "老玩家",
|
||
Level = 12,
|
||
Gold = 5000,
|
||
PositionX = 100.5f,
|
||
PositionY = 200.3f
|
||
};
|
||
|
||
Console.WriteLine("原始数据 (V1):");
|
||
Console.WriteLine($" 玩家: {v1Data.PlayerName}");
|
||
Console.WriteLine($" 等级: {v1Data.Level}");
|
||
Console.WriteLine($" 金币: {v1Data.Gold}");
|
||
Console.WriteLine($" 位置: ({v1Data.PositionX}, {v1Data.PositionY})");
|
||
Console.WriteLine();
|
||
|
||
// 显示迁移路径
|
||
var path = migrationManager.GetMigrationPath(typeof(PlayerSaveData), 1, 3);
|
||
Console.WriteLine("迁移路径:");
|
||
foreach (var step in path)
|
||
{
|
||
Console.WriteLine($" {step}");
|
||
}
|
||
Console.WriteLine();
|
||
|
||
// 执行迁移
|
||
var v3Data = (PlayerSaveDataV3)migrationManager.MigrateToLatest(v1Data, 3);
|
||
|
||
Console.WriteLine("迁移后数据 (V3):");
|
||
Console.WriteLine($" 玩家: {v3Data.PlayerName}");
|
||
Console.WriteLine($" 等级: {v3Data.Level}");
|
||
Console.WriteLine($" 金币: {v3Data.Gold}");
|
||
Console.WriteLine($" 位置: ({v3Data.Position.X}, {v3Data.Position.Y}, {v3Data.Position.Z})");
|
||
Console.WriteLine($" 经验值: {v3Data.Experience.Current}/{v3Data.Experience.ToNextLevel}");
|
||
Console.WriteLine($" 技能: {string.Join(", ", v3Data.UnlockedSkills)}");
|
||
Console.WriteLine();
|
||
|
||
// 4. 测试场景 2:从版本 2 迁移到版本 3
|
||
Console.WriteLine("--- 测试 2: V2 -> V3 迁移 ---");
|
||
var v2Data = new PlayerSaveDataV2
|
||
{
|
||
Version = 2,
|
||
PlayerName = "中期玩家",
|
||
Level = 8,
|
||
Gold = 2000,
|
||
PositionX = 50.0f,
|
||
PositionY = 75.0f,
|
||
PositionZ = 10.0f,
|
||
Experience = 350,
|
||
ExperienceToNextLevel = 800
|
||
};
|
||
|
||
Console.WriteLine("原始数据 (V2):");
|
||
Console.WriteLine($" 玩家: {v2Data.PlayerName}");
|
||
Console.WriteLine($" 等级: {v2Data.Level}");
|
||
Console.WriteLine($" 位置: ({v2Data.PositionX}, {v2Data.PositionY}, {v2Data.PositionZ})");
|
||
Console.WriteLine($" 经验值: {v2Data.Experience}/{v2Data.ExperienceToNextLevel}");
|
||
Console.WriteLine();
|
||
|
||
var v3Data2 = (PlayerSaveDataV3)migrationManager.MigrateToLatest(v2Data, 3);
|
||
|
||
Console.WriteLine("迁移后数据 (V3):");
|
||
Console.WriteLine($" 玩家: {v3Data2.PlayerName}");
|
||
Console.WriteLine($" 等级: {v3Data2.Level}");
|
||
Console.WriteLine($" 位置: ({v3Data2.Position.X}, {v3Data2.Position.Y}, {v3Data2.Position.Z})");
|
||
Console.WriteLine($" 经验值: {v3Data2.Experience.Current}/{v3Data2.Experience.ToNextLevel}");
|
||
Console.WriteLine($" 技能: {string.Join(", ", v3Data2.UnlockedSkills)}");
|
||
Console.WriteLine();
|
||
|
||
// 5. 测试场景 3:已是最新版本
|
||
Console.WriteLine("--- 测试 3: 已是最新版本 ---");
|
||
var v3DataLatest = new PlayerSaveDataV3
|
||
{
|
||
Version = 3,
|
||
PlayerName = "新玩家",
|
||
Level = 1
|
||
};
|
||
|
||
migrationManager.MigrateToLatest(v3DataLatest, 3);
|
||
Console.WriteLine();
|
||
|
||
Console.WriteLine("=== 测试完成 ===");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**代码说明**:
|
||
|
||
- 创建不同版本的测试数据
|
||
- 测试单步迁移(V2 -> V3)
|
||
- 测试多步迁移(V1 -> V3)
|
||
- 测试已是最新版本的情况
|
||
- 显示迁移前后的数据对比
|
||
|
||
## 完整代码
|
||
|
||
所有代码文件已在上述步骤中提供。项目结构如下:
|
||
|
||
```
|
||
MyGame/
|
||
├── Data/
|
||
│ ├── PlayerSaveData.cs # 版本 1 数据结构
|
||
│ ├── PlayerSaveDataV2.cs # 版本 2 数据结构
|
||
│ ├── PlayerSaveDataV3.cs # 版本 3 数据结构
|
||
│ └── Migrations/
|
||
│ ├── PlayerDataMigration_V1_to_V2.cs
|
||
│ ├── PlayerDataMigration_V2_to_V3.cs
|
||
│ └── DataMigrationManager.cs
|
||
└── Program.cs
|
||
```
|
||
|
||
## 运行结果
|
||
|
||
运行程序后,你将看到类似以下的输出:
|
||
|
||
```
|
||
=== 数据迁移系统测试 ===
|
||
|
||
--- 注册迁移器 ---
|
||
注册迁移器: PlayerSaveData v1 -> v2
|
||
注册迁移器: PlayerSaveDataV2 v2 -> v3
|
||
|
||
--- 测试 1: V1 -> V3 迁移 ---
|
||
原始数据 (V1):
|
||
玩家: 老玩家
|
||
等级: 12
|
||
金币: 5000
|
||
位置: (100.5, 200.3)
|
||
|
||
迁移路径:
|
||
v1 -> v2
|
||
v2 -> v3
|
||
|
||
开始迁移: v1 -> v3
|
||
[迁移] 版本 1 -> 2: 老玩家
|
||
- 添加 Z 轴坐标: 0
|
||
- 初始化经验值系统: 0/1200
|
||
[迁移] 版本 2 -> 3: 老玩家
|
||
- 重构位置数据: (100.5, 200.3, 0)
|
||
- 重构经验值数据: 0/1200
|
||
- 初始化技能系统: 3 个技能
|
||
迁移完成: v1 -> v3
|
||
|
||
迁移后数据 (V3):
|
||
玩家: 老玩家
|
||
等级: 12
|
||
金币: 5000
|
||
位置: (100.5, 200.3, 0)
|
||
经验值: 0/1200
|
||
技能: basic_attack, power_strike, shield_block
|
||
|
||
--- 测试 2: V2 -> V3 迁移 ---
|
||
原始数据 (V2):
|
||
玩家: 中期玩家
|
||
等级: 8
|
||
金币: 2000
|
||
位置: (50, 75, 10)
|
||
经验值: 350/800
|
||
|
||
开始迁移: v2 -> v3
|
||
[迁移] 版本 2 -> 3: 中期玩家
|
||
- 重构位置数据: (50, 75, 10)
|
||
- 重构经验值数据: 350/800
|
||
- 初始化技能系统: 2 个技能
|
||
迁移完成: v2 -> v3
|
||
|
||
迁移后数据 (V3):
|
||
玩家: 中期玩家
|
||
等级: 8
|
||
位置: (50, 75, 10)
|
||
经验值: 350/800
|
||
技能: basic_attack, power_strike
|
||
|
||
--- 测试 3: 已是最新版本 ---
|
||
数据已是最新版本 v3
|
||
|
||
=== 测试完成 ===
|
||
```
|
||
|
||
**验证步骤**:
|
||
|
||
1. 迁移器成功注册
|
||
2. V1 数据正确迁移到 V3
|
||
3. V2 数据正确迁移到 V3
|
||
4. 新字段获得合理的默认值
|
||
5. 已是最新版本的数据不会重复迁移
|
||
6. 迁移路径清晰可追踪
|
||
|
||
## 下一步
|
||
|
||
恭喜!你已经实现了一个完整的数据版本迁移系统。接下来可以学习:
|
||
|
||
- [实现存档系统](/zh-CN/tutorials/save-system) - 结合存档系统使用迁移
|
||
- [Godot 完整项目搭建](/zh-CN/tutorials/godot-complete-project) - 在实际项目中应用
|
||
- [数据与存档系统](/zh-CN/game/data) - 深入了解数据系统
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 版本号管理
|
||
|
||
```csharp
|
||
// 使用常量管理版本号
|
||
public static class DataVersions
|
||
{
|
||
public const int PlayerData_V1 = 1;
|
||
public const int PlayerData_V2 = 2;
|
||
public const int PlayerData_V3 = 3;
|
||
public const int PlayerData_Latest = PlayerData_V3;
|
||
}
|
||
```
|
||
|
||
### 2. 迁移测试
|
||
|
||
```csharp
|
||
// 为每个迁移器编写单元测试
|
||
[Test]
|
||
public void TestMigration_V1_to_V2()
|
||
{
|
||
var v1 = new PlayerSaveData { Level = 10 };
|
||
var migration = new PlayerDataMigration_V1_to_V2();
|
||
var v2 = (PlayerSaveDataV2)migration.Migrate(v1);
|
||
|
||
Assert.AreEqual(10, v2.Level);
|
||
Assert.AreEqual(0, v2.PositionZ);
|
||
Assert.AreEqual(1000, v2.ExperienceToNextLevel);
|
||
}
|
||
```
|
||
|
||
### 3. 数据备份
|
||
|
||
```csharp
|
||
// 迁移前自动备份
|
||
public ISettingsSection MigrateWithBackup(ISettingsSection data)
|
||
{
|
||
// 备份原始数据
|
||
var backup = SerializeData(data);
|
||
SaveBackup(backup);
|
||
|
||
try
|
||
{
|
||
// 执行迁移
|
||
var migrated = MigrateToLatest(data, targetVersion);
|
||
return migrated;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 迁移失败,恢复备份
|
||
RestoreBackup(backup);
|
||
throw;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 迁移日志
|
||
|
||
```csharp
|
||
// 记录详细的迁移日志
|
||
public class MigrationLogger
|
||
{
|
||
public void LogMigration(string playerName, int from, int to)
|
||
{
|
||
var log = $"[{DateTime.Now}] {playerName}: v{from} -> v{to}";
|
||
File.AppendAllText("migration.log", log + "\n");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. 向后兼容
|
||
|
||
- 新版本保留所有旧字段
|
||
- 为新字段提供合理的默认值
|
||
- 避免删除或重命名字段
|
||
- 使用 `[Obsolete]` 标记废弃字段
|
||
|
||
### 6. 性能优化
|
||
|
||
```csharp
|
||
// 批量迁移优化
|
||
public async Task<List<ISettingsSection>> MigrateBatchAsync(
|
||
List<ISettingsSection> dataList,
|
||
int targetVersion)
|
||
{
|
||
var tasks = dataList.Select(data =>
|
||
Task.Run(() => MigrateToLatest(data, targetVersion)));
|
||
|
||
return (await Task.WhenAll(tasks)).ToList();
|
||
}
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### 1. 如何处理跨多个版本的迁移?
|
||
|
||
迁移管理器会自动按顺序应用所有必要的迁移。例如从 V1 到 V3,会先执行 V1->V2,再执行 V2->V3。
|
||
|
||
### 2. 迁移失败如何处理?
|
||
|
||
建议在迁移前备份原始数据,迁移失败时可以恢复。同时在迁移过程中添加详细的日志记录。
|
||
|
||
### 3. 如何处理不兼容的数据变更?
|
||
|
||
对于破坏性变更,建议:
|
||
|
||
- 提供数据转换工具
|
||
- 在迁移中添加数据验证
|
||
- 通知用户可能的数据丢失
|
||
- 提供回滚机制
|
||
|
||
### 4. 是否需要保留所有历史版本的数据结构?
|
||
|
||
建议保留,这样可以:
|
||
|
||
- 支持从任意旧版本迁移
|
||
- 便于调试和测试
|
||
- 作为文档记录数据演变
|
||
|
||
### 5. 如何测试迁移功能?
|
||
|
||
- 创建各个版本的测试数据
|
||
- 验证迁移后的数据完整性
|
||
- 测试迁移链的正确性
|
||
- 使用真实的历史数据进行测试
|
||
|
||
## 相关文档
|
||
|
||
- [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明
|
||
- [实现存档系统](/zh-CN/tutorials/save-system) - 存档系统教程
|
||
- [架构系统](/zh-CN/core/architecture) - 架构设计原则
|