PrimeHotel – dodanie Entity Framework Core 5 w .NET 5

Przyjrzyjmy się, jak wprowadzić Entity Framework Core 5 w projekcie ASP.NET Core w .NET 5. Zaczniemy od pustej bazy danych, w której mamy pełne pole do popisu i możemy dodawać tabele tak, jak chcemy.

W tym poście będziemy pracować z projektem PrimeHotel, który stworzyłem do celów edukacyjnych. Wszystko to jest dostępne w moim GitHub, więc możesz go pobrać bezpłatnie. Zobacz także ten post, aby dowiedzieć się jak uruchomić ten projekt: PrimeHotel – jak uruchomić projekt.

Czym jest Entity Framework Core

Entity Framework Core 5 to lekka, rozszerzalna, otwarta i wieloplatformowa wersja popularnej technologii dostępu do danych Entity Framework. EF Core jest obiektowym mapperem (O/RM), umożliwia programistom pracę z bazą danych przy użyciu obiektów .NET i eliminuje potrzebę korzystania z większości kodu dostępu do danych, który zwykle muszą pisać. Oznacza to koniec pisania komend w SQL. Sprawdza się doskonale w większości przypadków, jednak gdy trzeba pracować z dużą ilością danych, prawdopodobnie lepiej będzie napisać własne polecenia SQL.

Dodanie Entity Framework Core

Dodawanie EF Core 5 do projektu ASP.NET Core jest bardzo proste. Zacznij od instalacji pakietów NuGet:

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

Po zakończeniu dodaj PrimeDbContext do folderu Models, który wyglądałby następująco:

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

        public virtual DbSet<Room> Rooms { get; set; }
    }

Teraz musimy dodać klasę Room, która reprezentowałaby encję pokoju, z tabeli Rooms.

    public class Room
    {
        public int Id { get; set; }

        public int Number { get; set; }

        public string Description { get; set; }

        public DateTime LastBooked { get; set; }

        public int Level { get; set; }

        public RoomType RoomType { get; set; }

        public int NumberOfPlacesToSleep { get; set; }
    }

    public enum RoomType
    {
        Standard,
        Suite
    }

Teraz zajmiemy się częścią konfiguracyjną. Użyjmy pustej bazy danych, która jest hostowana przez nas lokalnie. Najłatwiejszym sposobem jest zainstalowanie SQL Server w wersji Express i skonfigurowanie jej lokalnie. Możesz jednak skonfigurować serwer bazy danych w kontenerze docker. Sprawdź mój post, jak to zrobić: Set up a SQL Server in a docker container.

W pliku appsettings.json musimy wpisać nasz connection string. Powinien on wyglądać podobnie jak tutaj:

A teraz przejdźmy do pliku Startup.cs, gdzie musimy skonfigurować EF Core, aby używał naszego connection stringa. W metodzie ConfigureServices dodaj następujący wiersz:

    // Entity Framework
    services.AddDbContext<PrimeDbContext>(options =>
         options.UseSqlServer(Configuration.GetConnectionString("HotelDB")));

Zauważ, że użyłem nazwy HotelDB, która jest nazwą mojego connection stringa z pliku appsettings.json. To ważne, żeby te dwie wartości do siebie pasowały.

Wykonaliśmy już większość kluczowych rzeczy, ale potrzebujemy czegoś, co stworzyłoby tabele w bazie danych z naszego PrimeDbContext. Do tej pory mamy tylko jedną, ale nadszedł właściwy czas na wprowadzenie mechanizmów migracji.

Dodanie migracji w EF Core

Migracje w EF Core bazy danch pozwalają wprowadzać zmiany w bazie danych tak, aby była ona aktualna z kodem aplikacji, które ją używa. Jest to bardzo istotny mechanizm, ponieważ zmiany w strukturze bazy danych wprowadzane są dość często, nawet przez wielu programistów, więc potrzebujemy uniwersalnego mechanizmu, żeby te zmiany śledzić i wprowadzać. 

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

dotnet ef migrations add InitMigration

Po pomyślnym wykonaniu tego polecenia zostanie wygenerowany plik migracji, w którym można sprawdzić, jakie zmiany zostaną zastosowane.

W tym pliku znajdziesz dwie metody: Up i Down. Reprezentują zmiany, kiedy migracja zostanie zastosowana i kiedy zostanie wycofana.

    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Rooms",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Number = table.Column<int>(nullable: false),
                    Description = table.Column<string>(nullable: true),
                    LastBooked = table.Column<DateTime>(nullable: false),
                    Level = table.Column<int>(nullable: false),
                    RoomType = table.Column<int>(nullable: false),
                    NumberOfPlacesToSleep = table.Column<int>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Rooms", x => x.Id);
                });
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Rooms");
        }
    }

Ostatnią rzeczą do zrobienia jest uruchomienie aktualizacji bazy danych. Byłoby wspaniale, gdyby nasze zmiany były sprawdzane i stosowane przy każdym uruchomieniu projektu. Zobaczmy zatem, 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 wykorzysta wbudowany mechanizm wstrzykiwania zależności do pobrania instancji naszego PrimeDbContext i użycia jej do uruchomienia migracji bazy danych. Uruchomione zostaną tylko te, które nie zostały jeszcze zastosowane.

Pozostaje jeszcze jedna linia do dodania w metodzieConfigure, na samym dole.

    UpgradeDatabase(app);

Jeśli spojrzymy na bazę danych, zobaczymy, jakie migracje zostały zastosowane. Tabela __EFMigrationsHistory zostanie utworzona automatycznie przez Entity Framework Core.

Używanie EF Core 5

Gdy mamy już wszystko na miejscu, odpowiednią konfigurację i migracje baz danych, możemy zacząć korzystać z mechanizmu O/RM.

Wszystkie operacje na tabelach w EF Core muszą przejść przez PrimeDbContext. Korzystanie z niego jest bardzo proste, gdy zarejestrujemy go w klasie Startup.cs, będzie on dostępny w dowolnej klasie do wstrzyknięcia i użycia. Spójrz na ten przykład prostych operacji CRUD w RoomController.

    [ApiController]
    [Route("[controller]")]
    public class RoomController : ControllerBase
    {
        private readonly PrimeDbContext primeDbContext;

        public RoomController(PrimeDbContext _primeDbContext)
        {
            primeDbContext = _primeDbContext;
        }

        [HttpGet]
        public async Task<IEnumerable<Room>> Get()
        {
            return await primeDbContext.Rooms.ToListAsync();
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var room = await primeDbContext.Rooms.FindAsync(id);
            if (room == null)
            {
                return NotFound();
            }

            return Ok(room);
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] Room room)
        {
            var createdRoom = await primeDbContext.Rooms.AddAsync(room);
            await primeDbContext.SaveChangesAsync();

            return Ok(createdRoom.Entity);
        }

        [HttpPut]
        public async Task<IActionResult> Put([FromBody] Room room)
        {
            var existingRoom = await primeDbContext.Rooms.FindAsync(room.Id);
            if (existingRoom == null)
            {
                return NotFound();
            }

            existingRoom.Number = room.Number;
            existingRoom.Description = room.Description;
            existingRoom.LastBooked = room.LastBooked;
            existingRoom.Level = room.Level;
            existingRoom.RoomType = room.RoomType;
            existingRoom.NumberOfPlacesToSleep = room.NumberOfPlacesToSleep;

            var updatedRoom = primeDbContext.Update(existingRoom);
            await primeDbContext.SaveChangesAsync();
            return Ok(updatedRoom.Entity);
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            var existingRoom = await primeDbContext.Rooms.FindAsync(id);
            if (existingRoom == null)
            {
                return NotFound();
            }

            var removedRoom = primeDbContext.Rooms.Remove(existingRoom);
            await primeDbContext.SaveChangesAsync();

            return Ok(removedRoom.Entity);
        }
    }

Zauważ, że w EF Core każda metoda ma wersję asynchroniczną. Wykorzystanie asynchroniczności jest dobrym pomysłem. W ten sposób Twój kod będzie szybszy i może być uruchamiany wydajniej z wieloma innymi żądaniami równolegle.

Ważne rzeczy, o których należy pamiętać:

  • możemy wyszukiwać kolekcje encji w sposób, w jaki chcemy z LINQ, używając Where, Select i innych metod, które na koniec wygenerują SQL ze wszystkich tych warunków
  • Jeśli tylko filtrujesz encje do wyświetlenia, możesz użyć AsNoTracking() w celu poprawy wydajności
  • Wywołanie bazy danych zostanie wykonane dopiero wtedy, gdy kod, który piszemy, wymaga wyników. Dzieje się tak na przykład, gdy używamy ToListAsync
  • Wszystkie zmiany, które wprowadzamy, należy zapisać za pomocą SaveChangesAsync, aby je zapisać w bazie danych

To tylko kilka punktów, o których należy pamiętać, ale jest jeszcze wiele rzeczy dziejących się pod spodem, o których warto wiedzieć. To wprowadzenie wystarczy jednak na początek i jest więcej niż wystarczające, aby samodzielnie zacząć pracę z Entity Framework Core.

Podsumowanie

Schludnie i wygodnie! Entity Framework Core jest idealny do prawie każdego prostego użycia bazy danych. Jestem pewien, że uznasz jego możliwości za przydatne i intuicyjne.

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, daj mi znać, jeśli podoba Ci się ten post 🙂

Leave a Reply

Your email address will not be published.