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.

Need .NET help?

Building more than a quick prototype?

I've been developing professionally in VB.NET and C# for over 17 years. From data layers to complete desktop applications, I can help.

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.

Planning a project?

Need a scalable .NET data layer?

From DataTables to complete application architecture: I design software that lasts. Let's talk about your project.

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.

Need expert support?

Looking for an experienced .NET developer?

I'll take on your project, from clean data layers to finished desktop applications. Just drop me a message.

FAQ

How do I create a DataTable in VB.NET?

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.

How do I filter a DataTable in VB.NET?

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.

How do I bind a DataTable to a 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).

What is the difference between DataTable and List(Of T)?

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.

How do I load database data into a DataTable?

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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Robert Skibbe
Datenschutz-Übersicht

Diese Website verwendet Cookies, damit wir dir die bestmögliche Benutzererfahrung bieten können. Cookie-Informationen werden in deinem Browser gespeichert und führen Funktionen aus, wie das Wiedererkennen von dir, wenn du auf unsere Website zurückkehrst, und hilft unserem Team zu verstehen, welche Abschnitte der Website für dich am interessantesten und nützlichsten sind.