修复运行错误

This commit is contained in:
zhengxuan.zhang
2026-05-11 16:15:19 +08:00
parent 368481a950
commit cfdfe330a5
5 changed files with 95 additions and 1 deletions
BIN
View File
Binary file not shown.
@@ -362,7 +362,7 @@ namespace XplorePlane.Services.Cnc
Height = resultImage.PixelHeight Height = resultImage.PixelHeight
}); });
nodeResult.Status = InspectionNodeStatus.Succeeded; nodeResult.Status = InspectionNodeStatus.Succeeded;
_mainViewportService?.SetManualImage(resultImage, $"CNC Node: {inspectionNode.Name}"); _mainViewportService?.SetCncResultImage(resultImage, $"CNC 节点结果:{inspectionNode.Name}");
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -642,6 +642,19 @@ WHERE run_id = @run_id";
}; };
} }
// Schema migration: columns added after initial release that may be missing in existing databases.
private static readonly (string Table, string Column, string Definition)[] MigrationColumns =
[
("inspection_runs", "status", "TEXT NOT NULL DEFAULT 'pending'"),
("inspection_runs", "workpiece_id", "TEXT NOT NULL DEFAULT ''"),
("inspection_runs", "serial_number", "TEXT NOT NULL DEFAULT ''"),
("inspection_runs", "source_image_path", "TEXT NOT NULL DEFAULT ''"),
("inspection_runs", "result_root_path", "TEXT NOT NULL DEFAULT ''"),
("inspection_runs", "node_count", "INTEGER NOT NULL DEFAULT 0"),
("inspection_node_results", "status", "TEXT NOT NULL DEFAULT 'Pending'"),
("inspection_node_results", "duration_ms", "INTEGER NOT NULL DEFAULT 0"),
];
private async Task EnsureInitializedAsync() private async Task EnsureInitializedAsync()
{ {
if (_initialized) if (_initialized)
@@ -650,6 +663,16 @@ WHERE run_id = @run_id";
} }
Directory.CreateDirectory(_baseDirectory); Directory.CreateDirectory(_baseDirectory);
// Step 1: Apply column migrations BEFORE running CreateTableSql.
// CreateTableSql contains CREATE INDEX statements that reference columns (e.g. "status")
// which may not exist in databases created by older versions of the app.
// Those index statements will fail with "no such column" even though they are
// guarded by IF NOT EXISTS, because SQLite validates column references at parse time.
// Running ALTER TABLE ADD COLUMN first ensures the columns exist before the index SQL runs.
await ApplySchemaMigrationsAsync().ConfigureAwait(false);
// Step 2: Create any tables / indexes that do not yet exist.
var result = await _db.ExecuteNonQueryAsync(CreateTableSql).ConfigureAwait(false); var result = await _db.ExecuteNonQueryAsync(CreateTableSql).ConfigureAwait(false);
if (!result.IsSuccess) if (!result.IsSuccess)
{ {
@@ -660,6 +683,49 @@ WHERE run_id = @run_id";
_initialized = true; _initialized = true;
} }
private async Task ApplySchemaMigrationsAsync()
{
foreach (var (table, column, definition) in MigrationColumns)
{
// Check if the table exists at all. If not, CreateTableSql will create it with all
// columns, so there is nothing to migrate.
var (tableExistResult, tableCount) = await _db.ExecuteScalarAsync<long>(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=@name",
new Dictionary<string, object> { ["name"] = table }).ConfigureAwait(false);
if (!tableExistResult.IsSuccess || tableCount == 0)
continue;
// Check whether the column already exists.
// pragma_table_info is a table-valued function and does not accept bound parameters,
// so the table name is inlined. All values in MigrationColumns are internal constants.
// ExecuteScalarAsync uses ExecuteScalarAsync() directly and does not go through
// DataTable, so it is not affected by the dflt_value BLOB mapping issue.
var (colExistResult, colCount) = await _db.ExecuteScalarAsync<long>(
$"SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name='{column}'"
).ConfigureAwait(false);
if (!colExistResult.IsSuccess)
{
_logger.Warn("Schema migration: could not check column '{0}' in '{1}', skipping: {2}",
column, table, colExistResult.Message);
continue;
}
if (colCount == 0)
{
var alterResult = await _db.ExecuteNonQueryAsync(
$"ALTER TABLE {table} ADD COLUMN {column} {definition}").ConfigureAwait(false);
if (alterResult.IsSuccess)
_logger.Info("Schema migration: added column '{0}' to table '{1}'", column, table);
else
_logger.Warn("Schema migration: failed to add column '{0}' to '{1}': {2}",
column, table, alterResult.Message);
}
}
}
private async Task EnsureRunExistsAsync(Guid runId) private async Task EnsureRunExistsAsync(Guid runId)
{ {
var (result, value) = await _db.ExecuteScalarAsync<long>( var (result, value) = await _db.ExecuteScalarAsync<long>(
@@ -28,5 +28,11 @@ namespace XplorePlane.Services.MainViewport
/// CNC 开始运行时传入 true,结束时传入 false。 /// CNC 开始运行时传入 true,结束时传入 false。
/// </summary> /// </summary>
void SetCncRunning(bool isRunning); void SetCncRunning(bool isRunning);
/// <summary>
/// 由 CNC 执行引擎内部调用,将节点结果图像推送到 viewport。
/// 与 <see cref="SetManualImage"/> 不同,此方法在 CNC 运行期间不会被阻断。
/// </summary>
void SetCncResultImage(ImageSource image, string label);
} }
} }
@@ -186,6 +186,28 @@ namespace XplorePlane.Services.MainViewport
RaiseStateChanged(); RaiseStateChanged();
} }
public void SetCncResultImage(ImageSource image, string label)
{
if (image == null)
return;
var displayLabel = string.IsNullOrWhiteSpace(label) ? "CNC 节点结果" : label;
lock (_syncRoot)
{
// Intentionally bypasses the _isCncRunning guard so the node result
// is visible in the viewport immediately after each node completes.
_latestManualImage = image;
_latestManualInfo = displayLabel;
_currentSourceMode = MainViewportSourceMode.ManualImage;
_currentDisplayImage = _latestManualImage;
_currentDisplayInfo = _latestManualInfo;
}
_logger.Info("[图像链路] MainViewportService.SetCncResultImage:已推送 CNC 节点结果图像 {Label}", displayLabel);
RaiseStateChanged();
}
public void SetManualImage(ImageSource image, string filePath) public void SetManualImage(ImageSource image, string filePath)
{ {
if (image == null) if (image == null)