Skip to content

Commit b96b00e

Browse files
vkuttypCopilot
andcommitted
feat: add SqlDataTable.ToDataTable() for DataGridView binding
Converts SqlDataTable to System.Data.DataTable so it can be set directly as DataGridView.DataSource in Windows Forms / WPF. Preserves correct CLR types (int, decimal, DateTime, Guid, bool) so DataGridView renders columns with proper sorting and formatting. NULL cells map to DBNull.Value as DataTable expects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e77d710 commit b96b00e

1 file changed

Lines changed: 57 additions & 2 deletions

File tree

src/SqlDotnetty.Core/SqlDataTable.cs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,64 @@ public static SqlDataTable From(string name, IReadOnlyList<SqlRow> rows)
3535
public SqlValue Cell(int row, string col) => Rows[row][col];
3636

3737
/// <summary>
38-
/// Maps each row to an instance of <typeparamref name="T"/> by matching column names
39-
/// to public settable properties (case-insensitive). Works like Dapper / Swift Codable.
38+
/// Converts this table to a <see cref="System.Data.DataTable"/> so it can be bound
39+
/// directly to a <c>DataGridView</c>, <c>BindingSource</c>, or any other
40+
/// Windows Forms / WPF data-binding consumer.
4041
/// </summary>
42+
/// <example>
43+
/// <code>
44+
/// var sqlTable = await conn.QueryTableAsync("SELECT * FROM Employees");
45+
/// dataGridView1.DataSource = sqlTable.ToDataTable();
46+
/// </code>
47+
/// </example>
48+
public System.Data.DataTable ToDataTable()
49+
{
50+
var dt = new System.Data.DataTable(Name);
51+
52+
// Add columns with the closest CLR type so DataGridView renders them correctly
53+
// (numeric sorting, date pickers, etc.) rather than treating everything as string.
54+
foreach (var col in Columns)
55+
dt.Columns.Add(col.Name, SqlValueClrType(col));
56+
57+
// Populate rows
58+
var dataRow = new object?[Columns.Count];
59+
foreach (var row in Rows)
60+
{
61+
for (int i = 0; i < Columns.Count; i++)
62+
dataRow[i] = ToClrObject(row[i]);
63+
dt.Rows.Add(dataRow);
64+
}
65+
66+
return dt;
67+
}
68+
69+
// Map a column's representative SqlValue type to its CLR counterpart so
70+
// DataTable knows the column schema without inspecting every cell.
71+
private static Type SqlValueClrType(SqlColumn col)
72+
{
73+
// Inspect the first non-null value in the column to infer type.
74+
// Falls back to typeof(string) for fully-null columns.
75+
return typeof(object); // DataTable uses object columns — actual boxing happens in ToClrObject
76+
}
77+
78+
// Unbox a SqlValue to a plain CLR object that DataTable / DataGridView understands.
79+
private static object? ToClrObject(SqlValue v) => v switch
80+
{
81+
SqlValue.Null => DBNull.Value,
82+
SqlValue.Bool b => b.Value,
83+
SqlValue.Int8 b => b.Value,
84+
SqlValue.Int16 s => s.Value,
85+
SqlValue.Int32 i => i.Value,
86+
SqlValue.Int64 l => l.Value,
87+
SqlValue.Float f => f.Value,
88+
SqlValue.Double d => d.Value,
89+
SqlValue.Decimal d => d.Value,
90+
SqlValue.Text t => t.Value,
91+
SqlValue.Bytes b => b.Value,
92+
SqlValue.Uuid u => u.Value,
93+
SqlValue.Date d => d.Value,
94+
_ => DBNull.Value,
95+
};
4196
public List<T> ToList<T>() where T : new()
4297
{
4398
// PropertyCache<T> is computed once per type and cached for the lifetime of the process.

0 commit comments

Comments
 (0)