.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.

Why dotConnect for PostgreSQL?

ORM support provided

Advanced ORM support

Fully supports EF Core, Dapper, NHibernate, LinqConnect, and other modern data access technologies for efficient and reliable data management.

Full ADO.NET compliance

Full ADO.NET compliance

Conforms to the latest ADO.NET standards and recent industry innovations for seamless and consistent integration with .NET applications.

Support for MySQL-specific data types

PostgreSQL-specific data types

Includes many PostgreSQL-specific features and fully supports all unique data types for accurate and complete data representation.

Secure connection ensured

Secure connection options

Provides robust security with support for SSL/SSH connections, connecting via proxy servers, embedded servers, and HTTP tunneling.

Integration with popular IDEs

IDE integration

Features native integration with Visual Studio and complete design-time support for accelerated development.

Priority support provided

Priority support & frequent updates

Includes priority support, detailed documentation, and regular updates for continuous improvement.

Download and activate dotConnect for PostgreSQL

You can start using dotConnect for PostgreSQL immediately with a 30-day free trial. Choose one of the following installation options:

30-day free trial version

dotnet add package Devart.Data.PostgreSql
Install-Package Devart.Data.PostgreSql

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.

Start using dotConnect for PostgreSQL in your project today with a free trial

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.

Connect to PostgreSQL using Data Explorer in Visual Studio

Enter your server details and credentials, and click Connect.

Successful connection to PostgreSQL in Visual Studio

Once connected, you can browse tables, execute queries, and manage data directly within the Data Explorer.

Manage and manipulate PostgreSQL database objects in Visual Studio

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">
        <!-- 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 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.

View the data in a .NET MAUI application

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">
        <!-- 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>

The application UI for inserting records

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.

// ... (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();
    }

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;
    }
}

Inserting new records into a PostgreSQL database

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.

View the successfully inserted data in the application

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">
        <!-- 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>

The app UI for data updating

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.

// ... (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();
    }

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.

// 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}";
        }
    }

Updating data in the database from the application

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.

View the successfully updated data in the application

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">
        <!-- 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>

The app UI for the user to delete records

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.

// ... (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();
    }

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.

// ... (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}";
        }
    }

Deleting records from the database

The application is complete. It has the CRUD functionality to read, create, update, and delete records in a PostgreSQL database through a modern UI.

Confirm the successful data deletion

Video tutorial

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.

Victoria Lazarus

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.

Try the 30-day trial of the full product. No limits. No card required. Start free trial