.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.
Check PostgreSQL database objects
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.
Connect and retrieve PostgreSQL data
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.
Create the data model (Actor.cs)
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; }
}
Create the database connection
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);
}
}
Connection properties table
| 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 the data service page (ActorService.cs)
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;
}
}
Configure the user interface (MainPage.xaml)
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">
<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>
<Label x:Name="StatusLabel" Grid.Row="1" Text="Connecting..." HorizontalOptions="Center" />
</Grid>
</ContentPage>
Update the UI logic (MainPage.xaml.cs)
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.
Insert PostgreSQL data
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.
Update the UI (MainPage.xaml)
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">
<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>
<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>
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Update the data service (ActorService.cs)
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.
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();
}
Update the UI logic (MainPage.xaml.cs)
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.
Update PostgreSQL data
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.
Update the UI (MainPage.xaml)
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">
<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>
<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>
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Update the data service (ActorService.cs)
Add the UpdateActor method to ActorService.cs. This method will execute a parameterized UPDATE query based on the actor's ID.
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();
}
Update the UI logic (MainPage.xaml.cs)
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.
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.
Delete PostgreSQL data
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.
Update the UI (MainPage.xaml)
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">
<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>
<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>
<Label x:Name="StatusLabel" Grid.Row="2" Text="Ready" HorizontalOptions="Center" />
</Grid>
</ContentPage>
Update the data service (ActorService.cs)
Add the DeleteActor method to ActorService.cs. This method executes a DELETE query targeting the specific actor's ID.
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();
}
Update the UI logic (MainPage.xaml.cs)
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.
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.
Conclusion
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!
FAQ
How do you install and activate dotConnect for PostgreSQL in a .NET project?
Install dotConnect for PostgreSQL either by running the
Windows installer (EXE) or by adding the
Devart.Data.PostgreSql NuGet package to your .NET project. Then, retrieve your activation key from the
Devart Customer Portal and specify it in your connection string by using the License Key parameter to activate the provider and connect successfully.
How do you create a connection to PostgreSQL using dotConnect in C#?
Define a connection string that includes 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.
How do you enable SSL/TLS for secure PostgreSQL connections with dotConnect?
Add 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.
Can you connect to PostgreSQL using Entity Framework Core and dotConnect?
Yes, you can connect to PostgreSQL using Entity Framework Core and dotConnect. You can either use Entity Developer to visually create an EF Core model from the database and generate the 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.
Is it possible to connect to PostgreSQL using Visual Studio Server Explorer with dotConnect?
Yes. In Visual Studio Server Explorer, you can add a new Data Connection, select dotConnect for PostgreSQL as the data provider, enter your PostgreSQL connection details, test the connection, and then browse and work with database objects directly in the IDE.