VB.NET SQLite – Connect, Read & Write Data (2026)
SQLite is the easiest way to add a local database to your VB.NET application, no server, no installation, no configuration. In this guide I’ll show you step by step how to set up SQLite via NuGet, create tables, and perform all CRUD operations with parameterized queries. Full working example included. → Go to downloads.
Inhaltsverzeichnis
- 1 Why SQLite for your VB.NET application?
- 2 Installing the NuGet package
- 3 Creating a database and connecting
- 4 Creating tables
- 5 Inserting data
- 6 Reading data (SELECT)
- 7 Updating and deleting data
- 8 Complete example: Contact manager
- 9 Using transactions
- 10 Async/Await: keeping the UI responsive
- 11 Common pitfalls and solutions
- 12 FAQ
- 13 Wrapping up
- 14 Downloads
Why SQLite for your VB.NET application?
There are many database engines available, but SQLite stands out for local VB.NET applications. Here’s why:
- Zero setup – no server process, no installation, no admin rights needed
- Single file – the entire database lives in one
.dbfile you can ship with your app - Cross-platform – works on Windows, Linux, and macOS
- Lightweight – perfect for desktop apps, prototypes, embedded systems, and offline-first scenarios
- Reliable – used by Firefox, Chrome, Android, and millions of other applications
If you just need to persist settings or small amounts of data, you could also write a text file in VB.NET. But as soon as you need structured data, queries, or relationships, SQLite is the better choice.
Installing the NuGet package
There are two popular SQLite packages for .NET:
Microsoft.Data.Sqlite– Microsoft’s official, lightweight package (recommended for .NET 5+ / .NET Framework 4.6.1+)System.Data.SQLite– the older, full-featured package from the SQLite team (supports even .NET Framework 4.0)
In this article I’ll use Microsoft.Data.Sqlite because it’s lighter and the recommended path going forward. The API is almost identical, so switching later is straightforward.
Install via Package Manager Console
Open the Package Manager Console in Visual Studio (Tools → NuGet Package Manager → Package Manager Console) and run:
Install-Package Microsoft.Data.Sqlite
Alternatively, right-click your project in Solution Explorer → Manage NuGet Packages → search for Microsoft.Data.Sqlite → Install.
Add the Imports statement
At the top of your code file, add the following import:
Imports Microsoft.Data.Sqlite
Creating a database and connecting
A big advantage of SQLite: you don’t need to „create“ a database manually. If the file doesn’t exist yet, SQLite creates it automatically when you open a connection.
The connection string
The SQLite connection string is minimal. All you need is the path to the database file:
Dim connectionString As String = "Data Source=myapp.db"
This creates the file myapp.db in the application’s working directory. If you want to place the database next to your executable, you can use the VB.NET Application Path to build a reliable path:
Dim dbPath As String = Path.Combine(AppContext.BaseDirectory, "myapp.db")
Dim connectionString As String = $"Data Source={dbPath}"
Opening a connection
Always wrap your connection in a Using block. This ensures the connection is properly closed and disposed, even if an exception occurs:
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
' Your database operations here...
End Using ' connection is automatically closed
This is the basic pattern you’ll use for every database operation. The Using block is essential to avoid file locks and „database is locked“ errors (more on that later).
Creating tables
Before you can store data, you need at least one table. Use CREATE TABLE IF NOT EXISTS to safely run the command every time your app starts, without errors if the table already exists:
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "
CREATE TABLE IF NOT EXISTS Contacts (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Email TEXT,
Phone TEXT,
CreatedAt TEXT DEFAULT CURRENT_TIMESTAMP
)"
command.ExecuteNonQuery()
End Using
End Using
SQLite data types
SQLite uses a dynamic type system that’s different from SQL Server. The most common types are:
INTEGER– whole numbers (equivalent to Integer / Long in VB.NET)TEXT– strings (equivalent to String)REAL– floating point numbers (equivalent to Double)BLOB– binary data (equivalent to Byte())NULL– no value
A tip: SQLite doesn’t have a native DATETIME type. Store dates as TEXT in ISO 8601 format (e.g. 2026-04-07T14:30:00) or as INTEGER (Unix timestamp).
Inserting data
Now let’s insert a record into our Contacts table. Always use parameterized queries instead of string concatenation. This protects your app from SQL injection and handles special characters correctly:
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "INSERT INTO Contacts (Name, Email, Phone) VALUES (@name, @email, @phone)"
command.Parameters.AddWithValue("@name", "John Doe")
command.Parameters.AddWithValue("@email", "john@example.com")
command.Parameters.AddWithValue("@phone", "+49 123 456789")
command.ExecuteNonQuery()
End Using
End Using
Why parameterized queries matter
Never build SQL strings by concatenating user input directly. This is the #1 security mistake that leads to SQL injection:
' ❌ NEVER do this - SQL injection vulnerability!
command.CommandText = "INSERT INTO Contacts (Name) VALUES ('" & txtName.Text & "')"
' ✅ Always use parameters instead
command.CommandText = "INSERT INTO Contacts (Name) VALUES (@name)"
command.Parameters.AddWithValue("@name", txtName.Text)
Getting the last inserted ID
After inserting a row, you often want to know the auto-generated ID. Use last_insert_rowid() for that:
command.CommandText = "INSERT INTO Contacts (Name, Email) VALUES (@name, @email)"
command.Parameters.AddWithValue("@name", "Jane Doe")
command.Parameters.AddWithValue("@email", "jane@example.com")
command.ExecuteNonQuery()
command.CommandText = "SELECT last_insert_rowid()"
Dim newId As Long = CLng(command.ExecuteScalar())
Console.WriteLine($"New contact ID: {newId}")
Reading data (SELECT)
Use ExecuteReader() to query data and iterate over the results. Again, wrap everything in Using blocks:
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using command As New SqliteCommand("SELECT Id, Name, Email FROM Contacts", connection)
Using reader As SqliteDataReader = command.ExecuteReader()
While reader.Read()
Dim id As Long = reader.GetInt64(0)
Dim name As String = reader.GetString(1)
Dim email As String = If(reader.IsDBNull(2), "", reader.GetString(2))
Console.WriteLine($"[{id}] {name} - {email}")
End While
End Using
End Using
End Using
Filtering with WHERE
To search for specific records, use a parameterized WHERE clause:
Using command As New SqliteCommand(
"SELECT Id, Name, Email FROM Contacts WHERE Name LIKE @search",
connection)
command.Parameters.AddWithValue("@search", $"%{searchTerm}%")
Using reader As SqliteDataReader = command.ExecuteReader()
While reader.Read()
' Process results...
End While
End Using
End Using
If you’re building a search-as-you-type feature, consider debouncing the input so you don’t fire a query on every keystroke.
Updating and deleting data
UPDATE
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "UPDATE Contacts SET Email = @email WHERE Id = @id"
command.Parameters.AddWithValue("@email", "new-email@example.com")
command.Parameters.AddWithValue("@id", 1)
Dim rowsAffected As Integer = command.ExecuteNonQuery()
Console.WriteLine($"{rowsAffected} row(s) updated.")
End Using
End Using
DELETE
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using command As New SqliteCommand("DELETE FROM Contacts WHERE Id = @id", connection)
command.Parameters.AddWithValue("@id", 5)
Dim rowsAffected As Integer = command.ExecuteNonQuery()
Console.WriteLine($"{rowsAffected} row(s) deleted.")
End Using
End Using
Complete example: Contact manager
Let’s tie it all together. Here’s a complete helper class that wraps all CRUD operations for a Contacts table. You can use this as a starting point for your own projects:
Imports Microsoft.Data.Sqlite
Public Class ContactRepository
Private ReadOnly _connectionString As String
Public Sub New(dbPath As String)
_connectionString = $"Data Source={dbPath}"
End Sub
''' Ensures the Contacts table exists.
Public Sub InitializeDatabase()
Using connection As New SqliteConnection(_connectionString)
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "
CREATE TABLE IF NOT EXISTS Contacts (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Email TEXT,
Phone TEXT,
CreatedAt TEXT DEFAULT CURRENT_TIMESTAMP
)"
command.ExecuteNonQuery()
End Using
End Using
End Sub
''' Adds a new contact and returns the generated Id.
Public Function AddContact(name As String, email As String, phone As String) As Long
Using connection As New SqliteConnection(_connectionString)
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "
INSERT INTO Contacts (Name, Email, Phone)
VALUES (@name, @email, @phone)"
command.Parameters.AddWithValue("@name", name)
command.Parameters.AddWithValue("@email", CObj(email) ?? DBNull.Value)
command.Parameters.AddWithValue("@phone", CObj(phone) ?? DBNull.Value)
command.ExecuteNonQuery()
End Using
Using command As New SqliteCommand("SELECT last_insert_rowid()", connection)
Return CLng(command.ExecuteScalar())
End Using
End Using
End Function
''' Returns all contacts as a list of dictionaries.
Public Function GetAllContacts() As List(Of Dictionary(Of String, Object))
Dim contacts As New List(Of Dictionary(Of String, Object))
Using connection As New SqliteConnection(_connectionString)
connection.Open()
Using command As New SqliteCommand("SELECT Id, Name, Email, Phone FROM Contacts ORDER BY Name", connection)
Using reader As SqliteDataReader = command.ExecuteReader()
While reader.Read()
Dim contact As New Dictionary(Of String, Object) From {
{"Id", reader.GetInt64(0)},
{"Name", reader.GetString(1)},
{"Email", If(reader.IsDBNull(2), "", reader.GetString(2))},
{"Phone", If(reader.IsDBNull(3), "", reader.GetString(3))}
}
contacts.Add(contact)
End While
End Using
End Using
End Using
Return contacts
End Function
''' Updates a contact by Id.
Public Function UpdateContact(id As Long, name As String, email As String, phone As String) As Boolean
Using connection As New SqliteConnection(_connectionString)
connection.Open()
Using command As New SqliteCommand()
command.Connection = connection
command.CommandText = "UPDATE Contacts SET Name = @name, Email = @email, Phone = @phone WHERE Id = @id"
command.Parameters.AddWithValue("@name", name)
command.Parameters.AddWithValue("@email", CObj(email) ?? DBNull.Value)
command.Parameters.AddWithValue("@phone", CObj(phone) ?? DBNull.Value)
command.Parameters.AddWithValue("@id", id)
Return command.ExecuteNonQuery() > 0
End Using
End Using
End Function
''' Deletes a contact by Id.
Public Function DeleteContact(id As Long) As Boolean
Using connection As New SqliteConnection(_connectionString)
connection.Open()
Using command As New SqliteCommand("DELETE FROM Contacts WHERE Id = @id", connection)
command.Parameters.AddWithValue("@id", id)
Return command.ExecuteNonQuery() > 0
End Using
End Using
End Function
End Class
Using the ContactRepository
Here’s how you’d use the class above, for example in a WinForms Form_Load event:
Dim dbPath As String = Path.Combine(AppContext.BaseDirectory, "contacts.db")
Dim repo As New ContactRepository(dbPath)
' Create the table (safe to call every time)
repo.InitializeDatabase()
' Add a contact
Dim newId As Long = repo.AddContact("Robert Skibbe", "mail@robbelroot.de", "+49 123 456")
Console.WriteLine($"Added contact with ID {newId}")
' Read all contacts
For Each contact In repo.GetAllContacts()
Console.WriteLine($"[{contact("Id")}] {contact("Name")} - {contact("Email")}")
Next
' Update a contact
repo.UpdateContact(newId, "Robert S.", "new@robbelroot.de", "+49 999 999")
' Delete a contact
repo.DeleteContact(newId)
If you want to display the results in a grid, have a look at the VB.NET DataGridView with filter functionality. For storing results by key, the VB.NET Dictionary is a great data structure.
Using transactions
When you need to insert or update multiple rows at once, wrap them in a transaction. This is both faster and safer: either all operations succeed or none of them do.
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using transaction = connection.BeginTransaction()
Using command As New SqliteCommand()
command.Connection = connection
command.Transaction = transaction
command.CommandText = "INSERT INTO Contacts (Name, Email) VALUES (@name, @email)"
For i As Integer = 1 To 1000
command.Parameters.Clear()
command.Parameters.AddWithValue("@name", $"Contact {i}")
command.Parameters.AddWithValue("@email", $"contact{i}@example.com")
command.ExecuteNonQuery()
Next
End Using
transaction.Commit()
End Using
End Using
Without a transaction, inserting 1,000 rows can take several seconds because SQLite commits after each individual INSERT. With a transaction, the same operation finishes in milliseconds.
Async/Await: keeping the UI responsive
If a database query runs on the UI thread, the entire interface freezes until it completes. Using Async/Await moves the work to the background while the UI stays responsive. For a deeper dive, check out the article on asynchronous programming in VB.NET.
Public Async Function GetAllContactsAsync() As Task(Of List(Of Dictionary(Of String, Object)))
Dim contacts As New List(Of Dictionary(Of String, Object))
Using connection As New SqliteConnection(_connectionString)
Await connection.OpenAsync()
Using command As New SqliteCommand("SELECT Id, Name, Email FROM Contacts ORDER BY Name", connection)
Using reader As SqliteDataReader = Await command.ExecuteReaderAsync()
While Await reader.ReadAsync()
Dim contact As New Dictionary(Of String, Object) From {
{"Id", reader.GetInt64(0)},
{"Name", reader.GetString(1)},
{"Email", If(reader.IsDBNull(2), "", reader.GetString(2))}
}
contacts.Add(contact)
End While
End Using
End Using
End Using
Return contacts
End Function
The pattern applies to all operations: OpenAsync() instead of Open(), ExecuteNonQueryAsync() instead of ExecuteNonQuery(), ReadAsync() instead of Read(). The calling method must be declared as Async Function.
Common pitfalls and solutions
„Database is locked“
This is the most common SQLite error. It occurs when multiple threads or connections try to write to the database simultaneously. Here’s how to avoid it:
- Always dispose connections – use
Usingblocks consistently. A forgotten open connection locks the file. - Enable WAL mode – Write-Ahead Logging allows readers and one writer to work concurrently:
Using connection As New SqliteConnection("Data Source=myapp.db")
connection.Open()
Using walCommand As New SqliteCommand("PRAGMA journal_mode=WAL;", connection)
walCommand.ExecuteNonQuery()
End Using
End Using
- Set a busy timeout – instead of failing immediately, let SQLite retry for a few seconds:
Using walCommand As New SqliteCommand("PRAGMA busy_timeout=5000;", connection)
walCommand.ExecuteNonQuery()
End Using
- Use transactions for bulk operations instead of opening/closing connections in a tight loop.
- Avoid long-running reads while writing. If you need complex background work, look into asynchronous programming in VB.NET.
File path issues
A relative path like Data Source=myapp.db resolves against the current working directory, which may differ depending on how your application was started (e.g. double-click vs. command line). Use an absolute path built from AppContext.BaseDirectory to be safe, as shown in the Application Path article.
NULL handling
When reading nullable columns, always check with reader.IsDBNull() before calling GetString() or GetInt64(). Otherwise you’ll get an InvalidCastException at runtime. When inserting NULL values, pass DBNull.Value instead of Nothing.
FAQ
Just open a connection with a file path that doesn’t exist yet: New SqliteConnection("Data Source=myapp.db"). SQLite creates the file automatically when you call Open().
The minimal connection string is Data Source=path\to\database.db. You can add options like Mode=ReadWrite or Cache=Shared depending on your needs.
This happens when multiple connections try to write simultaneously, or a connection wasn’t properly closed. Use Using blocks to dispose connections, enable WAL mode with PRAGMA journal_mode=WAL, and set a busy timeout.
Microsoft.Data.Sqlite is lighter, modern, and maintained by Microsoft. System.Data.SQLite is older and supports .NET Framework 4.0+. For new projects on .NET 5+ or .NET Framework 4.6.1+, Microsoft.Data.Sqlite is the recommended choice.
Yes. Microsoft.Data.Sqlite supports async methods like ExecuteNonQueryAsync(), ExecuteReaderAsync(), and ExecuteScalarAsync(). Use them with Await to keep the UI responsive during database operations.
Wrapping up
SQLite is the fastest way to add a local database to your VB.NET application. Install Microsoft.Data.Sqlite via NuGet, use Using blocks for every connection, and always write parameterized queries. For multi-threaded scenarios, enable WAL mode and set a busy timeout to avoid „database is locked“ errors.
Downloads
