검증 실행 결과에 출력 행이 안 보이던 문제
결과를 ExpandoObject 목록으로 DataGrid 에 넘기고 있었다. DataGrid 의 AutoGenerateColumns 는
항목 <b>타입</b>의 속성을 반사해 열을 만드는데 ExpandoObject 에는 그런 속성이 없다 —
그래서 요약("성공 — 컬럼 2개, 표본 3행")은 맞게 뜨는데 표는 비어 있었다.
DataTable 로 바꿨다. DataGrid 가 직접 알아본다.
같은 컬럼명이 두 번 나오면(별칭 없는 조인에서 흔하다) DataTable 이 예외를 내므로 뒤에 번호를 붙인다.
■ 0행과 실패를 구분한다
구문은 맞는데 조건에 걸리는 데이터가 없을 수 있다. 검증 실행은 치환 변수를 빈 값으로 바꿔
돌리므로(런타임과 같은 방식) 조건이 빡빡하면 0행이 정상이다.
그 경우 표를 감추고 "구문은 정상입니다 — 치환 변수를 빈 값으로 실행하므로 조건에 걸리는 행이
없을 수 있습니다" 를 덧붙인다. 빈 표를 보여 주면 실패한 것처럼 읽힌다.
결과 영역을 190 → 260px 로 키웠다.
■ 이 종류를 다시 놓치지 않도록
--query-popup 진단에 3건을 더 넣었다: 결과 창이 열리는가 · 표에 열이 생기는가 · 행이 채워지는가.
요약만 검사했다면 이번 결함을 그대로 통과시켰을 것이다. 현재 12/12.
--dialog-shots 에도 검증 실행을 마친 상태(03b-query-trial)를 추가해 눈으로도 남는다.
회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업·정렬·결과표 점검 12/12,
검증 실행 점검 10/10, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3e26af8739
commit
f447ae742b
@@ -730,6 +730,20 @@ public static class DbSmoke
|
|||||||
!lastCharRect.IsEmpty && !caretRect.IsEmpty && caretRect.X >= lastCharRect.X,
|
!lastCharRect.IsEmpty && !caretRect.IsEmpty && caretRect.X >= lastCharRect.X,
|
||||||
$"글자 {lastCharRect.X:F1} / 캐럿 {caretRect.X:F1}");
|
$"글자 {lastCharRect.X:F1} / 캐럿 {caretRect.X:F1}");
|
||||||
|
|
||||||
|
// ⑨ 검증 실행 결과가 표로 실제로 채워지는가.
|
||||||
|
// 요약만 보고 넘어가면 '컬럼 2개, 표본 3행'이라 적혀 있는데 표는 비어 있는 상태를 놓친다
|
||||||
|
// (ExpandoObject 를 넘기면 DataGrid 가 열을 못 만든다).
|
||||||
|
var grid = (System.Windows.Controls.DataGrid)window.FindName("TrialGrid")!;
|
||||||
|
var pane = (System.Windows.FrameworkElement)window.FindName("TrialPane")!;
|
||||||
|
Type("SELECT ShtCod, ShtKorNam FROM E_ShtMst WHERE ROWNUM <= 3");
|
||||||
|
window.RunTrialForSmoke();
|
||||||
|
DrainDispatcher();
|
||||||
|
grid.UpdateLayout();
|
||||||
|
|
||||||
|
Check("검증 결과 창이 열린다", pane.Visibility == System.Windows.Visibility.Visible);
|
||||||
|
Check("결과 표에 열이 생긴다", grid.Columns.Count >= 2, $"열 {grid.Columns.Count}개");
|
||||||
|
Check("결과 표에 행이 채워진다", grid.Items.Count >= 1, $"행 {grid.Items.Count}개");
|
||||||
|
|
||||||
window.Close();
|
window.Close();
|
||||||
DrainDispatcher();
|
DrainDispatcher();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ public static class DialogShots
|
|||||||
("02-sheet-history", () => new Views.SheetHistoryDialogView("S999", "표본 서식", SampleVersions())),
|
("02-sheet-history", () => new Views.SheetHistoryDialogView("S999", "표본 서식", SampleVersions())),
|
||||||
("03-query-editor", () => NewQueryEditor(designer,
|
("03-query-editor", () => NewQueryEditor(designer,
|
||||||
"SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <<M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum>>")),
|
"SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <<M.CMM.HISOperatingInfo.bzPatientInfo.ChtNum>>")),
|
||||||
|
("03b-query-trial", () => NewQueryEditorWithTrial(designer)),
|
||||||
// 실제 카탈로그(384종)와 실제 제안 순위로 찍는다 — 표본 몇 개로는 분류·건수가 안 보인다
|
// 실제 카탈로그(384종)와 실제 제안 순위로 찍는다 — 표본 몇 개로는 분류·건수가 안 보인다
|
||||||
("04-tag-picker", () => new Views.TagPickerDialogView(
|
("04-tag-picker", () => new Views.TagPickerDialogView(
|
||||||
"데이터 태그 선택 — 자동 채움 원천(bzDataInterface)",
|
"데이터 태그 선택 — 자동 채움 원천(bzDataInterface)",
|
||||||
@@ -315,6 +316,19 @@ public static class DialogShots
|
|||||||
private static Window NewQueryEditor(DesignerViewModel designer, string sql)
|
private static Window NewQueryEditor(DesignerViewModel designer, string sql)
|
||||||
=> new Views.QueryEditorWindow("데이터소스", sql, controlNames: designer.ControlNames());
|
=> new Views.QueryEditorWindow("데이터소스", sql, controlNames: designer.ControlNames());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 검증 실행까지 마친 상태 — 결과 표가 실제로 채워지는지 눈으로 확인할 유일한 경로다.
|
||||||
|
/// DB 가 없으면 실패 메시지가 찍히고, 그것도 봐야 하는 화면이다.
|
||||||
|
/// </summary>
|
||||||
|
private static Window NewQueryEditorWithTrial(DesignerViewModel designer)
|
||||||
|
{
|
||||||
|
var window = new Views.QueryEditorWindow("데이터소스",
|
||||||
|
"SELECT ShtCod, ShtKorNam, ShtTyp FROM E_ShtMst WHERE ROWNUM <= 5",
|
||||||
|
controlNames: designer.ControlNames());
|
||||||
|
window.RunTrialForSmoke();
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
private static DesignerViewModel SampleDesigner()
|
private static DesignerViewModel SampleDesigner()
|
||||||
{
|
{
|
||||||
var document = new FormDocument { FormId = "S999", Title = "표본 서식" };
|
var document = new FormDocument { FormId = "S999", Title = "표본 서식" };
|
||||||
|
|||||||
@@ -154,7 +154,7 @@
|
|||||||
|
|
||||||
<!-- 검증 결과 -->
|
<!-- 검증 결과 -->
|
||||||
<Border x:Name="TrialPane" DockPanel.Dock="Bottom" Visibility="Collapsed" Margin="0,8,0,0"
|
<Border x:Name="TrialPane" DockPanel.Dock="Bottom" Visibility="Collapsed" Margin="0,8,0,0"
|
||||||
MaxHeight="190" CornerRadius="8" BorderThickness="1"
|
MaxHeight="260" CornerRadius="8" BorderThickness="1"
|
||||||
BorderBrush="{DynamicResource B.InputBorder}" Background="{DynamicResource B.Input}">
|
BorderBrush="{DynamicResource B.InputBorder}" Background="{DynamicResource B.Input}">
|
||||||
<DockPanel Margin="10,8">
|
<DockPanel Margin="10,8">
|
||||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
<TextBlock x:Name="TrialSummary" FontSize="11.5" TextWrapping="Wrap"/>
|
<TextBlock x:Name="TrialSummary" FontSize="11.5" TextWrapping="Wrap"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<DataGrid x:Name="TrialGrid" AutoGenerateColumns="True" IsReadOnly="True"
|
<DataGrid x:Name="TrialGrid" AutoGenerateColumns="True" IsReadOnly="True"
|
||||||
HeadersVisibility="Column" FontSize="11.5" MaxHeight="140"
|
HeadersVisibility="Column" FontSize="11.5" MaxHeight="200"
|
||||||
GridLinesVisibility="Horizontal" Background="Transparent"
|
GridLinesVisibility="Horizontal" Background="Transparent"
|
||||||
AutomationProperties.Name="검증 결과 표본"/>
|
AutomationProperties.Name="검증 결과 표본"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ public partial class QueryEditorWindow : Window
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void RefreshCompletionForSmoke() => UpdateCompletion();
|
internal void RefreshCompletionForSmoke() => UpdateCompletion();
|
||||||
|
|
||||||
|
/// <summary>진단(--query-popup)에서 검증 실행을 부르는 통로 — 결과 표가 실제로 차는지 본다</summary>
|
||||||
|
internal void RunTrialForSmoke() => OnTrial(this, new RoutedEventArgs());
|
||||||
|
|
||||||
/// <summary>커서 위치를 보고 목록을 띄우거나 닫는다</summary>
|
/// <summary>커서 위치를 보고 목록을 띄우거나 닫는다</summary>
|
||||||
private void UpdateCompletion()
|
private void UpdateCompletion()
|
||||||
{
|
{
|
||||||
@@ -399,9 +402,13 @@ public partial class QueryEditorWindow : Window
|
|||||||
if (result.Ok)
|
if (result.Ok)
|
||||||
{
|
{
|
||||||
TrialSummary.Foreground = (System.Windows.Media.Brush)FindResource("B.Ink");
|
TrialSummary.Foreground = (System.Windows.Media.Brush)FindResource("B.Ink");
|
||||||
TrialSummary.Text = result.Summary;
|
// 행이 0인 것과 실패는 다르다 — 구문은 맞는데 조건에 걸리는 데이터가 없을 뿐이다.
|
||||||
|
// 치환 변수를 빈 값으로 바꿔 실행하므로 조건이 빡빡하면 0행이 정상이다.
|
||||||
|
TrialSummary.Text = result.Rows.Count == 0
|
||||||
|
? result.Summary + " — 구문은 정상입니다. 치환 변수를 빈 값으로 실행하므로 조건에 걸리는 행이 없을 수 있습니다."
|
||||||
|
: result.Summary;
|
||||||
TrialGrid.ItemsSource = ToGridRows(result);
|
TrialGrid.ItemsSource = ToGridRows(result);
|
||||||
TrialGrid.Visibility = Visibility.Visible;
|
TrialGrid.Visibility = result.Rows.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -412,27 +419,36 @@ public partial class QueryEditorWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>DataGrid 는 속성으로 열을 만든다 — 컬럼명이 동적이라 사전 행으로 넘긴다</summary>
|
/// <summary>
|
||||||
private static List<System.Dynamic.ExpandoObject> ToGridRows(SheetMe.Data.Stores.QueryTrialResult result)
|
/// 결과를 DataGrid 가 열까지 만들어 주는 형태로 옮긴다.
|
||||||
|
///
|
||||||
|
/// <b>ExpandoObject 로는 안 된다.</b> DataGrid 의 AutoGenerateColumns 는 항목 <i>타입</i>의
|
||||||
|
/// 속성을 반사해 열을 만드는데 ExpandoObject 에는 그런 속성이 없다 —
|
||||||
|
/// 그래서 요약만 뜨고 표는 비어 보였다. DataTable 은 DataGrid 가 직접 알아본다.
|
||||||
|
/// </summary>
|
||||||
|
private static System.Data.DataView ToGridRows(SheetMe.Data.Stores.QueryTrialResult result)
|
||||||
{
|
{
|
||||||
var rows = new List<System.Dynamic.ExpandoObject>();
|
var table = new System.Data.DataTable();
|
||||||
|
for (var i = 0; i < result.Columns.Count; i++)
|
||||||
|
{
|
||||||
|
// 같은 이름이 두 번 나오면 DataTable 이 예외를 낸다(별칭 없는 조인에서 흔하다)
|
||||||
|
var name = result.Columns[i];
|
||||||
|
if (table.Columns.Contains(name))
|
||||||
|
{
|
||||||
|
name = $"{name}_{i}";
|
||||||
|
}
|
||||||
|
table.Columns.Add(name, typeof(string));
|
||||||
|
}
|
||||||
foreach (var values in result.Rows)
|
foreach (var values in result.Rows)
|
||||||
{
|
{
|
||||||
var row = new System.Dynamic.ExpandoObject();
|
var row = table.NewRow();
|
||||||
var bag = (IDictionary<string, object?>)row;
|
for (var i = 0; i < table.Columns.Count && i < values.Length; i++)
|
||||||
for (var i = 0; i < result.Columns.Count && i < values.Length; i++)
|
|
||||||
{
|
{
|
||||||
// 같은 이름이 두 번 나오면 DataGrid 가 열을 하나만 만든다 — 뒤에 번호를 붙인다
|
row[i] = values[i];
|
||||||
var name = result.Columns[i];
|
|
||||||
if (bag.ContainsKey(name))
|
|
||||||
{
|
|
||||||
name += "_" + i;
|
|
||||||
}
|
}
|
||||||
bag[name] = values[i];
|
table.Rows.Add(row);
|
||||||
}
|
}
|
||||||
rows.Add(row);
|
return table.DefaultView;
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCloseTrial(object sender, RoutedEventArgs e) => TrialPane.Visibility = Visibility.Collapsed;
|
private void OnCloseTrial(object sender, RoutedEventArgs e) => TrialPane.Visibility = Visibility.Collapsed;
|
||||||
|
|||||||
Reference in New Issue
Block a user