Monthly Archives: September 2020

Czytanie nagłówków żądania jako obiekt w ASP.NET Core

Odczytywanie nagłówków jest standardową operacją w ASP.NET Core i jest używane od wieków. Napisałem nawet post podsumowujący wszystkie metody przekazywania parametrów: ASP.Net Core in .NET 5 – przekazywanie parametrów do akcji. W ASP.NET Core wprowadzono przydatne atrybuty do obsługi parametrów w metodach kontrolera, takich jak [FromQuery] lub [FromHeader]. Ale czy istnieje sposób na użycie tych atrybutów i odczytanie nagłówków jako własny obiekt? Zobaczmy.

Tak wygląda standardowa metoda kontrolera.

    // POST: weatherForecast/
    [HttpPost]
    public IActionResult Post([FromBody] WeatherForecast forecast, [FromHeader] string parentRequestId)
    {
        try
        {
            Console.WriteLine($"Got a forecast for data: {forecast.Date} with parentRequestId: {parentRequestId}!");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
            
        return new AcceptedResult();
    }

W tym przykładzie [FromBody] oznacza, że forecast zostanie odwzorowana na obiekt z treści żądania, a [FromHeader] oznacza, że parentRequestId zostanie pobrany z nagłówka. To działa świetnie, ale jak zmapować więcej nagłówków, najlepiej jako osobny obiekt?

Spójrzmy na ten kod:

    // POST: weatherForecast/multipleHeaders
    [HttpPost("multipleHeaders")]
    public IActionResult Post([FromHeader] ForecastHeaders forecastHeaders)
    {
        try
        {
            Console.WriteLine($"Got a forecast for city: {forecastHeaders.City}," +
                                $"temperature: {forecastHeaders.TemperatureC} and" +
                                $"description: {forecastHeaders.Description}!");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }

        return new AcceptedResult();
    }

AForecastHeaders wygląda tak:

    public class ForecastHeaders
    {
        [FromHeader]
        public string City { get; set; }

        [FromHeader]
        public int TemperatureC { get; set; }

        [FromHeader]
        public string Description { get; set; }

        [FromQuery]
public string Sorting { get; set; } }

Czy zauważyłeś, że użyłem [FromHeader] zarówno w deklaracji parametrów metody kontrolera, jak i w mojej własnej klasie?

Teraz prześlijmy żądanie z Postmana.

Wynik? Czy Ciebie zdziwił tak samo jak mnie? 😀

Zadziałało!

Wszystkie nagłówki zostały poprawnie zmapowane jako obiekt. Zwróć uwagę, że sortowanie również zostało zmapowane, nawet jeśli pochodzi z parametru zapytania, a nie z nagłówka. Udowadnia to to, że możesz połączyć te dwa podejścia, jeśli w ogóle ma to sens.

Cały zamieszczony tutaj kod jest dostępny na moim GitHub – sprawdź.

Nie wiem tylko czy to błąd czy ficzer… ale mi się podoba! ❤️

Read request headers as an object in ASP.Net Core

Reading headers is a standard operation in ASP.NET Core and has been around for ages. I even wrote a post summarizing all methods of passing parameters: ASP.NET Core in .NET 5 – pass parameters to actions. ASP.NET Core introduced handy attributes to handle parameters in controller methods, like [FromQuery] or [FromHeader]. But is there a way to use those attributes and read headers as a custom object? Let’s see.

This is how a standard controller method looks like.

    // POST: weatherForecast/
    [HttpPost]
    public IActionResult Post([FromBody] WeatherForecast forecast, [FromHeader] string parentRequestId)
    {
        try
        {
            Console.WriteLine($"Got a forecast for data: {forecast.Date} with parentRequestId: {parentRequestId}!");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
            
        return new AcceptedResult();
    }

In this example [FromBody] means that forecast will be mapped into an object from the request body, and [FromHeader] means that parentRequestId will be taken from the header. That works great, but how to map more headers, preferrable as a separate object?

Let’s take a look at this code:

    // POST: weatherForecast/multipleHeaders
    [HttpPost("multipleHeaders")]
    public IActionResult Post([FromHeader] ForecastHeaders forecastHeaders)
    {
        try
        {
            Console.WriteLine($"Got a forecast for city: {forecastHeaders.City}," +
                                $"temperature: {forecastHeaders.TemperatureC} and" +
                                $"description: {forecastHeaders.Description}!");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }

        return new AcceptedResult();
    }

And ForecastHeaders looks like this:

    public class ForecastHeaders
    {
        [FromHeader]
        public string City { get; set; }

        [FromHeader]
        public int TemperatureC { get; set; }

        [FromHeader]
        public string Description { get; set; }

        [FromQuery]
public string Sorting { get; set; } }

Have you noticed, that I used [FromHeader] in both controller method parameter declaration and inside my custom class?

Now let’s make a request with Postman.

And result? Are you surprised as I was? 😀

It worked!

All headers were mapped correctly, as a custom object. Notice that Sorting was also mapped, even if it comes from the query parameter, not the header. It proves you can combine those two if that makes sense.

All of it is available in my GitHub – check it out.

I’m not sure if it’s a bug or a feature… but I like it! ❤️

Nie przekacuj parametrów w ten sposób w Entity Framework Core 5

Niedawno napisałem post o wykonywaniu poleceń SQL w Entity Framework Core 5: Wykonanie polecenia SQL w Entity Framework Core 5. Jeden z czytelników zauważył, że popełniłem duży błąd podczas przekazywania parametrów. Przyjrzyjmy się bliżej.

Napisałem taki kod:

    [HttpPost("UpdateProfiles")]
    public async Task<IActionResult> UpdateProfiles([FromBody] int minimalProfileId = 0)
    {
        await primeDbContext.Database.ExecuteSqlRawAsync(
            $"UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > {minimalProfileId}");

        return Ok();
    }

Ta metoda aktualizuje pole Country w profilu na wartość Poland, jeżeli numer telefonu zaczyna się od 48, a identyfikator jest wyższy niż podany. Zauważ, że użyłem ExecuteSqlRawAsync i podałem interpolowany ciąg, w którym przekazuję minimalProfileId. Więc gdzie jest błąd?

Podczas przekazywania parametrów do SQL należy być bardzo ostrożnym. Zwłaszcza gdy przekazujesz dane dostarczone przez użytkownika, jesteś narażony na atak typu SQL injection. Aby tego uniknąć, należy używać metod FromSqlInterpolated lub ExecuteSqlInterpolated. Zmiany w kodzie są minimalne:

    [HttpPost("UpdateProfiles")]
    public async Task<IActionResult> UpdateProfiles([FromBody] int minimalProfileId = 0)
    {
        await primeDbContext.Database.ExecuteSqlInterpolatedAsync(
            $"UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > {minimalProfileId}");

        return Ok();
    }

Nieźle, co? Nadal mogę przekazać interpolowany ciąg, ale z metodą ExecuteSqlInterpolatedAsync robię to w sposób bezpieczny. Użycie tej metody pozwala przesłać parametry osobno, przez co .Net Core je sprawdzi, czy nie zawierają nieprawidłowych znaków lub wyrażeń. Więcej na ten temat wyczytasz na stronie Microsoft.

Mam nadzieję, że podobał Ci się ten post i bądź bezpieczny 🙂

Cały zamieszczony tutaj kod dostępny jest też na moim GitHub. Zaglądaj!

How not to pass parameters in Entity Framework Core 5

Recently I wrote a post about executing raw SQL scripts in Entity Framework Core 5: Executing raw SQL with Entity Framework Core 5. One of the readers noticed that I did a big mistake when passing parameters. Let’s take a closed look.

I had code like this:

    [HttpPost("UpdateProfiles")]
    public async Task<IActionResult> UpdateProfiles([FromBody] int minimalProfileId = 0)
    {
        await primeDbContext.Database.ExecuteSqlRawAsync(
            $"UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > {minimalProfileId}");

        return Ok();
    }

This method updates profiles Country to Poland, where the phone number starts with 48, and an Id is higher than provided. Notice that I used ExecuteSqlRawAsync and provided an interpolated string, where I pass minimalProfileId. So where’s the catch?

When passing parameters to SQL you have to be super cautious. Especially when you’re passing a user-provided data, you are exposed to SQL injection attack. In order to avoid that, you should use FromSqlInterpolated or ExecuteSqlInterpolated methods. Changes to the code are minimal:

    [HttpPost("UpdateProfiles")]
    public async Task<IActionResult> UpdateProfiles([FromBody] int minimalProfileId = 0)
    {
        await primeDbContext.Database.ExecuteSqlInterpolatedAsync(
            $"UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > {minimalProfileId}");

        return Ok();
    }

Nice huh? I can still pass an interpolated string, but with ExecuteSqlInterpolatedAsync I’m safe. Using this method allows parameters to be passed separately, causing .Net Core to check them for invalid characters or expressions. You can read more about it on the Microsoft website.

Hope you liked this post, stay safe 🙂

All code posted here can be found on my GitHub account. Enjoy!

Wykonanie polecenia SQL w Entity Framework Core 5

Entity Framework Core 5 to lekki, rozszerzalny i wieloplatformowy ORM typu open source. Jest łatwy w zastosowaniu i sprawia, że dostęp do bazy danych jest bardzo prosty. Jednak czasami praca z tabelami i widokami po prostu nie wystarczy. Jak wykonać surowy skrypt SQL za pomocą Entity Framework Core 5? Dowiedzmy Się.

Wykonanie polecenia SQL

Uruchomianie SQL bez mapowania wyniku jest dość proste. Spójrz na ten przykład:

var sql = "UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > @p0";
await primeDbContext.Database.ExecuteSqlRawAsync(sql, parameters: new[] { minimalProfileId.ToString() });

Ten kod SQL aktualizuje kolumnę Country na podstawie TelNo dla profili z identyfikatorem wyższym niż podany. To tylko kilka linijek kodu i działa idealnie! Pokazuje również, jak możemy przekazać parametr do polecenia SQL, ale możesz także sformatować go za pomocą nawiasów klamrowych.

Wykonanie procedury składowanej

Procedura składowana jest doskonałym przykładem kodu SQL, który możesz chcieć uruchomić bezpośrednio w bazie danych, ale zachować SQL po stronie bazy danych. Załóżmy, że masz już procedurę składowaną o nazwie UpdateProfilesCountry z jednym parametrem. Jeśli chcesz go po prostu wykonać, możesz napisać taki kod:

await primeDbContext.Database.ExecuteSqlRawAsync(
    "UpdateProfilesCountry @p0",
    parameters: new[] { minimalProfileId.ToString() });

Nie musimy mapować wyników, więc możemy użyć metodyDbContext.Database.ExecuteSqlRawAsync i przekazać potrzebne parametry.

Jeżeli interesuje Cię ten proces dokładnie, na przykładzie, to opisałem to wszystko tutaj: Wykonanie procedury składowanej w Entity Framework Core.

Wykonanie procedury składowanej z wynikiem

Podczas gdy procedura składowana lub zwykłe polecenie SQL można wykonać bezpośrednio na poziomie bazy danych, zwrócenie wyniku jest nieco bardziej skomplikowane. Przede wszystkim musisz dodać model nie posiadający klucza, aby zmapować wyniki. Powiedzmy, że mamy procedurę składowaną GetGuestsForDate i chcielibyśmy zwrócić model o nazwie GuestArrival.

[Keyless]
public class GuestArrival
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public string TelNo { get; set; }

    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }
}

Musimy także dodać kolekcję do naszej klasy PrimeDbContext.

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    // from stored procedures
    public virtual DbSet<GuestArrival> GuestArrivals { get; set; }
}

Teraz możemy iść dalej i użyć go. Oto, jak możemy to osiągnąć:

var guests = primeDbContext.GuestArrivals.FromSqlRaw($"GetGuestsForDate '{date}'").ToList();

A jeśli umieszczę go w moim projekcie API, zmapuje wyniki na encje:

Zauważ, że aby odwzorować wyniki na obiekty, musimy użyć kolekcji DbSet.

Jeśli chcesz uzyskać pełny obraz, przeczytaj osobny artykuł: Pobranie danych za pomocę procedury składowanej w Entity Framework Core 5

Podsumowanie

Wykonanie polecenia SQL w Entity Framework Core 5 jest nie tylko możliwe, ale także zaskakująco łatwe. Możesz wykonywać SQL na poziomie bazy danych, ale jeśli zależy Ci na zwracanym rezultacie, musisz dodać DbSet reprezentujący Twoje wyniki.

Cały zamieszczony tutaj kod można znaleźć na moim GitHubie, czeka żeby z nim poeksperymentować 🙂

Pozdrawiam!

Executing raw SQL with Entity Framework Core 5

Entity Framework Core 5 is an open-source, lightweight, extensible, and a cross-platform ORM. It is easy to apply and it makes database access super simple. However, sometimes working with tables and views is just not enough. How to execute raw SQL script with Entity Framework Core 5? Let’s find out.

Running the raw SQL

Running a SQL without carrying about the result is quite easy. Have a look at this example:

await primeDbContext.Database.ExecuteSqlInterpolatedAsync(
$"UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > {minimalProfileId}");

This SQL updates the Country based on TelNo column for profiles with Id higher then the one provided. It is just a few lines of code and it works perfectly! It also shows how we can pass a parameter to SQL, but you also can format it with curly braces.

Executing a stored procedure

A stored procedure is a perfect example of a SQL, that you might want to run directly on the database, but keep SQL on the database side. Let’s say you already have a stored procedure named UpdateProfilesCountry with one parameter. If you would just like to execute it, you could simply have a code like this:

await primeDbContext.Database.ExecuteSqlInterpolatedAsync(
    $"UpdateProfilesCountry {minimalProfileId}");

You don’t need a DbSet to map the results, so you can use DbContext.Database.ExecuteSqlRawAsync and pass parameters if you’d like to.

If you’d like to get the full picture, read the separate article: Execute a stored procedure with Entity Framework Core 5.

Running a stored procedure with a result

While running a stored procedure or a plain SQL command can be done directly on the database level, returning the result is slightly more complicated. First of all, you need to add a keyless model to map your results. Let’s say we have a stored procedure GetGuestsForDate and we would like to return a model named GuestArrival.

[Keyless]
public class GuestArrival
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public string TelNo { get; set; }

    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }
}

We also need to add a DbSet to our PrimeDbContext.

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    // from stored procedures
    public virtual DbSet<GuestArrival> GuestArrivals { get; set; }
}

Now we can go ahead and use it. Here is how we can accomplish that:

var guests = primeDbContext.GuestArrivals.FromSqlInterpolated($"GetGuestsForDate '{date}'").ToList();

And if I place it in my API project, it will map results to entities:

Notice that to map results to objects, we need to use DbSet collection.

If you’d like to get the full picture, read the separate article: Select data with a stored procedure with Entity Framework Core 5

Summary

It is not only possible but also surprisingly easy to run any SQL on the database with Entity Framework Core 5. You can execute SQL on a database level, but when you care about the result, you need to add a DbSet representing your results.

All code mentioned here can be found on my GitHub, feel free to experiment with it.

Cheers!

 

Widoki w Entity Framework Core 5

Widok w kontekście baz danych jest wirtualną tabelą opartą na zestawie wyników uzyskanych przez wykonanie zapytaniem SQL. Są one zwykle używane jako obiekty tylko do odczytu, które są zoptymalizowane pod kątem dostarczania danych dla danego scenariusza. Entity Framework Core 5 obsługuje widoki, a w tym artykule pokażę, jak to działa.

Dodanie widoku

Pierwszą rzecz jaką musimy zrobić, to dodać widok do bazy danych. Najlepszym sposobem na to jest dodanie migracji bazy danych z odpowiednim kodem SQL. Zacznijmy od dodania migracji za pomocą polecenia narzędzia globalnego EF Core:

dotnet ef migrations add vwGuestArrivals

To polecenie wygeneruje migrację, w której możemy umieścić nasz SQL. Zobaczmy, jak to może wyglądać:

public partial class vwGuestArrivals : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        var sql = @"
            CREATE OR ALTER VIEW [dbo].[vwRoomsOccupied] AS
                SELECT r.[From], r.[To], ro.Number As RoomNumber, ro.Level, ro.WithBathroom
                FROM Reservations r
                JOIN Rooms ro ON r.RoomId = ro.Id";

        migrationBuilder.Sql(sql);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"DROP VIEW vwRoomsOccupied");
    }
}

Ten widok przedstawia zajęte pokoje, które możemy filtrować według daty. Tego rodzaju dane mogą być przydatne na przykład podczas planowania konserwacji i prac remontowych.

Pobieranie danych z widoku

W Entity Framework Core 5 widoki mogą być reprezentowane jako zwykła kolekcja DbSet. W moim przypadku, aby zmapować wszystkie kolumny widoku, musimy stworzyć model RoomOcupied, który wygląda tak:

[Keyless]
public class RoomOccupied
{
    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }

    public int Level { get; set; }

    public bool WithBathroom { get; set; }
}

Teraz musimy dodać DbSet do mojego PrimeDbContext i musimy skonfigurować nasz model, aby RoomsOccupied był wykonywany na konkretnym widoku. Zobaczmy, jak można to osiągnąć:

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    public virtual DbSet<RoomOccupied> RoomsOccupied { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<RoomOccupied>(eb =>
            {
                eb.HasNoKey();
                eb.ToView("vwRoomsOccupied");
            });
    }
}

Jak widać, jest to normalna kolekcja DbSet, na który można filtrować jak jak chcemy. Jest jednak drobiazg, który wyróżnia tę kolekcję. Zwróć uwagę, że konfigurujemy encję RoomOccupied tak, aby nie miała klucza. W ten sposób nie musimy mieć klucza w wyniku, ale oznacza to również, że byłby to model tylko do odczytu.

Obecnie Entity Framework Core 5 nie obsługuje aktualizowania widoku, chociaż jest to możliwe w bazie danych SQL Server. Możesz jednak zdefiniować widok, który posiada klucz. Pamiętaj tylko, aby usunąć HasNoKey w konfiguracji i atrybut [Keyless] w encji.

Użyjmy kodu, który właśnie napisaliśmy. Aby to zrobić w najprostszy możliwy sposób, dodałem metodę do mojego interfejsu API ASP.NET Core. Oto jak to wygląda:

[HttpGet("GetRoomsOccupied")]
public IActionResult GetGuestArrivalsFromView([FromQuery] string date)
{
    var parsedDate = DateTime.ParseExact(date, "dd-MM-yyyy", CultureInfo.InvariantCulture);
    var rooms = primeDbContext.RoomsOccupied.Where(r => r.From <= parsedDate && r.To >= parsedDate);

    return Ok(rooms);
}

Tutaj podaję datę w formacie dd-MM-rrrr i pytam o wszystkie pokoje zajęte w tym dniu. Oto wynik.

Używam vwRoomsOccupied i wykonuje zapytanie SQL z zastosowanymi wszystkimi filtrami. W SQL Server Profiler możemy spojrzeć na SQL, który został wykonany.

Zauważ, że w tym przykładzie używamy tylko dat bez czasu i wszystko działa dobrze. Jeśli jednak chcesz porównać również daty z czasem, musisz zastosować nieco inne podejście.

Podsumowanie

Entity Framework Core 5 bezbłędnie obsługuje widoki. Musisz go skonfigurować w swojej klasie DbContext i określić, że określona jednostka zostanie zamapowana na widok. Gdy to zrobisz, możesz użyć DbSet, jak chcesz, a wszystkie filtry zostaną zastosowane bezpośrednio do wygenerowanego kodu SQL.

Co więcej, możesz obsłużyć dodawanie lub aktualizowanie widoku za pomocą migracji EF Core, co oznacza, że wszystkie wymagane prace można wykonać za pomocą EF Core.

Cały pokazany tutaj kod można znaleźć na moim GitHubie, możesz spokojnie z nim eksperymentować, zachętam!

 

Working with views in Entity Framework Core 5

In SQL, a view is a virtual table based on the result-set of an SQL statement. They are typically used as read-only objects that are optimized to provide data for a given scenario. Entity Framework Core 5 can handle views and in this article, I’m going to show you how.

Adding a view

First of all, we need to add a view to the database. The best way to do so is to add a database migration with an appropriate SQL. Let’s start by adding a migration with EF Core global tool command:

dotnet ef migrations add vwGuestArrivals

This will generate a migration, that we can put our SQL into. Let’s see how it may look:

public partial class vwGuestArrivals : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        var sql = @"
            CREATE OR ALTER VIEW [dbo].[vwRoomsOccupied] AS
                SELECT r.[From], r.[To], ro.Number As RoomNumber, ro.Level, ro.WithBathroom
                FROM Reservations r
                JOIN Rooms ro ON r.RoomId = ro.Id";

        migrationBuilder.Sql(sql);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"DROP VIEW vwRoomsOccupied");
    }
}

This view presents rooms that are occupied, where we can filter by date. This kind of data can be useful for example when planning maintenance.

Getting view data

In Entity Framework Core 5 views can be represented as a regular DbSet. In my case, to map all view’s columns, we need to create a RoomOcupied model, that would look like this:

[Keyless]
public class RoomOccupied
{
    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }

    public int Level { get; set; }

    public bool WithBathroom { get; set; }
}

Now we need to add a DbSet to my PrimeDbContext and we need to configure our model, so that RoomsOccupied will be executed against view. Let’s see how that can be accomplished:

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    public virtual DbSet<RoomOccupied> RoomsOccupied { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<RoomOccupied>(eb =>
            {
                eb.HasNoKey();
                eb.ToView("vwRoomsOccupied");
            });
    }
}

As you can see it is a normal DbSet, that can be queried as we wish. However, there is a tiny detail that makes this collection different. Notice that we configure RoomOccupied entity to have no key. This way we do not need to have a key in the result, but it also means that it would only be read-only model.

Currently, Entity Framework Core 5 does not support updating the view, while it is possible in SQL Server database. You can specify a view with a key, though. Just remember to remove HasNoKey in configuration and [Keyless] attribute in the entity.

Let’s use the code that we just wrote. To do so the easiest possible way, I just added a method to my ASP.NET Core API. Here is how it looks like:

[HttpGet("GetRoomsOccupied")]
public IActionResult GetGuestArrivalsFromView([FromQuery] string date)
{
    var parsedDate = DateTime.ParseExact(date, "dd-MM-yyyy", CultureInfo.InvariantCulture);
    var rooms = primeDbContext.RoomsOccupied.Where(r => r.From <= parsedDate && r.To >= parsedDate);

    return Ok(rooms);
}

Here I’m passing date in dd-MM-yyyy format and list all occupied rooms at the given date. Here is the result.

It uses a vwRoomsOccupied and executes a SQL query with all filters applied. We can take a look in SQL Server Profiler at the SQL that was executed.

Note that in this example we are using only dates with no time and it works fine. However, if you would like to compare dates with time as well, you would need to use a slightly different approach.

Summary

Entity Framework Core 5 can handle views flawlessly. You need to configure it in your DbContext class and specify that a specific entity will be mapped to the view. When that’s done, you can use a DbSet as you wish and all filters will be applied directly into generated SQL.

What’s more, you can handle adding or updating the view with EF Core migrations, which means that all work required can be done with EF Core.

All code mentioned here can be found on my GitHub, feel free to experiment with it.

Cheers!

 

Pobranie danych za pomocę procedury składowanej w Entity Framework Core 5

Procedury składowane są integralną częścią każdej bazy danych MS SQL. Są idealne do opakowania skomplikowanego kodu SQL w obiekt bazy danych, którego możemy ponownie użyć. Jak wykonać procedurę składowaną, która zwraca dane w Entity Framework Core 5? W moim ostatnim poście: Wykonanie procedury składowanej w Entity Framework Core pokazałem, jak uruchomić procedurę składowaną. Jednak pobranie danych to już zupełnie inna historia. Spójrzmy.

Dodanie procedury składowanej

Przede wszystkim musimy dodać procedurę składowaną. Najlepszym sposobem na to jest dodanie migracji bazy danych z odpowiednim kodem SQL. Zacznijmy od dodania migracji za pomocą polecenia narzędzia globalnego EF Core:

dotnet ef migrations add spGetGuestsForDate 

To wygeneruje migrację, w której możemy umieścić nasz SQL. Zobaczmy, jak to może wyglądać:

public partial class spGetGuestsForDate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        var sql = @"
            IF OBJECT_ID('GetGuestsForDate', 'P') IS NOT NULL
            DROP PROC GetGuestsForDate
            GO

            CREATE PROCEDURE [dbo].[GetGuestsForDate]
                @StartDate varchar(20)
            AS
            BEGIN
                SET NOCOUNT ON;
                SELECT p.Forename, p.Surname, p.TelNo, r.[From], r.[To], ro.Number As RoomNumber
                FROM Profiles p
                JOIN Reservations r ON p.ReservationId = p.ReservationId
                JOIN Rooms ro ON r.RoomId = ro.Id
                WHERE CAST([From] AS date) = CONVERT(date, @StartDate, 105)
            END";

        migrationBuilder.Sql(sql);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"DROP PROC GetGuestsForDate");
    }
}

Jest to prosty kod SQL, który najpierw sprawdza, czy procedura istnieje, a jeśli tak, usuwa ją. Następnie tworzy nową procedurę o nazwie GetGuestsForDate, która pobierze wszystkich przybywających gości danego dnia.

Kiedy migracja zostanie wykonana na bazie danych, ta procedura składowana będzie już istniała, co możemy zobaczyć tutaj:

Pobranie danych za pomocą procedury skłdowanej

Kiedy przyjrzysz się bliżej SQL, zauważysz, że spodziewamy się otrzymać listę gości z polami: Forename, Surname, TelNo, From, To and RoomNumber. Aby użyć procedury składowanej do zapytania bazy danych i zmapowania wyników na encje, musimy dodać odpowiednią encję. W moim przypadku dodam GuestArrival, który wygląda tak:

[Keyless]
public class GuestArrival
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public string TelNo { get; set; }

    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }
}

Ta klasa zawiera wszystkie kolumny, które chcę zmapować, a także ma atrybut [Keyless]. Encje bez klucza mają większość możliwości mapowania tak jak zwykłe encje, ale nie są śledzone pod kątem zmian w DbContext. Oznacza to również, że nie będziemy w stanie wykonać wstawiania, aktualizowania ani usuwania takiego obiektu.

Musimy również dodać DbSet do naszego kontekstu PrimeDbContext.

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    // from stored procedures
    public virtual DbSet<GuestArrival> GuestArrivals { get; set; }
}

Teraz możemy iść dalej i użyć go. Oto, jak możemy wykonać tą procedurę składowaną:

var guests = primeDbContext.GuestArrivals.FromSqlInterpolated($"GetGuestsForDate '{date}'").ToList();

A jeśli umieszczę go w moim projekcie API, zmapuje wyniki na encje, to zobaczę taki wynik:

Wspomnę tutaj, ze używam parametru date w formacie dd-mm-rrrr, który przekazuję jako ciąg znaków do mojej procedury składowanej. Następnie w środku używam CONVERT(data, @StartDate, 105), gdzie 105 to format daty, który zamierzam konwertować. Więcej obsługiwanych formatów znajdziesz w tym artykule. Inną możliwością jest użycie parametru SqlParameter z typem danych Date, jednak formatowanie i przekazywanie daty może być kłopotliwe, więc wybrałem taki sposób.

Podsumowanie

Entity Framework Core może łatwo obsługiwać dane pobrane przez procedury składowane. Nie ma dedykowanej metody uruchamiania procedury, ale można ją uruchomić jako standardowy surowy kod SQL na kolekcji DbSet. Jeśli jednak interesuje Cię tylko wykonanie procedury składowanej, nie potrzebujesz do tego dedykowanego DbSet. Możesz sprawdzić szczegóły w moim poprzednim poście: Wykonanie procedury składowanej w Entity Framework Core.

Co więcej, możesz obsługiwać dodawanie lub aktualizowanie procedur składowanych za pomocą migracji EF Core, co oznacza, że wszystkie wymagane prace można wykonać za pomocą EF Core.

Cały opublikowany tutaj kod można znaleźć na moim GitHubie, możesz z nim eksperymentować do woli.

Pozdrawiam!

Select data with a stored procedure with Entity Framework Core 5

Stored procedures are an integral part of any MS SQL database. They are perfect to wrap complicated SQL into a database object, that we can reuse. How to execute a stored procedure that returns data in Entity Framework Core 5? In my last post: Execute a stored procedure with Entity Framework Core 5 I showed how to run a stored procedure, but selecting the data it’s a different kind of story. Let’s have a look.

Adding a stored procedure

First of all, we need to add a stored procedure. The best way to do so is to add a database migration with an appropriate SQL. Let’s start by adding a migration with EF Core global tool command:

dotnet ef migrations add spGetGuestsForDate 

This will generate a migration, that we can put our SQL into. Let’s see how it may look:

public partial class spGetGuestsForDate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        var sql = @"
            IF OBJECT_ID('GetGuestsForDate', 'P') IS NOT NULL
            DROP PROC GetGuestsForDate
            GO

            CREATE PROCEDURE [dbo].[GetGuestsForDate]
                @StartDate varchar(20)
            AS
            BEGIN
                SET NOCOUNT ON;
                SELECT p.Forename, p.Surname, p.TelNo, r.[From], r.[To], ro.Number As RoomNumber
                FROM Profiles p
                JOIN Reservations r ON p.ReservationId = p.ReservationId
                JOIN Rooms ro ON r.RoomId = ro.Id
                WHERE CAST([From] AS date) = CONVERT(date, @StartDate, 105)
            END";

        migrationBuilder.Sql(sql);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"DROP PROC GetGuestsForDate");
    }
}

This is a simple SQL code, that first checks if a procedure exists and if so, it deletes it. Then it creates a new procedure with the name GetGuestsForDate, which will get all arriving guests for a given date.

When the migration will be executed on the database, this stored procedure will be present, which we can see here:

Selecting data with a stored procedure

When you look closely at the SQL, you will notice that we expect to receive a list of guests with fields: Forename, Surname, TelNo, From, To and RoomNumber. In order to use the stored procedure to query the database and map results into entities, we need to add an appropriate entity. In my case I’ll add GuestArrival, that looks like this:

[Keyless]
public class GuestArrival
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public string TelNo { get; set; }

    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public int RoomNumber { get; set; }
}

This class contains all columns that I’d like to map and also it has [Keyless] attribute. Keyless entities have most of the mapping capabilities as normal entities, but they are not tracked for changes in the DbContext. It also means that we won’t be able to perform insert, update, or delete on such entity.

We also need to add a DbSet to our PrimeDbContext.

public class PrimeDbContext : DbContext
{
    public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
        : base(options)
    {
    }

    // from stored procedures
    public virtual DbSet<GuestArrival> GuestArrivals { get; set; }
}

Now we can go ahead and use it. Here is how we can accomplish that:

var guests = primeDbContext.GuestArrivals.FromSqlInterpolated($"GetGuestsForDate '{date}'").ToList();

And if I place it in my API project, it will map results to entities:

In here I’m using date parameter in dd-mm-yyyy format, which I pass as a string into my stored procedure. Then inside I use CONVERT(date, @StartDate, 105), where 105 is a date format I’m going to parse. You can find more supported formats in this article. It probably would be better to use SqlParameter class with Date datatype, but I couldn’t get it to work. 

Summary

Entity Framework Core can handle data selected by stored procedures easily. There is no dedicated method to run the procedure, but it can be run as a standard raw SQL on a DbSet. However, if you’re interested only in executing the stored procedure, you don’t need a DbSet for it. You can check out the details in my previous post: Execute a stored procedure with Entity Framework Core 5.

What’s more, you can handle adding or updating stored procedures with EF Core migrations, which means that all work required can be done with EF Core.

All code mentioned here can be found on my GitHub, feel free to experiment with it.

Cheers!