VB.NET DataTable – Create, Fill, Filter with Examples (2026)
The VB.NET DataTable stores tabular data in memory with typed columns, row-level access, and built-in filtering. It is the backbone of data binding in WinForms and the bridge between databases and UI controls like the DataGridView. This guide covers creation, filtering, sorting, database loading, and the most common mistakes.
Inhaltsverzeichnis
- 1 What is a DataTable and when to use it
- 2 Creating a DataTable with columns
- 3 Adding rows
- 4 Reading and accessing data
- 5 Filtering with Select and DataView
- 6 Sorting rows
- 7 Updating and deleting rows
- 8 Binding a DataTable to a DataGridView
- 9 Loading data from a database
- 10 Converting between DataTable and List
- 11 Common pitfalls
- 12 FAQ
- 13 Wrapping up
What is a DataTable and when to use it
A DataTable lives in the System.Data namespace and represents a single table of in-memory data. Think of it as a spreadsheet: it has columns with names and types, and rows that hold the actual values.
Use a DataTable when:
- You need to bind tabular data to a DataGridView or other WinForms controls
- You work with ADO.NET and want to fill a table from a database query
- You need built-in filtering and sorting without writing your own logic
- You process CSV or Excel imports where rows and columns map naturally
If you only need an ordered list of objects, a VB.NET List(Of T) is simpler and more type-safe. If you need key-value lookups, use a Dictionary. DataTable shines when you need a flexible, schema-based structure with filtering, sorting, and UI binding built in.
Creating a DataTable with columns
Every DataTable starts with a schema: the columns that define what data each row can hold.
Dim table As New DataTable("Contacts")
' Add columns with name and type
table.Columns.Add("Id", GetType(Integer))
table.Columns.Add("Name", GetType(String))
table.Columns.Add("Email", GetType(String))
table.Columns.Add("Age", GetType(Integer))
' Set a primary key
table.PrimaryKey = {table.Columns("Id")}
Every column has a DataType (Integer, String, DateTime, Decimal, Boolean, etc.) and an optional ColumnName. The primary key enables fast lookups with Rows.Find() and prevents duplicate entries.
Column properties
' Auto-increment column
Dim idCol = table.Columns("Id")
idCol.AutoIncrement = True
idCol.AutoIncrementSeed = 1
idCol.AutoIncrementStep = 1
' Default value
table.Columns("Age").DefaultValue = 0
' Disallow nulls
table.Columns("Name").AllowDBNull = False
Adding rows
' Method 1: NewRow + field assignment
Dim row As DataRow = table.NewRow()
row("Name") = "Alice"
row("Email") = "alice@example.com"
row("Age") = 30
table.Rows.Add(row)
' Method 2: Add values directly (column order)
table.Rows.Add(Nothing, "Bob", "bob@example.com", 25)
' Method 3: Add from an Object array
Dim values() As Object = {Nothing, "Clara", "clara@example.com", 35}
table.Rows.Add(values)
When using AutoIncrement on the Id column, pass Nothing for that field. The DataTable assigns the next value automatically.
Reading and accessing data
' Access a specific row by index
Dim firstRow As DataRow = table.Rows(0)
Dim name As String = CStr(firstRow("Name"))
' Iterate all rows
For Each row As DataRow In table.Rows
Console.WriteLine($"{row("Id")}: {row("Name")} ({row("Email")})")
Next
' Find a row by primary key
Dim found As DataRow = table.Rows.Find(2)
If found IsNot Nothing Then
Console.WriteLine($"Found: {found("Name")}")
End If
' Total row count
Dim count As Integer = table.Rows.Count
Fields return Object, so you need to cast them (CStr, CInt, CDec, etc.) or use the typed Field(Of T) extension method: row.Field(Of String)("Name"). The Field method also handles DBNull safely for nullable types.
Filtering with Select and DataView
DataTable has two ways to filter data: the Select() method for quick queries, and DataView for persistent, bindable filters.
DataTable.Select()
' Filter by condition
Dim olderThan25() As DataRow = table.Select("Age > 25")
' Filter + sort
Dim sorted() As DataRow = table.Select("Age > 25", "Name ASC")
' Multiple conditions
Dim specific() As DataRow = table.Select("Age > 20 AND Name LIKE 'A%'")
' Process results
For Each row In olderThan25
Console.WriteLine(row("Name"))
Next
The filter expression uses a SQL-like syntax: =, <>, >, <, LIKE, AND, OR, IN, IS NULL. The sort expression takes a column name followed by ASC or DESC.
DataView for bindable filtering
A DataView wraps a DataTable and provides a filtered, sorted view that you can bind directly to UI controls:
Dim view As New DataView(table) view.RowFilter = "Age >= 30" view.Sort = "Name ASC" ' Bind to DataGridView DataGridView1.DataSource = view ' Change the filter at runtime (UI updates automatically) view.RowFilter = "Name LIKE 'B%'"
DataView is the better choice for DataGridView filter functionality because it updates the bound control automatically when you change RowFilter or Sort.
Sorting rows
DataTable itself has no Sort() method. You sort through Select() or DataView:
' Sort with Select (returns DataRow array)
Dim sortedRows() As DataRow = table.Select("", "Age DESC")
' Sort with DataView (bindable)
Dim view As New DataView(table)
view.Sort = "Name ASC, Age DESC"
' Sort with LINQ (returns new collection)
Dim linqSorted = From row In table.AsEnumerable()
Order By row.Field(Of String)("Name")
Select row
For DataGridView binding, use DataView. For processing rows in code, the Select() method or LINQ both work well.
Updating and deleting rows
' Update a field
table.Rows(0)("Email") = "alice.new@example.com"
' Update by primary key
Dim target As DataRow = table.Rows.Find(2)
If target IsNot Nothing Then
target("Age") = 26
End If
' Delete a row (marks it for deletion)
table.Rows(0).Delete()
' Remove a row immediately
table.Rows.Remove(table.Rows(0))
' Accept all pending changes
table.AcceptChanges()
There is an important difference: Delete() marks the row with RowState = Deleted but keeps it in the table until AcceptChanges() is called. Remove() deletes the row immediately. Use Delete() when you need change tracking (e.g. syncing back to a database with a DataAdapter).
Binding a DataTable to a DataGridView
One of the most common uses of DataTable is displaying data in a DataGridView:
' Direct binding DataGridView1.DataSource = table ' Binding via DataView (with filter + sort) Dim view As New DataView(table) view.RowFilter = "Age > 25" view.Sort = "Name ASC" DataGridView1.DataSource = view
The DataGridView auto-generates columns from the DataTable schema. Changes in the grid are written back to the DataTable automatically. If you want a read-only grid, set DataGridView1.ReadOnly = True.
Refreshing the grid after data changes
' Add a row programmatically table.Rows.Add(Nothing, "David", "david@example.com", 28) ' Grid updates automatically when bound to the DataTable ' No manual refresh needed ' If you replace the entire DataTable, re-bind it DataGridView1.DataSource = Nothing DataGridView1.DataSource = newTable
Loading data from a database
DataTable works with any ADO.NET provider. Here is an example using SQLite:
Imports System.Data.SQLite
Dim table As New DataTable()
Using connection As New SQLiteConnection("Data Source=app.db")
connection.Open()
Using command As New SQLiteCommand("SELECT * FROM Contacts", connection)
Using reader As SQLiteDataReader = command.ExecuteReader()
table.Load(reader)
End Using
End Using
End Using
' table now contains all rows from the Contacts table
DataGridView1.DataSource = table
The Load() method reads all rows from a DataReader and creates the columns automatically based on the query result. This works with SQL Server, MySQL, PostgreSQL, or any other ADO.NET provider.
Alternative: using a DataAdapter
Dim table As New DataTable()
Using connection As New SQLiteConnection("Data Source=app.db")
Using adapter As New SQLiteDataAdapter("SELECT * FROM Contacts", connection)
adapter.Fill(table)
End Using
End Using
The DataAdapter approach is shorter and also supports writing changes back to the database with adapter.Update(table). For simpler, strongly-typed database access without DataTable, consider Dapper as an alternative.
Converting between DataTable and List
Sometimes you need to switch between a DataTable and a typed List(Of T):
DataTable to List(Of T)
Public Class Contact
Public Property Id As Integer
Public Property Name As String
Public Property Email As String
Public Property Age As Integer
End Class
' Convert DataTable rows to a typed List
Dim contacts = table.AsEnumerable().Select(Function(row)
Return New Contact With {
.Id = row.Field(Of Integer)("Id"),
.Name = row.Field(Of String)("Name"),
.Email = row.Field(Of String)("Email"),
.Age = row.Field(Of Integer)("Age")
}
End Function).ToList()
List(Of T) to DataTable
Public Function ToDataTable(contacts As List(Of Contact)) As DataTable
Dim table As New DataTable()
table.Columns.Add("Id", GetType(Integer))
table.Columns.Add("Name", GetType(String))
table.Columns.Add("Email", GetType(String))
table.Columns.Add("Age", GetType(Integer))
For Each c In contacts
table.Rows.Add(c.Id, c.Name, c.Email, c.Age)
Next
Return table
End Function
The LINQ approach with AsEnumerable() requires a reference to System.Data.DataSetExtensions. In modern .NET (5+), this is included by default.
Common pitfalls
Ignoring DBNull
DataTable fields can be DBNull.Value instead of Nothing. A direct cast will throw an InvalidCastException:
' BAD - throws InvalidCastException if Email is DBNull
Dim email As String = CStr(row("Email"))
' GOOD - check for DBNull first
Dim email As String = If(row.IsNull("Email"), "", CStr(row("Email")))
' BEST - use the typed Field extension
Dim email As String = row.Field(Of String)("Email") ' returns Nothing for DBNull
Deleting rows in a For Each loop
Just like with a List, modifying a DataTable while iterating throws an exception. Use a backward For loop or collect rows first:
' BAD - modifying during iteration
For Each row As DataRow In table.Rows
If CInt(row("Age")) < 18 Then
row.Delete()
End If
Next
' GOOD - iterate backwards
For i As Integer = table.Rows.Count - 1 To 0 Step -1
If CInt(table.Rows(i)("Age")) < 18 Then
table.Rows(i).Delete()
End If
Next
table.AcceptChanges()
Forgetting AcceptChanges
When you call Delete() on a row, it is only marked as deleted. The row still shows up in table.Rows.Count and can cause unexpected behavior. Call AcceptChanges() after a batch of deletions, or use Remove() for immediate removal.
FAQ
Create it with Dim table As New DataTable(), then add columns with table.Columns.Add("Name", GetType(String)) and rows with table.Rows.Add(values). Each column needs a name and a data type.
Use table.Select("Age > 25") for quick queries that return a DataRow array. For persistent, bindable filters, create a DataView with view.RowFilter = "Age > 25" and bind it to your DataGridView.
Set DataGridView1.DataSource = table. The grid auto-generates columns from the table schema. For filtered views, bind a DataView instead: DataGridView1.DataSource = New DataView(table).
A DataTable stores untyped rows with columns defined at runtime, supports SQL-like filtering, and binds natively to DataGridView. A List(Of T) holds strongly-typed objects, is simpler to use, but requires manual filtering with LINQ.
Use table.Load(reader) with any ADO.NET DataReader, or use a DataAdapter with adapter.Fill(table). Both approaches create columns automatically from the query result.
Wrapping up
The DataTable is the right choice when you need schema-based, filterable tabular data with native DataGridView binding. Use Select() for quick queries, DataView for live-filtered UI binding, and Field(Of T) to avoid DBNull issues. For simpler use cases with typed objects, a List(Of T) is often the better fit. For database access without DataTable, take a look at Dapper.