Category Archives: Uncategorized

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! ❤️

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!

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!

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!

 

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!

Wykonanie procedury składowanej w Entity Framework Core

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ą w Entity Framework Core 5? Przekonajmy się.

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 globalnego narzędzia EF Core:

dotnet ef migrations add spUpdateProfilesCountry

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

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

            CREATE PROCEDURE [dbo].[UpdateProfilesCountry]
                @StardId int
            AS
            BEGIN
                SET NOCOUNT ON;
                UPDATE Profiles SET Country = 'Poland' WHERE LEFT(TelNo, 2) = '48' AND Id > @StardId
            END";

        migrationBuilder.Sql(sql);
    }

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

Jest to prosty kod SQL, który najpierw sprawdza, czy procedura istnieje, a jeśli tak, usuwa ją. Następnie tworzy nową procedurę z nazwą UpdateProfilesCountry, która zaktualizuje kolumnę Country dla każdego profilu, którego numer telefonu zaczyna się od 48.

Kiedy migracja zostanie wykonana na bazie danych, stworzy procedurę składowaną UpdateProfilesCountry.

Wykonanie procedury składowanej

Nie ma dedykowanej metody wykonywania procedury składowanej, więc w przypadku, gdy procedura składowana nie zwraca danych, możemy po prostu wywołać ją jako zwykły kod SQL. Można to osiągnąć w następujący sposób:

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

Kiedy odpytam bazę danych o wszystkie numery zaczynające się od 48, to zobaczę, że kraj został zaktualizowany na Poland. Oznacza to, że nasza procedura została poprawnie wykonana.

BTW. Nie przejmuj się że widzisz prawdziwe dane osobowe. Są to dane fałszywe, wygenerowane przy pomocy biblioteki Bogus 🙂

Podsumowanie

Entity Framework Core całkiem dobrze radzi sobie z procedurami składowanymi. Nie ma dedykowanej metody uruchamiania procedury, ale można ją uruchomić jako standardowy kod SQL. Jeśli jednak chcesz pobrać dane z bazy danych przy pomocy procedury składowanej, to jest to już inne podejście. Opisałem je dokładnie tutaj: Pobranie danych za pomocę procedury składowanej w Entity Framework Core 5.

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 wymieniony tutaj kod można znaleźć na moim GitHubie, możesz z nim eksperymentować do woli.

Pozdrowienia!

Jak stworzyć pakiet NuGet wspierający wiele frameworków

Praca z .NET Standard to przyjemność. Ten framework jest ciekawy, zwięzły, niezwykle czytelny i prosty. Jako programista chciałbym pracować tylko z kodem .NET Core lub .NET Standard. Czasami jednak trzeba wspierać również stary .Net Framework. Jak stworzyć pakiet NuGet, który może być używany przez obie strony?

Przykład z życia wzięty

Jestem autorem i programistą prostego klienta dla API Egnyte, które obsługuje prywatną chmurę dla dokumentów z mnóstwem funkcji. Jest to pakiet, który stworzyłem jako PCL (Portable Class Library), ponieważ może obsługiwać wiele frameworków, jednocześnie programując w pełnym .Net Framework.

Jednak ostatnio poproszono mnie o napisanie obsługi .Net Standard, aby można go było używać w nowszych projektach. Z radością skorzystałem z okazji i zacząłem szukać informacji na ten temat.

Okazało się, że gdy trzeba obsługiwać wiele frameworków, preferowanym sposobem nie jest już PCL, ale właśnie .Net Standard. Wziąłem się zatem do pracy. Przeportowałem swój pakiet do .Net Standard i sprawiłem, że działa. Na szczęście jest to bardzo prosty projekt, więc nie było to trudne zadanie. Co więcej, jeśli chcesz obsługiwać wiele frameworków, wystarczy, że umieścisz je w swoim pliku .csproj.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

</Project>

Zauważ, że zmieniłem TargetFramework na TargetFrameworks i teraz mogę wprowadzić listę frameworków, któą będą wspierał. Więcej na temat wspierania wielu frameworków na raz znajdziesz na tej stronie Microsoft.

Czemu się nie buduje?

Do tej pory wydaje się to niewiarygodnie łatwe, ale prawda jest taka, że może być jednak trochę trudniej. Kiedy zbudowałem swój projekt, dostałem wyjątek:

Error CS0234: The type or namespace name ‘Http’ does not exist in the namespace ‘System.Net’ (are you missing an assembly reference?)

Nie ma odwołania do biblioteki System.Net.Http, ponieważ typy które wymagają tej przestrzeni nazw, znajdują się wewnątrz .Net Standard. Nie musiałem jej dodatkowo importować. Jednak dla .Net Framework 4.6.1 potrzebuję dodać taki pakiet, aby mój kod działał. Po pewnym czasie doszedłem do wniosku, że potrzebuję pewnej modyfikacji w moim pliku .csproj.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>

   <!--Conditionally obtain references for the .NET Framework 4.6.1 target-->  
  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <Reference Include="System.Net.Http" />
  </ItemGroup>
  <ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

</Project>

Na szczęście był to jedyny problem, ale być może będziesz musiał zrobić więcej „if-ów”, a nawet dodać kilka haków w kodzie. Możesz np. napisać kod, który będzie działał tylko dla określonego frameworka.

#if NET40
        Console.WriteLine("Target framework: .NET Framework 4.0");
#elif NET45  
        Console.WriteLine("Target framework: .NET Framework 4.5");
#else
        Console.WriteLine("Target framework: .NET Standard 1.4");
#endif

Teraz moje drzewo projektu wygląda tak. Jak widać, teraz w Dependencies mam wymienione dwa frameworki.

Tworzenie pakietu NuGet

W .Net Standard nie ma prostszej rzeczy niż utworzenie pakietu NuGet. Wystarczy uruchomić polecenie dotnet pack w głównym folderze projektu.

W moim przypadku potrzebowałem więcej informacji o pakiecie, takich jak autor i opis, więc zmodyfikowałem mój plik .csproj i oto moja ostateczna wersja.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
    <PackageId>Egnyte.Api</PackageId>
    <Version>2.0.0-alpha1</Version>
    <Authors>Michal Bialecki</Authors>
    <Company>Egnyte Inc.</Company>
    <Description>Egnyte Api client for .net core</Description>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <!--Conditionally obtain references for the .NET Framework 4.6.1 target--> 
  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <Reference Include="System.Net.Http" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>

</Project>

Jedna z rzeczy, o której warto wspomnieć to <GenerateDocumentationFile>true</GenerateDocumntationFile>, ponieważ powoduje wygenerowanie dokumentacji XML wraz z bibliotekami dll. Właśnie stąd IntelliSense pobiera opisy metod, co jest bardzo przydatne dla programistów.

Teraz, jeśli otworzę mój pakiet NuGet w NuGet Package Explorer, widzę coś takiego:

Teraz mój pakiet obsługuje zarówno .Net Standard 2.0, jak i .Net Framework 4.6.1.

Zobaczmy, jak to wygląda podczas instalacji tego pakietu.

Pakiet Egnyte.Api zawiera informacje o tym, jaki framework obsługuje i że jest to wersja wstępna (Prerelease), ponieważ celowo ustawiłem wersję na 2.0.0-alpha1. Podczas instalowania pakietu NuGet Package Manager sam zdecyduje, która wersja biblioteki jest odpowiednia dla projektu w którym pracujesz i zainstaluje właściwą. Miło i łatwo, wszystko odbywa się automatycznie!

Summary

Wspieranie wielu frameworków w .NET Standard jest naprawdę proste, ale może być bardziej skomplikowane w przypadku korzystania z wielu odwołań i bibliotek. Jednak jest to warte wysiłku, ponieważ możesz pracować z .Net Standard zamiast ze starymi frameworkami i projektami.

Tworzenie i budowanie pakietu NuGet jest znacznie prostsze w .Net Standard i można korzystać ze wszystkich fajnych funkcji tej platformy. Będziesz także bardziej na bieżąco ze zmianami w najnowszych wersjach frameworka.

.Net Standard to nowy PCL, standard tworzenia bibliotek dla wielu frameworków i platform. Ponadto NuGet obsługuje wiele wersji biblioteki dll w jednym pakiecie i wybiera odpowiednią dla Ciebie. Jeśli odbywa się to automatycznie, dlaczego nie spróbować?

Cały kod, który znalazł się w tym poście jest dostępny w repozytorium na GitHub.

Dobrej zabawy!

 

Scalanie migracji w Entity Framework Core 5

Podczas pracy z szybko rozwijającym się projektem zmiany zachodzą szybko nie tylko w kodzie projektu, ale także w schemacie bazy danych. Dzieje się tak zwłaszcza podczas pracy nad mikroserwisem od samego początku, kiedy często zmienia się jego przeznaczenie.

Jak działają migracje w EF Core 5

W migracjach Entity Framework Core 5 dodajemy migrację jako zmiany pomiędzy naszą klasą DbContext a istniejącą [nazwa DbContext] ModelSnapshot. Podczas generowania nowej migracji narzędzie CLI wygeneruje tylko różnice między tymi dwoma bytami i umieści je w dwóch metodach: Up i Down. W pierwszej nastąpi zmiana dotycząca zastosowania migracji, aw drugiej usunięcia migracji.

Po zastosowaniu migracji jej nazwa jest zapisywana w tabeli __EFMigrationsHistory.

Scalanie wielu migracji

Powiedzmy, że po wielokrotnej zmianie schematu na wczesnych etapach nasz projekt jest teraz stabilny. Mamy kilka migracji, które można by połączyć w jedną, która raz stworzyłaby model, bez wielu małych aktualizacji.

Scalanie kiedy możemy wszystko usunąć

Najłatwiejszym sposobem scalenia wszystkich migracji byłoby usunięcie wszystkiego! A dokładnie mam na myśli taki proces:

  • usuń katalog Migrations ze wszystkimi migracjami
  • wyczyść tabelę __EFMigrationHistory
  • usuń wszystkie tabele i inne obiekty bazy danych, które zostały dodane podczas migracji
  • utwórz nową migrację ze wszystkimi zmianami

Jest to drastyczny sposób scalania migracji, ponieważ utracimy wszystkie dane. Jest to jednak bardzo proste i może w niektórych przypadkach spełniać swoją rolę.

Scalanie, kiedy chcemy zachować dane

Kiedy chcemy zachować dane, nie możemy usunąć wszystkich już utworzonych obiektów bazy danych, ale możemy scalić pliki migracji w naszym kodzie. Zobaczmy, jak można to zrobić.

  • Usuń wszystkie skrypty migracji z folderu Migrations
  • Dodaj nową migrację za pomocą polecenia dotnet ef migrations add MergedMigration
  • Skopiuj cały plik i wyczyść obie metody Up i Down
  • Zaktualizuj bazę danych i zastosuj migrację MergedMigration za pomocą polecenia dotnet ef database update
  • Następnie zamień zawartość pliku MergedMigration na wcześniej wygenerowany kod

W rezultacie będziesz mieć tylko jeden plik migracji. W moim przykładzie tabela __EFMigrationHistory wygląda następująco.

A teraz w Visual Studio widzę tylko jedną migrację.

Zawiera ona zmiany ze wszystkich moich poprzednich migracji, które zostały scalone.

Zadziałało!

Wskazówka! Możesz również nazwać scaloną migrację tak jak pierwszą, która została już zastosowana, aby nie trzeba było aktualizować bazy danych i dodawać jej do tabeli __EFMigrationHistory.

Nie zadziała za każdym razem

Migracje możemy łatwo łączyć, gdy są stosowane tylko do kontrolowanej przez nas bazy danych. Ponadto ten proces nie będzie działać w środowiskach, w których nie zastosowano wszystkich scalonych migracji. Proces jest łatwy, ale są pewne rzeczy, które należy zwrócić uwagę.

Z drugiej strony, czy musimy zachować wszystkie migracje, nawet jeśli wiemy, że nigdy więcej ich nie uruchomimy? Nie sądzę. W tym miejscu byłoby wspaniale połączyć stare migracje, ale zostawić te najnowsze. Można to osiągnąć w bardzo podobny sposób.

  • Przywróć ostatnie N migracji lokalnie, jedna po drugiej i przenieś je gdzieś
  • Przywróć stan projektu w miejscu pasującym do migracji (git checkout w odpowiednim miejscu w historii)
  • Scal wszystkie istniejące migracje
  • Przywróć stan projekt do najnowszej wersji
  • Dodawaj po kolei zapisane najnowsze migracje

W takim przypadku zachowamy najnowsze migracje i utworzymy dużą migrację początkową, która byłaby zgodna z Twoim projektem. Jeśli wystąpi przypadek, w którym wszystkie migracje będą musiały zostać zastosowane do bazy danych, schemat bazy danych nie zostanie uszkodzony.

Podsumowanie

Scalanie migracji w Entity Framework Core 5 jest możliwe i mogę powiedzieć, że jest to zaskakująco łatwe. Obejmuje jednak proces automatycznego generowania i musisz sprawdzić, czy migracja scalona działa dokładnie tak samo, jak wszystkie migracje stosowane pojedynczo. Co więcej, istnieje więcej niż jeden sposób łączenia migracji i musisz wybrać ten, który jest dla Ciebie najbardziej odpowiedni.

Cały kod zamieszczony tutaj jest dostępny na moim GitHubie, więc możesz go pobrać i bawić się nim. Spójrz również na ten post, jak go uruchomić: PrimeHotel – jak uruchomić projekt.

Dzięki za czytanie i do zobaczenia ponownie 🙂

Dodanie migracji w Entity Framework Core 5 do projektu w .NET 5

Migracje bazy danych pomagają programiście w utrzymywaniu aktualności schematu bazy danych z kodem. Jest to podstawowy mechanizm, który utrzymuje zmiany w kodzie i stosuje je w bazie danych. Migracje Entity Framework Core 5 są przeznaczone do śledzenia klasy DbContext i generowania migracji podczas jej aktualizacji.

Instalowanie narzędzi

Aby dodać migracje EF Core, musisz mieć już skonfigurowane Entity Framework Core w projekcie. Możesz sprawdzić, jak przejść przez ten proces w tym poście: PrimeHotel – dodanie Entity Framework Core 5 w .NET 5

Najłatwiejszym sposobem dodawania migracji i zarządzania nimi jest użycie narzędzi .NET Core CLI, które powinieneś mieć już zainstalowane. Wpisz poniższe polecenie, aby to sprawdzić:

dotnet tool install --global dotnet-ef

Możesz także zaktualizować narzędzie po zainstalowaniu używając komendy:

Dodanie migracji

Dodanie pierwszej migracji nie różni się zbytnio od dodawania kolejnych. Musisz otworzyć okno terminala w lokalizacji projektu i wykonać polecenie:

dotnet ef migrations add InitialCreate

Kiedy to polecenie zostanie wykonane pomyślnie, wygeneruje katalog Migrations. Plik InitialCreate to indywidualna migracja w celu dopasowania do DbContext. PrimeDbContextModelSnapshot reprezentuje bieżący stan modelu. Jest dodawany do projektu podczas tworzenia pierwszej migracji i aktualizowany przy każdej kolejnej migracji. Umożliwia platformie migracji obliczenie zmian wymaganych w celu zaktualizowania bazy danych do modelu.

W pliku InitialCreate znajdziesz dwie metody: w Up i Down. Będą one reprezentować zmiany, kiedy migracja zostanie zastosowana i kiedy zostanie wycofana.

Wygenerowana migracja pozostanie taka, jaka jest. Nie jest to plik generowany automatycznie, który zostanie zaktualizowany w dalszej części procesu. Ta migracja została wygenerowana, abyś mógł rzucić okiem i sprawdzić, czy robi to, co powinna. Migrację możesz modyfikować zgodnie ze swoimi potrzebami, zatem nic nie stoi na przeszkodzie, aby wprowadzić własne ulepszenia.

Dodanie drugiej i kolejnych migracji nie będzie się właściwie niczym różniło od powyższego procesu. Wystarczy użyć polecenia dotnet migrations add <name> i zostanie wygenerowany odpowiedni plik.

Uruchamianie migracji ręcznie

W tym momencie możesz uruchomić migracje Entity Framework Core 5 i zaktualizować schemat bazy danych. Możesz to zrobić za pomocą następującego polecenia:

dotnet ef database update

Migracje bazy danych zostaną zastosowane, a wszystkie wykonane migracje zostaną odnotowane w tabeli __EFMigrationsHistory. Oto listing tej tabeli po kilku migracjach.

Uruchamianie migracji automatycznie

Byłoby wspaniale, gdyby nasze zmiany zostały sprawdzone i zastosowane przy każdym uruchomieniu projektu. Zobaczmy, jak możemy to osiągnąć. Przede wszystkim przejdźmy do pliku Startup.cs i utwórzmy metodę.

    private void UpgradeDatabase(IApplicationBuilder app)
    {
        using (var serviceScope = app.ApplicationServices.CreateScope())
        {
            var context = serviceScope.ServiceProvider.GetService<PrimeDbContext>();
            if (context != null && context.Database != null)
            {
                context.Database.Migrate();
            }
        }
    }

Ta metoda używa wbudowanego mechanizmu Dependency Injection, aby pobrać wystąpienie naszego PrimeDbContext i użyć go do uruchomienia migracji bazy danych. Tylko te, które nie zostały jeszcze zastosowane, zostaną uruchomione.

Następnie, w metodzie Configure, dodaj na końcu.

    UpgradeDatabase(app);

Dzięki tej konfiguracji mechanizmu aplikacja zaktualizuje używaną bazę danych, niezależnie od tego, czy jest uruchamiana lokalnie, czy też jest wdrażana i uruchamiana na serwerze produkcyjnym.

Podsumowanie

Migracje bazy danych ułatwiają aktualizowanie schematu bazy danych zgodnie ze zmianami kodu. Dodanie migracji Entity Framework Core 5 jest naturalnym krokiem, gdy masz Entity Framework Core. Wszystkie operacje można wykonać za pomocą narzędzi .NET Core CLI i bardzo prostych poleceń. Pamiętaj, że zawsze możesz edytować migracje przed ich zastosowaniem.

Cały kod opublikowany w tym poście jest dostępny na moim GitHub, więc możesz go dowolnie ściągać i modyfikować. Zerknij także na post jak uruchomić projekt PrimeHotel: PrimeHotel – jak uruchomić projekt

Dzięki za przeczytanie i powodzenia! 🙂

Dodanie Entity Framework Core 5 do istniejącej bazy danych

Entity Framework Core 5 to lekki i łatwy w użyciu ORM, który pozwala korzystać z bazy danych bez pisania jakichkolwiek poleceń SQL. Wbudowane mechanizmy będą tłumaczyć zapytania LINQ dotyczące klas encji na zapytania SQL i zwracać zamapowane obiekty.

Zerknij też na post dotyczący dodawania EF Core z migracjami do pustej bazy danych: PrimeHotel – dodanie Entity Framework Core 5 w .NET 5

Dodanie Entity Framework Core 5 jest bardzo proste, jeśli masz pustą bazę danych, ale czy jest to takie łatwe podczas pracy z bazą danych, która posiada już swoją strukturę? Czy musimy to wszystko mapować, czy możemy po prostu pracować z częścią bazy danych, która nas interesuje? Zacznijmy od początku.

Czego potrzebujemy

Aby pracować z EF Core 5, musimy zainstalować pakiety NuGet:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.SqlServer

Ostatni pakiet zdradza nam, że będziemy pracować z bazą danych MS SQL Server. Teraz połączmy się z naszą bazą danych i zobaczmy, jak to wygląda.

Do pracy z bazą danych używam Azure Data Studio, które jest lekkim i szybkim narzędziem, które może wykonywać większość podstawowych operacji na bazach danych. Jest znacznie szybszy niż SQL Server Management Studio i do większości mojej pracy używam właśnie tego pierwszego.

Dodamy EF Core dla bazy danych aspnetcore, która wygląda następująco.

Dodanie klasy DbContext

Następną rzeczą, którą musimy zrobić, jest utworzenie klasy DbContext. Moglibyśmy stworzyć ją ręcznie i wszystko wpisać ręcznie. Jednak .NET Core ma narzędzia do tworzenia i generowania ich automatycznie.

Proces ten nazywa się inżynierią wsteczną i jest to tworzenie szkieletów klas encji i klasy DbContext w oparciu o schemat bazy danych. Aby wykonać tę operację, użyjemy narzędzia .NET CLI, które musisz zainstalować, jeśli jeszcze tego nie zrobiłeś. Wpisz poniższe polecenie, aby to sprawdzić:

dotnet tool install --global dotnet-ef

Możesz także zaktualizować narzędzie po jego zainstalowaniu:

Proces tworzenia szkieletu wymaga connection stringa do przekazania. Moglibyśmy go przekazać w poleceniu, ale możemy też wykonać to zadanie w bardziej elegancki sposób, podając tylko jego nazwę.

Przejdźmy do pliku appsettings.json i skonfigurujmy connection string dla naszej nowej bazy danych.

Dodałem connection string o nazwie aspnetcore w sekcji ConnectionStrings.

Polecenie, którego będziemy używać, jest bardzo proste, uruchom je w katalogu swojego projektu:

dotnet ef dbcontext scaffold Name=aspnetcore Microsoft.EntityFrameworkCore.SqlServer

Powyższe polecenie doda aspnetcoreDbContext i wszystkie encje reprezentujące bazę danych.

Efekt jest zadowalający, jednak byłoby wygodniej mieć większą kontrolę nad tym procesem.

Dostosuj proces do swoich potrzeb

Na szczęście jest więcej parametrów, których możemy użyć. Przyjrzyjmy się niektórym z nich:

  • --table może służyć do dołączania określonych tabel
  • --use-database-names zachowa oryginalne nazwy baz danych tak bardzo, jak to możliwe. Jednak nieprawidłowe identyfikatory .NET nadal zostaną zmienione
  • --context może służyć do nadania wygenerowanemu kontekstowi DbContext własnej nazwy
  • --context-dir służy do tworzenia szkieletu klasy DbContext w określonym katalogu
  • --output-dir służy do tworzenia klas jednostek w określonym katalogu
  • --force nadpisze istniejącą klasę DbContext i klasy encji

Zmodyfikowałem moje polecenie, aby wyglądało tak:

dotnet ef dbcontext scaffold Name=aspnetcore --table Profiles --table Events
  --context AspNetCoreDbContext --context-dir AspNetCoreModels 
  --output-dir AspNetCoreModels Microsoft.EntityFrameworkCore.SqlServer

Zobaczmy, jakie klasy zostały wygenerowane.

Zwróć uwagę, że zostały wygenerowane tylko tabele Events i Profiles, klasa DbContext ma nazwę AspNetCoreCotext i wszystko zostało wygenerowane w katalogu AspNetCoreModels. O to chodziło!

Ograniczenia

Inżynieria wsteczna wykonuje ogromną pracę polegającą na tworzeniu szkieletów klas encji, więc nie musimy pisać jej samodzielnie. Istnieją jednak pewne ograniczenia tego procesu:

  • nie wszystko na temat modelu jest przedstawione w schemacie bazy danych. Na przykład hierarchie dziedziczenia, typy własne i tabele dzielone nie zostaną odtworzone
  • ponadto dokumentacja EF Core twierdzi, że istnieją pewne typy kolumn, które nie zostaną uwzględnione w modelu
  • typy dopuszczające wartość null nie zostaną zamapowane jako dopuszczające wartość null. Na przykład kolumny typu string, które mogą mieć wartość null, nie będą mapowane jako typ string?. Taką zmianę trzeba dodać samodzielnie.

Więcej o tym możesz przeczytać w tym artykule Microsoft-u.

Aktualizowanie modelu

Zawsze, gdy coś się zmieni w bazie danych, będziesz musiał zaktualizować swój model. Większość zmian będzie trywialnych, takich jak dodanie kolumny do tabeli, zmiana nazwy tabeli lub zmiana typu kolumn. Te zmiany można szybko wprowadzić w klasach encji własnoręcznie.

Jeśli jednak nie masz pewności, jak mapować zmiany, zawsze możesz ponownie wygenerować cały DbContext ze wszystkimi klasami encji. Aby to zrobić, użyj parametru --force w poleceniu scaffold. Należy jednak pamiętać, że wszystkie zmiany wprowadzone ręcznie zostaną nadpisane. Obecnie nie ma możliwości aktualizacji modelu ze schematu bazy danych i zachowania ręcznych zmian.

Aktualizowanie bazy danych

Tworzenie szkieletu DbContext to tylko sposób na generowanie klas pasujących do bazy danych. Oznacza to, że możesz dodać migracje nawet do istniejącej bazy danych.

Najpierw musisz dodać AspNetCoreDbContext do kontenera DI. Przejdź do pliku Startup.cs i w metodzie ConfigureServices dodaj następujący wiersz.

    services.AddDbContext<AspNetCoreDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("aspnetcore")));

Teraz możesz dodać migracje do drugiej bazy danych. Dobrą praktyką jest rozdzielenie modeli i migracji dla każdej bazy danych. Wykonaj to polecenie:

dotnet ef migrations add InitialCreate --context AspNetCoreDbContext --output-dir Migrations/AspNetCore

Spójrzmy co zostało wygenerowane:

Warto zwrócić uwagę na jedną rzecz. Entity Framework Core wygenerował początkową migrację ze wszystkimi zmianami, które są obecnie w AspNetCoreDbContext. Może być konieczne wyczyszczenie wszystkich zmian w tej migracji, ponieważ te tabele już istnieją.

Podsumowanie

Podczas dodawania Entity Framework Core 5 do istniejącej bazy danych dobrym pomysłem jest stworzenie szkieletu klas jednostek i kontekstu za pomocą dedykowanego narzędzia .NET CLI. Na szczęście istnieje kilka parametrów, które możesz użyć, aby zbudować dokładnie to, czego potrzebujesz i tak jak tego potrzebujesz.

Migracje EF Core 5 można dodać później, aby zapewnić aktualność bazy danych za pomocą kodu. Jedną z rzeczy, o których należy pamiętać, jest to, że ponowne zaktualizowanie DbContext z bazy danych spowoduje nadpisanie wszystkich zmian, które zostały wprowadzone ręcznie. Z tego powodu tworzenie szkieletów DbContext jest raczje operacją jednorazową.

Cały zamieszczony tutaj kod został zademonstrowany na przykładzie projektu PrimeHotel, który jest dostępny na moim GitHubie, więc możesz go bezpłatnie pobrać i na nim eksperymentować. Spójrz również na ten post, jak uruchomić ten projekt: PrimeHotel – jak uruchomić projekt.

Mam nadzieję, że podobał Ci się ten post. Pozdrawiam! 🙂