Entity Framework Core 8 packs a punch, aiming for the undisputed title of “most powerful Object-Relational Mapper (ORM)” in the .NET space. Many folks in our community are typically divided about using ORMs, with many preferring Micro-ORMs. A Micro-ORM has basic mapping functionality from SQL results but lacks object-tracking features that make ORMs what they are.

What Micro-ORMs lack in object-tracking, they make up for in simplicity and functionality. The most popular library is Dapper, which allows folks to write SQL, execute the query, and map the results to any .NET Object. Well, with Entity Framework Core 8, you can have Dapper-like functionality with the strengths of EF Core.

This post will be short, but for most folks, this is a public service announcement to improve performance and tune existing code paths. Let’s get started.

What’s this about Mapping Any Object?

Before the EF Core 8 release, all entities that were to be queried from a DbContext had to be registered within the implementation. These types are typically referenced as a DbSet<T>. Anyone who’s worked with a database understands that SQL allows us to create any result we like, even though those entities might not directly exist in our schema. This disadvantaged EF Core compared to Dapper, which lets you craft any SQL statements you want and map them to C# objects.

In EF Core 8, you can now use SqlQuery<T> to pass in any SQL statement and map the results to a C# object.

var result = await db
    .Database
    .SqlQuery<NamedResult>($"Select Id, Name from Customers")
    .FirstOrDefaultAsync();

Console.WriteLine($"NamedResult: {result}");

record NamedResult(int Id, string Name);

Awesome! As a bonus, you can apply additional LINQ methods to your existing SQL statement.

var result = await db
    .Database
    .SqlQuery<NamedResult>($"Select Id, Name from Customers")
    .Where(c => c.Id == 1)
    .FirstOrDefaultAsync();

Console.WriteLine($"NamedResult: {result}");

record NamedResult(int Id, string Name);

The EF Core generates an SQL statement that uses a nested select to keep your original query intact while adding additional filtering.

SELECT "n"."Id", "n"."Name"
FROM (
  Select Id, Name from Customers
) AS "n"
WHERE "n"."Id" = 1
LIMIT 1

There you have it. I hope you enjoyed this short post. I’m glad to see this feature as part of EF Core 8 finally. It’s been a long time in the making, and thanks to the EF Core team for making it happen.

As always, thanks for reading and sharing my posts. Cheers.