You can install the driver by using the Windows installer.
After you receive the license key, add it to your connection strings to connect to the data source.
.NET MAUI (Multi-platform App UI) is a cross-platform framework for building desktop and mobile applications using C# and XAML so that developers can target multiple operating systems, including Windows, macOS, and Android, from a single codebase.
PostgreSQL is one of the most popular options for building data-driven applications, favored for its reliability, flexibility, and powerful SQL capabilities. Therefore, a combination of .NET MAUI and PostgreSQL is quite common.
In this tutorial, we will demonstrate how to integrate PostgreSQL with a .NET MAUI application and manage databases directly from C# code.
Fully supports EF Core, Dapper, NHibernate, LinqConnect, and other modern data access technologies for efficient and reliable data management.
Conforms to the latest ADO.NET standards and recent industry innovations for seamless and consistent integration with .NET applications.
Includes many PostgreSQL-specific features and fully supports all unique data types for accurate and complete data representation.
Provides robust security with support for SSL/SSH connections, connecting via proxy servers, embedded servers, and HTTP tunneling.
Features native integration with Visual Studio and complete design-time support for accelerated development.
Includes priority support, detailed documentation, and regular updates for continuous improvement.
You can start using dotConnect for PostgreSQL immediately with a 30-day free trial. Choose one of the following installation options:
Our first option is connecting to PostgreSQL via the Data Explorer in Visual Studio. Click Tools, then select Connect to Database, and choose PostgreSQL as the data source.
Enter your server details and credentials, and click Connect.
Once connected, you can browse tables, execute queries, and manage data directly within the Data Explorer.
Our goal is to establish a connection to the PostgreSQL database and display data in a read-only mode. This ensures our connection and data service are correctly configured to provide the basics for the other CRUD operations.
The first step is to create a new C# class named Actor.cs in your project. This class will act as a model to hold the data for each actor record.
namespace PostgreSqlMAUI;
public class Actor
{
public int ActorId { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime LastUpdate { get; set; }
}
Next, create the DatabaseConnection.cs file. This class centralizes our connection string, making it easy to manage. We use Devart.Data.PostgreSql and specify the SearchPath to target the public schema.
using Devart.Data.PostgreSql;
namespace PostgreSqlMAUI;
public class DatabaseConnection
{
private const string ConnectionString =
"Host=127.0.0.1;User Id=postgres;Password=postgres;Database=postgres;Schema=public;License key=**********";
public static PgSqlConnection GetConnection()
{
return new PgSqlConnection(ConnectionString);
}
}
| Property | Meaning |
|---|---|
| Host | Specifies the hostname or IP address of the PostgreSQL server |
| Port | States the port number on which the PostgreSQL server is listening |
| UserId | Indicates the user ID used to authenticate with PostgreSQL |
| Password | Defines the password for the user ID |
| Database | Specifies the default database to use after connecting |
| Schema | The PostgreSQL schema to use |
| License Key | Specify your license key in this parameter. This is required only when using .NET Standard compatible assemblies |
Create an ActorService.cs file. We will start by adding a single method, GetAllActors, that queries the database and returns a list of all actors.
using Devart.Data.PostgreSql;
namespace PostgreSqlMAUI;
public class ActorService
{
public static List<Actor> GetAllActors()
{
var actors = new List<Actor>();
using var connection = DatabaseConnection.GetConnection();
connection.Open();
using var command = new PgSqlCommand("SELECT actor_id, first_name, last_name, last_update FROM public.actor ORDER BY actor_id", connection);
using var reader = command.ExecuteReader();
while (reader.Read())
{
actors.Add(new Actor
{
ActorId = reader.GetInt32(0),
FirstName = reader.GetString(1),
LastName = reader.GetString(2),
LastUpdate = reader.GetDateTime(3)
});
}
return actors;
}
}
Modify MainPage.xaml to contain a ListView for displaying the actors and a Label for status messages. At this stage, there are no input fields or buttons.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PostgreSqlMAUI.MainPage"
Title="Actor Management">
<Grid RowDefinitions="*,Auto" Padding="20" RowSpacing="15">
<!-- Actor List -->
<ListView x:Name="ActorListView" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout Padding="10">
<Label Text="{Binding ActorId, StringFormat='ID: {0}'}" WidthRequest="60" />
<Label Text="{Binding FirstName}" WidthRequest="100" />
<Label Text="{Binding LastName}" WidthRequest="100" />
<Label Text="{Binding LastUpdate, StringFormat='{0:yyyy-MM-dd HH:mm}'}" HorizontalOptions="EndAndExpand" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Status Bar -->
<Label x:Name="StatusLabel" Grid.Row="1" Text="Connecting..." HorizontalOptions="Center" />
</Grid>
</ContentPage>
Update the code-behind file that is called MainPage.xaml.cs. The code will call the LoadActors method from the constructor, ensuring the data is fetched immediately upon application launch.
namespace PostgreSqlMAUI;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
LoadActors();
}
private void LoadActors()
{
try
{
var actors = ActorService.GetAllActors();
ActorListView.ItemsSource = actors;
StatusLabel.Text = $"Loaded {actors.Count} actors";
}
catch (Exception ex)
{
StatusLabel.Text = $"Error: {ex.Message}";
}
}
}
This way, you have built a functional, read-only .NET MAUI application that connects to a PostgreSQL database and displays the data. Your core setup is working correctly.
Let us enhance our application by adding the ability to create new records. For that, we need to add input fields and a button to the UI, and also we need to implement the necessary logic to execute an INSERT SQL statement.
Modify MainPage.xaml to add Entry fields for the first and last name, and an Insert button. It will adjust the main grid layout to accommodate these new elements.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PostgreSqlMAUI.MainPage"
Title="Actor Management">
<Grid RowDefinitions="Auto,*,Auto" Padding="20" RowSpacing="15">
<!-- Input Form -->
<Grid Grid.Row="0" ColumnDefinitions="*,*" ColumnSpacing="10" RowDefinitions="Auto,Auto">
<Entry x:Name="FirstNameEntry" Placeholder="First Name" Grid.Row="0" Grid.Column="0" />
<Entry x:Name="LastNameEntry" Placeholder="Last Name" Grid.Row="0" Grid.Column="1" />
<Button Text="Insert" Clicked="OnInsertClicked" Grid.Row="1" Grid.ColumnSpan="2" />
</Grid>
<!-- Actor List -->
<ListView x:Name="ActorListView" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout Padding="10">
<Label Text="{Binding ActorId, StringFormat='ID: {0}'}" WidthRequest="60" />
<Label Text="{Binding FirstName}" WidthRequest="100" />
<Label Text="{Binding LastName}" WidthRequest="100" />
<Label Text="{Binding LastUpdate, StringFormat='{0:yyyy-MM-dd HH:mm}'}" HorizontalOptions="EndAndExpand" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Status Bar -->
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Add the InsertActor method to the ActorService.cs file. This method takes the first and last names, connects to the database, and executes a parameterized INSERT query. PostgreSQL uses the paramName syntax for parameters.
// ... (add this method inside the ActorService class)
public static void InsertActor(string firstName, string lastName)
{
using var connection = DatabaseConnection.GetConnection();
connection.Open();
using var command = new PgSqlCommand(
"INSERT INTO public.actor (first_name, last_name, last_update) VALUES (:firstName, :lastName, :lastUpdate)",
connection);
command.Parameters.Add(new PgSqlParameter("firstName", firstName));
command.Parameters.Add(new PgSqlParameter("lastName", lastName));
command.Parameters.Add(new PgSqlParameter("lastUpdate", DateTime.Now));
command.ExecuteNonQuery();
}
Modify MainPage.xaml.cs to handle the new functionality. Add a private field _selectedActor (we'll use it in the next steps) and create the OnInsertClicked and ClearForm methods.
namespace PostgreSqlMAUI;
public partial class MainPage : ContentPage
{
private Actor? _selectedActor;
public MainPage()
{
InitializeComponent();
LoadActors();
}
private void LoadActors()
{
try
{
var actors = ActorService.GetAllActors();
ActorListView.ItemsSource = actors;
StatusLabel.Text = $"Loaded {actors.Count} actors";
}
catch (Exception ex)
{
StatusLabel.Text = $"Error: {ex.Message}";
}
}
private async void OnInsertClicked(object? sender, EventArgs e)
{
var firstName = FirstNameEntry.Text?.Trim();
var lastName = LastNameEntry.Text?.Trim();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
await DisplayAlert("Validation", "Please enter both first and last name.", "OK");
return;
}
try
{
ActorService.InsertActor(firstName, lastName);
StatusLabel.Text = $"Inserted: {firstName} {lastName}";
ClearForm();
LoadActors();
}
catch (Exception ex)
{
StatusLabel.Text = $"Error: {ex.Message}";
}
}
private void ClearForm()
{
FirstNameEntry.Text = string.Empty;
LastNameEntry.Text = string.Empty;
_selectedActor = null;
ActorListView.SelectedItem = null;
}
}
Now, our application can add new records to the database. We can input data into the form, press the Insert Actor button, and see the table updated immediately with the new entry.
Here, our task is to implement the ability to edit existing records. This requires configuring the logic to select an item from the list, populate the input fields with its data, and then save the changes with an UPDATE SQL command.
In MainPage.xaml, add the Update button next to Insert and attach an ItemSelected event handler to ListView.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PostgreSqlMAUI.MainPage"
Title="Actor Management">
<Grid RowDefinitions="Auto,*,Auto" Padding="20" RowSpacing="15">
<!-- Input Form -->
<Grid Grid.Row="0" ColumnDefinitions="*,*" ColumnSpacing="10" RowDefinitions="Auto,Auto">
<Entry x:Name="FirstNameEntry" Placeholder="First Name" Grid.Row="0" Grid.Column="0" />
<Entry x:Name="LastNameEntry" Placeholder="Last Name" Grid.Row="0" Grid.Column="1" />
<Grid Grid.Row="1" Grid.ColumnSpan="2" ColumnDefinitions="*,*">
<Button Text="Insert" Clicked="OnInsertClicked" Grid.Column="0" />
<Button Text="Update" Clicked="OnUpdateClicked" Grid.Column="1" />
</Grid>
</Grid>
<!-- Actor List -->
<ListView x:Name="ActorListView" Grid.Row="1" ItemSelected="OnActorSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout Padding="10">
<Label Text="{Binding ActorId, StringFormat='ID: {0}'}" WidthRequest="60" />
<Label Text="{Binding FirstName}" WidthRequest="100" />
<Label Text="{Binding LastName}" WidthRequest="100" />
<Label Text="{Binding LastUpdate, StringFormat='{0:yyyy-MM-dd HH:mm}'}" HorizontalOptions="EndAndExpand" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Status Bar -->
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Add the UpdateActor method to ActorService.cs. This method will execute a parameterized UPDATE query based on the actor's ID.
// ... (add this method inside the ActorService class)
public static void UpdateActor(int actorId, string firstName, string lastName)
{
using var connection = DatabaseConnection.GetConnection();
connection.Open();
using var command = new PgSqlCommand(
"UPDATE public.actor SET first_name = :firstName, last_name = :lastName, last_update = :lastUpdate WHERE actor_id = :actorId",
connection);
command.Parameters.Add(new PgSqlParameter("firstName", firstName));
command.Parameters.Add(new PgSqlParameter("lastName", lastName));
command.Parameters.Add(new PgSqlParameter("lastUpdate", DateTime.Now));
command.Parameters.Add(new PgSqlParameter("actorId", actorId));
command.ExecuteNonQuery();
}
Add the OnActorSelected and OnUpdateClicked methods to MainPage.xaml.cs. Here, OnActorSelected will populate the form when a user taps an item in the list. After that, OnUpdateClicked will save the changes.
// PostgreSqlMAUI/MainPage.xaml.cs
// ... (add these methods inside the MainPage class)
private void OnActorSelected(object? sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem is Actor actor)
{
_selectedActor = actor;
FirstNameEntry.Text = actor.FirstName;
LastNameEntry.Text = actor.LastName;
}
}
private async void OnUpdateClicked(object? sender, EventArgs e)
{
if (_selectedActor == null)
{
await DisplayAlert("Validation", "Please select an actor to update.", "OK");
return;
}
var firstName = FirstNameEntry.Text?.Trim();
var lastName = LastNameEntry.Text?.Trim();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
await DisplayAlert("Validation", "Please enter both first and last name.", "OK");
return;
}
try
{
ActorService.UpdateActor(_selectedActor.ActorId, firstName, lastName);
StatusLabel.Text = $"Updated: {firstName} {lastName}";
ClearForm();
LoadActors();
}
catch (Exception ex)
{
StatusLabel.Text = $"Error: {ex.Message}";
}
}
These updates allowed our application to support editing data. We can select an actor, change their details in the input form, and save the changes back to the database.
Finally, we are implementing the delete functionality. For that, we add the Delete button and update the logic necessary to execute the DELETE SQL command. Note that it must include the confirmation step to prevent accidental data loss.
Modify MainPage.xaml to include the Delete button. It will complete our set of CRUD controls.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PostgreSqlMAUI.MainPage"
Title="Actor Management">
<Grid RowDefinitions="Auto,*,Auto" Padding="20" RowSpacing="15">
<!-- Input Form -->
<Grid Grid.Row="0" ColumnDefinitions="*,*" ColumnSpacing="10" RowDefinitions="Auto,Auto">
<Entry x:Name="FirstNameEntry" Placeholder="First Name" Grid.Row="0" Grid.Column="0" />
<Entry x:Name="LastNameEntry" Placeholder="Last Name" Grid.Row="0" Grid.Column="1" />
<Grid Grid.Row="1" Grid.ColumnSpan="2" ColumnDefinitions="*,*,*">
<Button Text="Insert" Clicked="OnInsertClicked" Grid.Column="0" />
<Button Text="Update" Clicked="OnUpdateClicked" Grid.Column="1" />
<Button Text="Delete" Clicked="OnDeleteClicked" Grid.Column="2" BackgroundColor="#D32F2F" />
</Grid>
</Grid>
<!-- Actor List -->
<ListView x:Name="ActorListView" Grid.Row="1" ItemSelected="OnActorSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout Padding="10">
<Label Text="{Binding ActorId, StringFormat='ID: {0}'}" WidthRequest="60" />
<Label Text="{Binding FirstName}" WidthRequest="100" />
<Label Text="{Binding LastName}" WidthRequest="100" />
<Label Text="{Binding LastUpdate, StringFormat='{0:yyyy-MM-dd HH:mm}'}" HorizontalOptions="EndAndExpand" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Status Bar -->
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Add the DeleteActor method to ActorService.cs. This method executes a DELETE query targeting the specific actor's ID.
// ... (add this method inside the ActorService class)
public static void DeleteActor(int actorId)
{
using var connection = DatabaseConnection.GetConnection();
connection.Open();
using var command = new PgSqlCommand(
"DELETE FROM public.actor WHERE actor_id = :actorId",
connection);
command.Parameters.Add(new PgSqlParameter("actorId", actorId));
command.ExecuteNonQuery();
}
Add the OnDeleteClicked method to MainPage.xaml.cs. This method triggers the pop-up window to confirm the user's action first, and after that only it proceeds to delete the item.
// ... (add this method inside the MainPage class)
private async void OnDeleteClicked(object? sender, EventArgs e)
{
if (_selectedActor == null)
{
await DisplayAlert("Validation", "Please select an actor to delete.", "OK");
return;
}
var confirm = await DisplayAlert("Confirm Delete",
$"Delete actor {_selectedActor.FirstName} {_selectedActor.LastName}?",
"Yes", "No");
if (!confirm) return;
try
{
ActorService.DeleteActor(_selectedActor.ActorId);
StatusLabel.Text = $"Deleted: {_selectedActor.FirstName} {_selectedActor.LastName}";
ClearForm();
LoadActors();
}
catch (Exception ex)
{
StatusLabel.Text = $"Error: {ex.Message}";
}
}
The application is complete. It has the CRUD functionality to read, create, update, and delete records in a PostgreSQL database through a modern UI.
We explored how to perform basic Create, Read, Update, and Delete (CRUD) operations with PostgreSQL in a .NET MAUI application. As the framework continues to evolve, understanding the fundamentals of working with databases such as PostgreSQL becomes increasingly important.
This guide provided code examples and connection strings to help you integrate PostgreSQL into projects built with .NET MAUI. With this foundation, you can move forward to build applications that manage data in both local and remote environments.
Using dotConnect for PostgreSQL further simplifies the process by reducing many of the challenges involved in establishing connectivity between an application and its data source. You can try dotConnect for PostgreSQL in your own projects—start a free trial and experience the benefits firsthand!
Host, User Id, Password, Database, and (if required) License Key. Then create a PgSqlConnection with this string and call Open() inside a try/catch block to verify the connection and handle any errors.
SslMode=Require and provide the certificate file paths (for example, CACert, Cert, and Key) in the connection string. Then create a PgSqlConnection with this string and call Open() to establish an encrypted SSL/TLS session.
DbContext and entity classes, or run Scaffold-DbContext with a dotConnect connection string (including License Key) to scaffold the DbContext and entities from an existing database.
I'm a technical content writer who loves breaking complex tech topics into clear and helpful content that's enjoyable to read. With a solid writing background and growing skill in software development and database tools, I create content that's accurate, easy to follow, and genuinely useful. When I'm not writing, you'll probably find me learning something new or sweating it out at the gym.