Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
153621 stories
·
33 followers

Introducing WinUI agent plugin for GitHub Copilot and Claude Code

1 Share

Introducing WinUI Agent plugin: Build WinUI apps with AI agents

We’re excited to share agent skills designed to efficiently build native Windows apps with WinUI and the Windows App SDK with agents such as GitHub Copilot and Claude Code.

These skills enable your agent to take your ideas to a native Windows app in minutes! They are built around the end-to-end loop: scaffold, build, run, test, iterate. They’ve been optimized to know how to drive each stage, recognize common failures that get normal agents stuck in loops, and steer towards successful patterns, while also minimizing the number of tokens used by your agent. We hope to continually improve this experience and outcomes as we gather more feedback and examples from our developer community!

Try it

# Add the WinUI plugin to GitHub Copilot CLI
/plugin install winui@awesome-copilot

# Run the winui-setup skill to install all prerequisites
/winui-setup

Then ask GitHub Copilot for the WinUI 3 app you’ve been meaning to build, and tell us how it goes.

Skills built for WinUI development

Modern Windows app development covers a lot of ground – XAML and Fluent Design, MVVM, MSIX packaging, code signing, Store submission, accessibility, theming, UI automation. Each piece is well-documented on its own but stitching them together into a smooth inner loop takes practice. AI agents working from generic web context tend to mix WinUI with older stacks, miss the packaged-execution model, or stop short of running and verifying what they built.

We had two goals from day one: build skills that handle WinUI development end-to-end – from dotnet new through a packaged app – and keep them cheap to run so developers aren’t paying for unnecessary context on every turn. The two goals push in opposite directions, so we paired the skills with purpose-built tools (covered below) that give the agent ground-truth answers on demand, instead of loading entire reference pages up front.

Copilot CLI agents and skills let us put that knowledge right next to the developer. You ask copilot -p "create a WinUI 3 photo viewer with thumbnails and EXIF metadata", and the agent:

  1. Picks the right template and scaffolds the project
  2. Builds it and runs it through the right packaged-execution pipeline
  3. Interacts with the app to validate functionality, if requested

All the while it’s:

  • Designing with XAML theming and accessibility in mind
  • Using MVVM principles to separate concerns
  • Fixing compilation errors automatically
  • Considering the right usage of controls for your scenario

and when you think you have your ideal validated, the agent can polish your app at a higher level with:

  • Accessibility audits
  • Code reviews
  • Packaging
  • and UI testing

…all without you needing to guide it or teach it how to do any of those steps. And because each skill only loads what it needs and leans on the tools below for the rest, the agent does all of this in more than 70% fewer tokens than when we started – on the same model.

What’s inside the plugin

The plugin ships one agent, eight skills, and several supporting tools. The agent is the entry point – it loads by default the skills for building, debugging, and design, and you can add additional skills in the prompt or individually use just the skills you need.

The WinUI agent plugin inlcudes 8 skills

The agent: winui-dev

A focused agent for WinUI / Windows App SDK / XAML / C# work. Use it for new apps, adding features, converting from WPF/Electron/web, or fixing bugs. It’s the orchestrator that pulls in the winui-dev-workflow and winui-design skills by default and kicks of the agent to accomplish the task.

The skills

Each skill is a focused, self-contained playbook. The agent loads winui-design and winui-dev-workflow by default – those cover most “build me a WinUI 3 app” requests end-to-end. You opt into the others when you want them. Mention UI testing and the agent will load the winui-ui-testing skill to drive your app through real UI automation, find functional issues, and iterate on fixes; ask forwinui-packaging when you’re ready to ship; include winui-wpf-migration when you’re porting from WPF. Skills compose cleanly, so adding one doesn’t disrupt the others.

Skill What it does
winui-dev-workflow Build and run workflow – project creation from templates, the BuildAndRun.ps1 helper, winapp run, error diagnosis, prerequisites. Use when building, running, or fixing build errors.
winui-design UI design and XAML correctness – layout planning, control selection, Fluent Design, theming (Light/Dark/HighContrast), typography, spacing, brushes, accessibility, data-binding review. Use when designing new pages, converting from WPF/Electron/web, reviewing XAML, or fixing theme issues.
winui-code-review Code-quality review before committing – MVVM compliance, x:Bind correctness, accessibility, theming, security, performance. Catches the issues the compiler and UI tests won’t.
winui-ui-testing Automated UI testing – generates a batch test script, runs all tests in one pass, reads results. Covers element assertions, interactions, value checks (TextBox, ComboBox, ToggleSwitch), file pickers, flyouts, dialogs, persistence, and accessibility audits.
winui-packaging MSIX packaging, code signing, and distribution – release builds, certificate generation (winapp cert generate), trust, signing (winapp sign), self-contained deployment, GitHub Actions CI/CD, and Microsoft Store submission.
winui-wpf-migration WPF → WinUI 3 migration – namespace replacement, control mapping (DataGridListView, WrapPanelItemsRepeater, TabControlTabView), DispatcherDispatcherQueue, System.DrawingBitmapImage, MVVM conversion to CommunityToolkit.Mvvm, and DynamicResourceThemeResource.
winui-session-report Diagnostic report on the current or a recent Copilot session. Use when asking for session feedback, debugging agent behavior, or reviewing what happened during a build session.
winui-setup Setup your development environment – only call once if needed to ensure all the tools are installed and available.

The tools we lean on

Skills are prompts plus playbooks. They get their actual leverage from a small set of tools – some included as part of each skill.

External: tools expected on your machine

  • WinApp CLI (winget install Microsoft.WinAppCLI) – the command-line driver for installing, running, signing, packaging, and automating WinUI 3 / WinAppSDK apps. Without winapp, these skills wouldn’t be possible. Packaged WinUI apps can’t just be launched as a .exe – they need to be installed and started through the right activation pipeline. winapp run does exactly that, and crucially streams the app’s debug output back to the agent so it can see crashes and exceptions instead of staring at silence. winapp ui powers the entire winui-ui-testing skill: enumerating elements, asserting state, driving controls, capturing accessibility audits – all from the command line, all scriptable, all readable by an AI agent. winapp manifest and winapp pack are what makes the winui-packaging skill able to take a build all the way to a signed MSIX.winapp CLI is built in parallel with these skills, in lockstep. Every time a skill needed a new automation surface, a missing JSON output mode, or a tighter feedback loop, that requirement landed in winapp first. The two repos co-evolve, and we expect that to continue. Read the latest WinApp CLI blog post for more details on winapp run and winapp ui.
  • Microsoft.WindowsAppSDK.WinUI.CSharp.Templates — the new dotnet new templates for WinUI 3 + Windows App SDK. The dev-workflow skill uses these as the canonical scaffold so every new project starts from the same supported baseline. Read the blog post introducing the new dotnet new templates for WinUI to learn more.

In-skill: the three tools we built alongside the skills

These pre-build binaries ship with the plugin. Each one solves a problem that prevents the skills from being reliable. All three are early days – see the “What’s next” notes for where each is heading.

🛠 winui3-analyzer – a Roslyn analyzer for WinUI 3 pitfalls

A netstandard2.0 Roslyn analyzer that catches common WinUI 3 / WinAppSDK issues at build time: UWP namespace leaks, Window.Current and CoreDispatcher usage, WebView2 without EnsureCoreWebView2Async, TabView items containing raw content, attached-property initializer bugs, removed ONNX GenAI APIs, the old field-backed [ObservableProperty] pattern, and more. Distributed today as a prebuilt DLL the winui-dev-workflow skill auto-injects into your dotnet build.

  • What’s next: the goal is to ship this as a standalone NuGet package with a categorized rule catalog, severity-ceiling policy (no Error rules by default), and per-rule helpLinkUris. Whenever that lands on NuGet, the skill will switch to a <PackageReference> and the in-repo distribution path goes away. Feedback on rule severity, false positives, and which categories matter most will shape the catalog.

🔍 winui-search – a fast, offline catalog of WinUI Gallery + Community Toolkit samples

A native-AOT CLI that indexes WinUI Gallery and CommunityToolkit/Windows scenarios so the agent has grounded knowledge of which control to pick, and how to use it well. WinUI 3 ships with a lot of controls – and Community Toolkit adds many more on top. Knowing whether the right answer is NavigationView or TabView, ListView or ItemsRepeater, a stock Expander or a Toolkit SettingsExpander, isn’t something a generic LLM gets right reliably. winui-search puts the actual shipping samples behind a fast lookup so the agent can see real usage before writing a single line of XAML. When a skill is drafting a settings page, it can ask winui-search for “toggle switch with description text” and get back the actual XAML pattern from WinUI Gallery – including the expected resource keys, spacing, and ancillary controls. Need a NavigationView example with a custom title bar? An ItemsRepeater with virtualized cards? A Community Toolkit SettingsCard with an icon and a hyperlink? It’s one query away. Backs the design skill’s “show me a real-world example” capability and grounds the agent’s XAML in patterns that already shipped to real users.

  • What’s next: the data layer and the CLI surface are both in flux. We’re considering a few directions: shipping it as a standalone CLI tool, folding the search surface into winapp itself, or exposing the catalog over a small MCP server. We’re also working on expanding what’s searchable – multi-control composite patterns (a full settings page, not just a single ToggleSwitch), and going beyond UI into C# code patterns so agents can reliably search for snippets like “how do I implement a custom ICommand” or “Dispatcher-safe progress reporting from a background Task“. The right answer depends on where developers want to use it – your feedback will decide.

🧬 winmd-cli – offline WinRT + .NET API metadata lookup

A native-AOT CLI that indexes .winmd and managed .dll metadata from NuGet, the Windows SDK, and WinAppSDK and provides fast, offline API lookup with the same XML doc text Visual Studio IntelliSense uses. The agent can use it to verify that an API actually exists and has the signature it thinks it does – before writing code that won’t compile.

  • What’s next: same set of options as winui-search – standalone CLI, integration into winapp, or MCP server. Likely shares whatever distribution decision we land on for winui-search.

One more in-repo tool you should know about

🩹 BuildAndRun.ps1 – a temporary build wrapper

The winui-dev-workflow skill currently invokes a small PowerShell helper, BuildAndRun.ps1, instead of calling dotnet build directly. Why? There’s a known XAML compiler issue (issue, issue) where, under dotnet build, a malformed XAML file produces no useful diagnostic – the build just fails with no indication of what’s wrong in which .xaml. Agents that hit this get completely stuck: they can see the build failed, but they have no signal to act on, and they thrash through unrelated guesses.

The script’s actual workaround is small but specific: if MSBuild is available on the machine (i.e. Visual Studio with the right workload is installed), the script builds with MSBuild instead of dotnet build, because the XAML diagnostics flow correctly through MSBuild. If MSBuild isn’t there, it falls back to dotnet build and surfaces what it can. Either way, after a successful build it hands off to winapp run so the app launches the right way.

  • What’s next: this script is explicitly temporary. It’s slated to be removed entirely once the XAML compiler fix ships in the next Windows App SDK release. We’d rather not paper over platform bugs forever – the right fix lives in the platform.

We want your feedback!

This is the part we want to over-communicate: everything you see today is a starting point.

  • Skill names, scopes, and structure can change. We’ve already collapsed and re-scoped a few skills based on early internal feedback.
  • Distribution mechanics for the analyzer and CLI tools are open questions – see each tool’s “what’s next” above.
  • The BuildAndRun.ps1 helper will go away when the platform fix ships.
  • The winapp CLI is itself young and evolving in lockstep with these skills.

If a skill produces something wrong, surprising, or just not as good as it should be, we want to know. The fastest way to influence the direction is to file an issue on the microsoft/win-dev-skills repo (the bug template asks you to attach a session report – generated for you by the winui-session-report skill).

Give the plugin a try and let us know what you think!

The post Introducing WinUI agent plugin for GitHub Copilot and Claude Code appeared first on #ifdef Windows.

Read the whole story
alvinashcraft
54 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Building a Blazor app with OpenCode and DeepSeek

1 Share
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

The complete guide to mastering Dapper micro-ORM in .NET

1 Share

This blog post is originally published on https://blog.elmah.io/the-complete-guide-to-mastering-dapper-micro-orm-in-net/

For developers who want to taste ORM but don't want to leave SQL either, Dapper is a perfect choice. Dapper runs SQL queries like ADO.NET but returns results as C# objects, like Entity Framework Core. Apart from its abstracting nature, you leverage high-speed data access. The feature set of Dapper is quite large and covers key areas of database work, such as JOINs, aggregate functions, database procedures and functions, transactions, etc. In today's post, I will walk through everything needed to get you started with Dapper.

The complete guide to mastering Dapper micro-ORM in .NET

What is Dapper?

Dapper is a micro-ORM (Object Relational Mapper) for .NET. Developed by Stack Overflow, Dapper offers higher performance database operations faster than full ORMs like Entity Framework Core (EF Core). It suits developers who work with ADO.NET because of its query commands, unlike EF Core, which operates on objects rather than SQL. SQL Queries are directly mapped to strongly typed objects like any ORM.

Why Use Dapper?

As Dapper is a micro-ORM that offers a thin abstraction between the database and applications, it introduces very little overhead. For these reasons, it is an optimal choice for high-performing scenarios. Similar to ADO .NET, you can get full control over SQL queries. You can think of it as the middle ground between ADO.NET and an ORM like EF Core, which uses data as strongly typed C# objects. One can view the SQL generated by EF Core, but can't granularly control it. Dapper uses minimal dependencies, keeping the architecture lightweight. That is one of the reasons Dapper best suits microservices, financial systems, web APIs, and modular APIs.

Commonly used Dapper methods

To summarize the most commonly used Dapper methods, let's go ahead and create a new project.

Step 1: Create a project

dotnet new console -n UserDapperDemo
cd UserDapperDemo

Step 2: Install the required packages

dotnet add package Dapper
dotnet add package Npgsql
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json

Npgsql provides a NpgsqlConnection class for connecting to PostgreSQL. The Configuration* packages are useful when we create and include appsettings.json in the project.

Step 3: Create models

namespace UserDapperDemo.Models;

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public int DirectorId { get; set; }
    public double Rating { get; set; }
}
namespace UserDapperDemo.Models;

public class Director
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

Step 4: Add appsettings.json and its configuration

{
  "ConnectionStrings": {
    "PostgresConnection": "Host=localhost;Port=5432;Database=movieDb;Username=postgres;Password=1234"
  }
}

In the .proj file add the following item group:

    <ItemGroup>
        <None Update="appsettings.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>

Step 5: Configure connection factory

using System.Data;
using Microsoft.Extensions.Configuration;
using Npgsql;

namespace UserDapperDemo.Data;

public class DbConnectionFactory
{
    
    private readonly string _connectionString;

    public DbConnectionFactory()
    {
        
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

        _connectionString = config.GetConnectionString("PostgresConnection");
    }

    public IDbConnection CreateConnection()
        => new NpgsqlConnection(_connectionString);
}

It makes a centralized database connection point.

Step 6: Prepare the database

Run the following query for the database

CREATE TABLE IF NOT EXISTS public."Director"
(
    "Id" integer NOT NULL DEFAULT nextval('"Director_Id_seq"'::regclass),
    "Name" text COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT "Director_pkey" PRIMARY KEY ("Id")
)
CREATE TABLE IF NOT EXISTS public."Movie"
(
    "Id" integer NOT NULL DEFAULT nextval('"Movie_Id_seq"'::regclass),
    "Title" text COLLATE pg_catalog."default" NOT NULL,
    "DirectorId" integer,
    "Rating" numeric,
    CONSTRAINT "Movie_pkey" PRIMARY KEY ("Id"),
    CONSTRAINT "Movie_DirectorId_fkey" FOREIGN KEY ("DirectorId")
        REFERENCES public."Director" ("Id") MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
)

So, our database tables look like:

Tables
Database

Step 7: Implement Dapper methods

I will follow a repository pattern to test some of Dapper's most common methods. This is just for the sake of demoing common methods. Whether or not you want to implement repositories in your code is entirely up to you. Some people love repositories while others absolutely hate them.

The movie repository will be:

public class MovieRepo
{
    private readonly DbConnectionFactory _factory;
    public MovieRepo(DbConnectionFactory factory)
    {
        _factory = factory;
    }
    
    //methods
}
    

QueryAsync<T>()

public async Task<IEnumerable<Movie>> GetAllMoviesAsync()
{
    using var connection = _factory.CreateConnection();

    return await connection.QueryAsync<Movie>(
        "SELECT * FROM \"Movie\""
    );

QueryAsync returns multiple rows of data asynchronously. It is used when we need to fetch multiple rows of data. Its synchronous counterpart is Query<>().

QueryFirstAsync<T>()

public async Task<Movie> GetFirstMovie()
{
    using var connection = _factory.CreateConnection();

    return await connection.QueryFirstAsync<Movie>(
        "SELECT * FROM \"Movie\" ORDER BY \"Id\" LIMIT 1"
    );
}

A single row-returning operation that returns the first row that matches the given condition. It expects the record to exist, otherwise, it throws an exception. QueryFirst<T>() is for synchronous operation.

QueryFirstOrDefaultAsync<T>()

public async Task<Movie?> GetMovieByTitle(string title)
{
    using var connection = _factory.CreateConnection();

    return await connection.QueryFirstOrDefaultAsync<Movie>(
        "SELECT * FROM \"Movie\" WHERE \"Title\" = @Title",
        new { Title = title }
    );
}

As the name suggests, it returns the first row that matches the condition, or otherwise returns the default value. A similar sync method is QueryFirstOrDefault.

QuerySingleAsync<T>()

public  async Task<Movie> GetMovieById(int id)
{
    using var connection = _factory.CreateConnection();

    return await connection.QuerySingleAsync<Movie>(
        "SELECT * FROM \"Movie\" WHERE \"Id\" = @Id",
        new { Id = id }
    );
}

QuerySingleAsync or QuerySingle (sync version) expects exactly one row to match the condition. If fewer or more are found, it throws an exception. QuerySingle is ideal when filtering a record by ID, as ID does not duplicate.

QuerySingleOrDefaultAsync<T>()

public  async Task<Movie?> GetMovieSafe(int id)
{
    using var connection = _factory.CreateConnection();

    return await connection.QuerySingleOrDefaultAsync<Movie>(
        "SELECT * FROM \"Movie\" WHERE \"Id\" = @Id",
        new { Id = id }
    );
}

Returns one record matched or null. Use QuerySingleOrDefault if you want non-async.

ExecuteAsync

public  async Task<int> InsertMovie(Movie movie)
{
    using var connection = _factory.CreateConnection();

    return await connection.ExecuteAsync(
        @"INSERT INTO ""Movie"" (""Title"", ""director_id"", ""rating"")
  VALUES (@Title, @DirectorId, @Rating)",
        movie
    );
}
public async Task<int> UpdateMovieAsync(Movie movie)
{
    using var connection = _factory.CreateConnection();

    return await connection.ExecuteAsync(
        @"UPDATE ""Movie"" 
      SET ""Title"" = @Title, ""Rating"" = @Rating
      WHERE ""Id"" = @Id",
        movie
    );
}

ExecuteAsync runs the command and returns the number of rows affected. Specifically, this and its Execute methods are used for Insert, Update, and Delete operations.

ExecuteScalarAsync

public async Task<int> GetMovieCount()
{
    using var connection = _factory.CreateConnection();

    return await connection.ExecuteScalarAsync<int>(
        "SELECT COUNT(*) FROM \"Movie\""
    );
}

ExecuteScalarAsync or ExecuteScalar returns a single value as a result of SQL aggregation functions such as COUNT(), AVG(), and SUM().

QueryMultipleAsync

public async Task<(IEnumerable<Movie>, IEnumerable<Director>)> GetDashboard()
{
    using var connection = _factory.CreateConnection();

    var sql = @"
        SELECT * FROM ""Movie"";
        SELECT * FROM ""Director"";
    ";

    using var multi = await connection.QueryMultipleAsync(sql);

    var movies = multi.Read<Movie>();
    var directors = multi.Read<Director>();

    return (movies, directors);
}

The method is complex, running multiple commands from a single command. QueryMultiple is the sync version of it. Instead of hitting the database multiple times, QueryMultiple lets you fetch multiple datasets in a single round-trip.

Step 8: Set up Program.cs

using UserDapperDemo.Models;
using UserDapperDemo.Data;
using UserDapperDemo.Data.Repos;

var factory = new DbConnectionFactory();

var repo = new MovieRepo(factory);

// INSERT
repo.InsertMovie(new Movie
{
    Title = "Inception",
    DirectorId = 1,
    Rating = 9.0
});

Console.WriteLine("=== ALL MOVIES ===");
// QUERY
var movies = await repo.GetAllMoviesAsync();

foreach (var m in movies)
{
    Console.WriteLine($"Id: {m.Id}, Title: {m.Title}, Rating: {m.Rating}");
}

Console.WriteLine("\n=== SINGLE MOVIE (QueryFirst) ===");
// SINGLE
var movie1 = await repo.GetFirstMovie();

Console.WriteLine($"Id: {movie1.Id}, Title: {movie1.Title}, Rating: {movie1.Rating}");

Console.WriteLine("\n=== SINGLE MOVIE (QueryFirstOrDefault) ===");
// SINGLE
var movie2 = await repo.GetMovieByTitle("Memento");

Console.WriteLine($"Id: {movie2.Id}, Title: {movie2.Title}, Rating: {movie2.Rating}");

Console.WriteLine("\n=== SINGLE MOVIE (QuerySingle) ===");
// SINGLE
var movie3 = await repo.GetMovieById(1);

Console.WriteLine($"Id: {movie3.Id}, Title: {movie3.Title}, Rating: {movie3.Rating}");

Console.WriteLine("\n=== SINGLE MOVIE (QuerySingleOrDefault) ===");
// SINGLE
var movie4 = await repo.GetMovieSafe(1);

Console.WriteLine($"Id: {movie4.Id}, Title: {movie4.Title}, Rating: {movie4.Rating}");

Console.WriteLine("\n=== MOVIE COUNT (ExecuteScalar) ===");
// COUNT
var count = await repo.GetMovieCount();
Console.WriteLine($"Total Movies: {count}");

Console.WriteLine("\n=== DASHBOARD (QueryMultiple) ===");
// MULTIPLE
var (allMovies, directors) = await repo.GetDashboard();
foreach (var m in allMovies)
{
    Console.WriteLine($"Id: {m.Id}, Title: {m.Title}, Rating: {m.Rating}");
}

foreach (var d in directors)
{
    Console.WriteLine($"Id: {d.Id}, Name: {d.Name}");
}

Here we are using movieRepo to call all of its methods.

Step 9: Run and test

dotnet run
Result
Result

Advanced Dapper Features

We have seen some of the most common dapper methods till now. However, Dapper offers ways to tackle them as well for complex scenarios.

Working with transactions

When inserting or updating data, an exception in between the operation can leave the database inconsistent. Transactions rescue and atomize the data manipulation operations as one, either the whole block executes or fails and rolls back. Let's see how we can do it in Dapper.

public async Task CreateMovieWithDirector(Movie movie, string directorName)
{
    using var connection = _factory.CreateConnection();
    connection.Open();

    using var transaction = connection.BeginTransaction();

    try
    {
        var directorId = await connection.ExecuteScalarAsync<int>(
            @"INSERT INTO ""Director"" (""Name"")
          VALUES (@Name)
          RETURNING ""Id"";",
            new { Name = directorName },
            transaction
        );

        movie.DirectorId = directorId;

        await connection.ExecuteAsync(
            @"INSERT INTO ""Movie"" (""Title"", ""DirectorId"", ""Rating"")
          VALUES (@Title, @DirectorId, @Rating)",
            movie,
            transaction
        );

        transaction.Commit();
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}

I opened a transaction on the connection, then we first created the director before the movie, as the movie depends on the directorId. At last transaction.Commit() commits the transaction to write everything in the database, while the catch block rolls back the transaction using transaction.Rollback(). Note that I am using a parameterized query to keep the database safe from SQL injection attacks and unwanted data.

In Program.cs:

Console.WriteLine("\n=== Transaction ===");
await repo.CreateMovieWithDirector(
    new Movie()
    {
        Title = null,
        Rating = 9,
    },
    "Martin Scorsese"
);

I intentionally kept the title null, as per the repo method, the director will be added successfully, but during Movie creation, there is an error. The transaction will roll back the director insertion when the second half fails as well.

Exception

Upon correct values it will be done:

new Movie()
    {
        Title = "The Departed",
        Rating = 9
    },

Using Stored Procedures/Functions

Stored procedures and functions encapsulate complex logic and provide reusable calls. For such detailed operations, you should not write a raw query spanning dozens of lines in the code that can be difficult to maintain and organize. The best way is to abstract into functions or procedures. Let's consider a function

CREATE FUNCTION public.get_movies_by_director(
	p_director_id integer)
    RETURNS TABLE("Id" integer, "Title" text, "Rating" numeric) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
BEGIN
    RETURN QUERY
    SELECT m."Id", m."Title", m."Rating"
    FROM "Movie" m
    WHERE m."DirectorId" = p_director_id;
END;
$BODY$;

repo method that calls the function

public async Task<IEnumerable<Movie>> GetMoviesByDirector(int directorId)
{
    using var connection = _factory.CreateConnection();

    return await connection.QueryAsync<Movie>(
        @"SELECT * FROM get_movies_by_director(@DirectorId)",
        new { DirectorId = directorId }
    );
}
var moviesByDirector = await repo.GetMoviesByDirector(1);

foreach (var m in moviesByDirector)
{
    Console.WriteLine($"{m.Id} - {m.Title} - {m.Rating}");
}

Result:

Result

Bulk Operations with Dapper

Bulk operations are important features supported by Dapper. When inserting a large amount of data, you do not want to hit the database separately for each record, nor should you. SQL allows bulk insertion or update at once, and you can run the query with ExecuteAsync.

public async Task BulkInsertMovies(IEnumerable<Movie> movies)
{
    using var connection = _factory.CreateConnection();

    await connection.ExecuteAsync(
        @"INSERT INTO ""Movie"" (""Title"", ""DirectorId"", ""Rating"")
          VALUES (@Title, @DirectorId, @Rating)",
        movies
    );
}

Program.cs call

await repo.BulkInsertMovies(new List<Movie>
{
    new Movie { Title = "Batman Begins", DirectorId = 1, Rating = 8.2 },
    new Movie { Title = "The Dark Knight", DirectorId = 1, Rating = 9.0 }
});

Opt between Buffered and Unbuffered Query

By default, Dapper loads everything into the buffer memory. The behavior can be problematic with large datasets, so choose unbuffered queries that stream results rather than storing them in memory.

var movies = await connection.QueryAsync<Movie>(
    sql,
    buffered: false
);

Multi-Mapping (Handling JOIN Queries)

JOINs are a common way to get data from multiple tables in SQL. That common feature is workable using Dapper as well. With the same QueryAsync you can run queries with JOIN commands.

public async Task<IEnumerable<MovieWithDirector>> GetMoviesWithDirectors()
{
    using var connection = _factory.CreateConnection();

    var sql = @"
    SELECT 
        m.""Id"", 
        m.""Title"", 
        m.""Rating"",
        d.""Name"" AS ""DirectorName""
    FROM ""Movie"" m
    JOIN ""Director"" d ON m.""DirectorId"" = d.""Id"";
";

    var result = await connection.QueryAsync<MovieWithDirector>(sql);

    return result;
}

We need a model to get the results

namespace UserDapperDemo.Models;

public class MovieWithDirector
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public double Rating { get; set; }
    public string DirectorName { get; set; } = string.Empty;
}

Simply printing them all after calling the repo method

var moviesWithDirectors = await repo.GetMoviesWithDirectors();

foreach (var m in moviesWithDirectors)
{
    Console.WriteLine($"{m.Id} - {m.Title} - {m.Rating} by {m.DirectorName}");
}

Result

Result

Best Practices When Using Dapper

Following some practices can make Dapper work better than usual. These ways make the application fast, maintainable, and secure.

Use Parameterized Queries

That is general advice to always use parameters to prevent SQL injection attacks. Apart from security, some special characters, such as "," and")", can cause errors when inserting or updating string values.

Prefer Async Methods

To avoid thread block and support asynchronous, go with async versions like QueryAsync and ExecuteAsync. Applications usually run different threads in parallel, preferring async methods in Dapper.

Organize SQL Queries

Good architecture makes a project maintainable. You should handle data access as well when using Dapper. Repository pattern is a preferable choice however choose as per your requirement. Keeping long queries in files will also save mess in the code.

Manage Connections Efficiently

Database connections are like portals to the database. Open and close them the right way with using statements or connection factories to ensure that connections open only once and are properly closed after each query.

Avoid Business logic in the SQL queries

Validate the inputs and other business rules before passing them to SQL queries. This avoids unnecessary checks on SQL and keeps the single responsibility in the data access layer.on SQL and keep the single responsibility on the data access layer.

Avoid checks like

CASE WHEN rating > 5 THEN 'Good' ELSE 'Bad'

It is generally good to keep the repository or other data access separate from business rules.

Cache frequently-accessed data

Database operations are always costly, try to reduce them as much as possible. Caching is one of the best ways to reduce database processing and delays. First, identify which data are most requested or don't change frequently. Set up caching and save them to provide quick access to users, rather than requesting the same data from the dataset every time.

You can follow some generic recommendations for the application apart from Dapper.

Conclusion

We rediscovered Dapper from its basic methods to its advanced features. Later, we reviewed some recommendations for using Dapper. Like any other feature, Dapper has its own world and definitions. I defined its commonly used methods and some of the advanced features. I shared a real example of how those methods and features address most database requirements. By following the tips in the article, you can scale up the use of the high-performance library.

Code: https://github.com/elmahio-blog/MovieDapperDemo.git



Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Be Ready: GitHub Copilot Pricing & Usage Changes Will Hit Hard

1 Share
We have been in the honeymoon period with GitHub Copilot over the last 12 months, amazing functionality, huge improvements in productivity, and the ability to do things that are just amazing. That all came with some really, really low pricing, but that is all about to change, and the method of that change is still very much up in the air, but it is something that we need to plan for, and be ready!
Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

.NET 11 Preview 4

1 Share

We are pleased to announce the release of .NET 11 Preview 4. Follow the main announcement and blog announcement.

Browse the release notes for a full list of improvements in this release:

Get started

To get started with .NET 11, install the .NET 11 SDK.

If you're on Windows using Visual Studio, we recommend installing the latest Visual Studio 2026 Insiders. You can also use Visual Studio Code and the C# Dev Kit extension with .NET 11.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

iOS 27 might add a lot more customization to the Camera app

1 Share
The Camera Control button on an iPhone 16 Pro being used to change settings.

Apple's next iOS update could include something phone photographers have been waiting for: a lot more control over the Camera app. According to Bloomberg's Mark Gurman, the Camera app will be "fully customizable" in iOS 27 and users will be able to "pick their own set of controls - called widgets - that run along the top of the interface."

Gurman notes that the Camera app will keep the current set of default widgets, but starting with iOS 27, you will "be able to switch over to an 'advanced' array of options or choose their own items." From there, you'll reportedly be able to select from a new "Add Widgets" tray, with widgets split into thr …

Read the full story at The Verge.

Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories