DEV Community

Masui Masanori
Masui Masanori

Posted on

1 1

【Entity Framework Core】Raw SQL Queries

Intro

To improve performance, I sometimes use Raw SQL Queries.

In ASP.NET Framework(Entity Framework 6), I can do this.

using (var context = new BloggingContext())
{
    context.Database.ExecuteSqlCommand(
        "UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");
}
Enter fullscreen mode Exit fullscreen mode

But in ASP.NET Core, I can't use "ExecuteSqlCommand".

So in this time, I try it.

Environments

  • .NET ver.5.0.100-rc.1.20452.10
  • Microsoft.EntityFrameworkCore ver.5.0.0-rc.1.20451.13
  • Npgsql.EntityFrameworkCore.PostgreSQL ver.5.0.0-rc1
  • NLog.Web.AspNetCore ver.4.9.3

Execute Raw SQL Queries

I only can execute Raw SQL Queries through "DbSet".

...
var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser {0}", user)
    .ToList();
Enter fullscreen mode Exit fullscreen mode

But how about projection class?

For example, I want to get data by this SQL query.

SELECT c."Id", c."Name" AS "CompanyName", b."Id" AS "BookId", b."Name" AS "BookName", 
b."PublishDate", b."GenreId", b."Price" FROM "Companies" c 
    INNER JOIN LATERAL(SELECT * FROM "Books" innerb
        WHERE innerb."CompanyId" = c."Id" ORDER BY innerb."Id" DESC LIMIT 1) b
    ON c."Id" = b."CompanyId"
Enter fullscreen mode Exit fullscreen mode

And I want to set into this class.

SearchedCompany.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace BookStoreSample.Books
{
    public class SearchedCompany
    {
        [Key]
        public int CompanyId { get; set; }
        public string CompanyName { get; set; } = "";
        public int BookId { get; set; }
        public string BookName { get; set; } = "";
        public DateTime? PublishDate { get; set; }
        public int GenreId { get; set; }
        public decimal? Price { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

I thought classes in "DbSet<T>" should match a table in the database.
But I can also use projection classes like "SearchedCompany".

BookStoreContext.cs

using BookStoreSample.Books;
using Microsoft.EntityFrameworkCore;

namespace BookStoreSample.Models
{
    public class BookStoreContext: DbContext
    {
...
        public DbSet<SearchedCompany>  SearchedCompanies => Set<SearchedCompany>();
    }
}
Enter fullscreen mode Exit fullscreen mode

So I can execute SQL query like below.

BookSearchSample.cs

...
        public async Task<List<SearchedCompany>> SearchCompaniesAsync()
        {
            var sql = "SELECT c.\"Id\", c.\"Name\" AS \"CompanyName\", " +
                "b.\"Id\" AS \"BookId\", b.\"Name\" AS \"BookName\", " +
                "b.\"PublishDate\", b.\"GenreId\", b.\"Price\" FROM \"Companies\" c " +
                "INNER JOIN LATERAL(SELECT * FROM \"Books\" innerb " +
                "WHERE innerb.\"CompanyId\" = c.\"Id\" ORDER BY innerb.\"Id\" DESC LIMIT 1) b " +
                "ON c.\"Id\" = b.\"CompanyId\" ";
            return await _context.SearchedCompanies.FromSqlRaw(sql)
                .ToListAsync();
        }
...
Enter fullscreen mode Exit fullscreen mode

One important thing is the class must have "[Key]" or a property what is named "Id".

Skip Raw SQL Queries?

If I remove "FromSqlRaw" from the sample, can I get empty result?

...
    public async Task<List<SearchedCompany>> SearchCompaniesAsync()
    {
        // Don't do this
        return await _context.SearchedCompanies
            .ToListAsync();
    }
...
Enter fullscreen mode Exit fullscreen mode

The answer is getting an exception.

2020-10-10 07:04:17.1997|13|ERROR|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HM3CJT1I07AV", Request id "0HM3CJT1I07AV:00000002": An unhandled exception was thrown by the application. Npgsql.PostgresException (0x80004005): 42P01: relation "SearchedCompanies" does not exist
   at Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
...
Enter fullscreen mode Exit fullscreen mode

Except their names, I still don't know how can I distinguish them.

// OK
var companies = await _context.Companies
    .ToListAsync();
// Exception
var searchedCompanies = await _context.SearchedCompanies
    .ToListAsync();
Enter fullscreen mode Exit fullscreen mode

Where clauses

Of cource I can add where clauses into "sql" as string.
But it can cause SQL injection.

Because "FromSqlRaw" also can use Linq, so if the where clauses aren't very complecated, I think I shall use Linq to add them.

BookSearchSample.cs

...
        public async Task<List<SearchedCompany>> SearchCompaniesAsync()
        {
            var sql = "SELECT c.\"Id\", c.\"Name\" AS \"CompanyName\", " +
                "b.\"Id\" AS \"BookId\", b.\"Name\" AS \"BookName\", " +
                "b.\"PublishDate\", b.\"GenreId\", b.\"Price\" FROM \"Companies\" c " +
                "INNER JOIN LATERAL(SELECT * FROM \"Books\" innerb " +
                "WHERE innerb.\"CompanyId\" = c.\"Id\" ORDER BY innerb.\"Id\" DESC LIMIT 1) b " +
                "ON c.\"Id\" = b.\"CompanyId\" ";
            return await _context.SearchedCompanies.FromSqlRaw(sql)
                .Where(c => c.CompanyId == 1)
                .ToListAsync();
        }
...
Enter fullscreen mode Exit fullscreen mode

According to logs, ".Where(c => c.CompanyId == 1)" was also converted into SQL queries.

Generated SQL queries

SELECT s."Id", s."BookId", s."BookName", s."CompanyName", s."GenreId", s."Price", s."PublishDate"
FROM (
    SELECT c."Id", c."Name" AS "CompanyName", b."Id" AS "BookId", b."Name" AS "BookName", b."PublishDate", b."GenreId", b."Price" FROM "Companies" c INNER JOIN LATERAL(SELECT * FROM "Books" innerb WHERE innerb."CompanyId" = c."Id" ORDER BY innerb."Id" DESC LIMIT 1) b ON c."Id" = b."CompanyId" 
) AS s
WHERE s."Id" = 1
Enter fullscreen mode Exit fullscreen mode

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay