Gå til indholdet

Intro til EF Core

Entity Framework Core (EF Core) er en open source og platformuafhængig version af Entity Framework, et populært Object-Relational Mapping (ORM) framework udviklet af Microsoft.

Entity Framework blev først introduceret i 2008 som en del af .NET Framework 3.5 Service Pack 1. Det blev designet til at gøre det nemmere for .NET udviklere at arbejde med relationelle databaser ved at abstrahere databaselaget og tillade udviklere at arbejde med .NET objekter i stedet for SQL queries. Over tid blev Entity Framework videreudviklet og forbedret, hvilket førte til flere versioner med forskellige nye funktioner og forbedringer.

Information til undervisere

Kursisterne bør kende til EF Core, da det er et populært og udbredt ORM framework til .NET. Det er vigtigt at de har en forståelse for hvad et ORM framework er, og en grundlæggende forståelse for, hvordan de fungerer. Det vil dog være en stor fordel at kende til databaser og SQL, da EF Core er designet til at arbejde med relationelle databaser, og det er vigtigt at forstå, hvordan EF Core oversætter .NET objekter til SQL queries og omvendt. Brug gerne eksemplet (person.db) til at vise EF Core i praksis, og lad kursisterne prøve at skrive deres egne queries og opdateringer. Lidt mere omfattende vil være opgaven omkring Northwind databasen - se nederst.

Det blev dog hurtigt klart, at det originale Entity Framework ikke ville kunne understøtte mange nye ønskede features. Derfor blev Entity Framework Core udviklet som et nyt ORM framework, der er designet fra bunden med henblik på at være lettere, mere fleksibelt og at kunne understøtte nye funktioner, såsom ikke-relationelle databaser, in-memory databaser til test, og mere.

EF Core har bevaret mange af de populære funktioner fra det originale Entity Framework, men med mange forbedringer og nye funktioner. Det understøtter LINQ queries, ændringssporing, opdateringer, migrationer og meget mere. Men det er vigtigt at bemærke, at på grund af dets redesign er EF Core ikke bare en version af Entity Framework, der er “portet” til .NET Core. Det er et helt nyt framework, og mens det har mange ligheder med det originale Entity Framework, er der også mange forskelle.

Overordnet om EF Core

Entity Framework Core (EF Core) er en moderne, alsidig Object-Relational Mapper (ORM) til .NET. Det gør det muligt for udviklere at arbejde med databaser ved hjælp af .NET-objekter, hvilket gør det nemmere at skrive databasedrevet software.

Når du arbejder med EF Core, starter du typisk med at definere dine dataklasser, også kendt som dine “entiteter”. Disse klasser repræsenterer tabellerne i din database og indeholder egenskaber, der repræsenterer kolonnerne i disse tabeller.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Header { get; set; }
    public string Text { get; set; }
    public DateTime Date { get; set; }
}

Efter at have defineret dine entiteter, skal du oprette en DbContext klasse, som repræsenterer en session med databasen, hvilket giver dig mulighed for at udføre CRUD-operationer (Create, Read, Update, Delete).

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

For at konfigurere din forbindelse til databasen (her SQL server), skal du overskrive OnConfiguring metoden i din DbContext klasse og angive forbindelsesstrengen.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(
        @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True");
}

Når du har din DbContext opsat, kan du begynde at bruge EF Core til at interagere med din database. Du kan oprette nye instanser af dine entiteter, tilføje dem til din DbContext og kalde SaveChanges metoden for at persistere disse ændringer til databasen.

EF Core understøtter også LINQ (Language Integrated Query), hvilket gør det nemt at skrive komplekse data queries i C#. Du kan bruge LINQ til at hente, filtrere, sortere og manipulere dine data på forskellige måder.

BloggingContext c = new BloggingContext();
var res = c.Posts.Where(i=> i.Date < DateTime.Now.AddDays(-14)).OrderBy(i => i.Date).ToList();

Endelig understøtter EF Core også migrationer, hvilket er en måde at administrere databasens skemaændringer over tid. Du kan bruge migrationer til at oprette databasen, ændre dens skema ved at tilføje, ændre eller slette tabeller og kolonner og endda til at seede databasen med initial data.

Det skal bemærkes, at mens EF Core har mange fordele, herunder dens alsidighed og lette brug, er det ikke altid det bedste valg for alle situationer. Afhængig af dine specifikke krav, kan det nogle gange være mere effektivt at bruge andre tilgange, som f.eks. ADO.NET for mere lavniveau kontrol, eller Dapper (alternativt ORM produkt) for performancekritiske anvendelser.

Prøv det selv

Den bedste måde at bliver introduceret til EF er ved at prøve det. Følgende applikation er en simpel konsol applikation, der bruger EF Core til at interagere med en SQLite database. Du kan bruge denne applikation som en skabelon til at eksperimentere med EF Core og udforske dets funktioner.

  • Skab en tom konsol applikation med top-level statements
  • Tilføj NuGet pakken Microsoft.EntityFrameworkCore.Sqlite
  • Tilføj NuGet pakken SQLitePCLRaw.bundle_e_sqlite3
    • Nødvendig i de sidste nye versioner af Microsoft.EntityFrameworkCore.Sqlite (muligvis en fejl der bliver rettet)
  • Tilføj NuGet pakken Dumpify og Spectre.Console
    • Bruges til at skrive “nemt” ud på konsol samt skabe menu mv.
  • Download min eksempel Sqlite database og gem den i c:\temp
  • Læs om eksempel databasen eller kig selv med SQLite Browser
  • Tilføj følgende kode og prøv så at køre programmet.
using Dumpify;
using Spectre.Console;
using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization;
using System.Reflection;

// Nødvendig i de sidste nye versioner af `Microsoft.EntityFrameworkCore.Sqlite` (muligvis en fejl der bliver rettet)
SQLitePCL.Batteries.Init();

string? menu = "";
int menuIndex = -1;

while (menu != "Afslut")
{
    Console.Clear();

    var choices = new[] {
        "Hent personer",
        "Find alle over 180 sorteret efter efternavn",
        "Find person (med land) udfra id",
        "Ret person med id = 1",
        "Opret ny person",
        "Slet person",
        "Benyt transaktion",
        "Hent personer asynkront",
        "Find land med personer",
        "Afslut"
    };

    menu = AnsiConsole.Prompt(
        new SelectionPrompt<string>()
            .Title("EF DEMO")
            .PageSize(20)
            .AddChoices(choices)
    );


    menuIndex = Array.IndexOf(choices, menu);
    switch (menuIndex)
    {
        case 0:
            using (PeopleContext context = new PeopleContext())
            {
                int antal = AnsiConsole.Ask<int>("Hvor mange personer skal hentes: ", 5);
                var persons = context.People.Take(antal).ToList();
                persons.Dump();
                break;
            }
        case 1:
            using (PeopleContext context = new PeopleContext())
            {
                var persons = context.People.Where(i => i.Height > 180).OrderBy(i => i.LastName).ToList();
                persons.Dump();
                break;
            }
        case 2:
            using (PeopleContext context = new PeopleContext())
            {

                int id = AnsiConsole.Ask<int>("Indtast id: ");
                var person = context.People.Include(i => i.Country).FirstOrDefault(i => i.PersonId == id);
                person.Dump();
                break;
            }
        case 3:
            using (PeopleContext context = new PeopleContext())
            {

                var person = context.People.FirstOrDefault(i => i.PersonId == 1);
                if (person != null)
                {
                    person.FirstName = AnsiConsole.Ask<string>("Fornavn: ");
                    person.LastName = AnsiConsole.Ask<string>("Efternavn: ");
                    context.SaveChanges();
                }
                AnsiConsole.WriteLine("Person rettet");
                break;
            }
        case 4:
            using (PeopleContext context = new PeopleContext())
            {
                var countries = context.Countries.ToList();
                var selectedCountry = AnsiConsole.Prompt(
                    new SelectionPrompt<dynamic>()
                        .Title("Vælg et land")
                        .PageSize(20)
                        .AddChoices(countries)
                        .UseConverter(p => p.Name)
                );
                var person = new Person
                {
                    FirstName = AnsiConsole.Ask<string>("Fornavn: ", "Anders"),
                    LastName = AnsiConsole.Ask<string>("Fornavn: ", "And"),
                    DateOfBirth = new DateTime(2000, 1, 1),
                    Gender = GenderType.Male,
                    IsHealthy = true,
                    CountryId = selectedCountry.CountryId,
                };
                context.People.Add(person);
                context.SaveChanges();
                AnsiConsole.WriteLine("Person opretter med id " + person.PersonId);
                break;
            }
        case 5:

            using (PeopleContext context = new PeopleContext())
            {
                var id = AnsiConsole.Ask<int>("Indtast id: ");
                var person = context.People.FirstOrDefault(i => i.PersonId == id);
                if (person != null)
                {
                    context.People.Remove(person);
                    context.SaveChanges();
                    AnsiConsole.WriteLine("Person slettet");
                }
                else
                {
                    AnsiConsole.WriteLine("Person ikke fundet");
                }

                break;
            }
        case 6:
            using (PeopleContext context = new PeopleContext())
            {

                string efternavn = Guid.NewGuid().ToString().Substring(0, 5);
                AnsiConsole.WriteLine("Forsøger at oprette en ny person med efternavn " + efternavn);
                using (var transaction = context.Database.BeginTransaction())
                {
                    try
                    {
                        var p = new Person
                        {
                            LastName = efternavn,
                            CountryId = 1,
                        };
                        context.People.Add(p);
                        context.SaveChanges();
                        bool fejl = AnsiConsole.Ask<bool>("Smid en fejl?: ", true);
                        if (!fejl)
                            transaction.Commit();
                        else
                            throw new Exception("Fejl");
                    }
                    catch (Exception ex)
                    {
                        transaction.Rollback();
                        AnsiConsole.WriteLine("Fejl - transaktion rullet tilbage");
                    }
                }
                break;
            }
        case 7:
            using (PeopleContext context = new PeopleContext())
            {
                int antal = AnsiConsole.Ask<int>("Hvor mange personer skal hentes: ", 5);
                var persons = await context.People.Take(antal).ToListAsync();
                persons.Dump();
                break;
            }
        case 8:
            using (PeopleContext context = new PeopleContext())
            {
                var countries = context.Countries.ToList();
                var selectedCountry = AnsiConsole.Prompt(
                    new SelectionPrompt<dynamic>()
                        .Title("Vælg et land")
                        .PageSize(20)
                        .AddChoices(countries)
                        .UseConverter(p => p.Name)
                );
                int id = selectedCountry.CountryId;
                var countriesPeople = context.Countries.Include(i => i.People).Where(i => i.CountryId == id).ToList();
                countriesPeople.Dump();
                break;
            }
    }
    if (menu != "Afslut" && !AnsiConsole.Confirm("\r\nFortsæt?", true))
        break;
}

public enum GenderType
{
    Male,
    Female
}

public class Person
{
    public int PersonId { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public bool IsHealthy { get; set; }
    public GenderType Gender { get; set; }
    public int Height { get; set; }
    public int CountryId { get; set; }
    public Country? Country { get; set; }

}


public class Country
{
    public int CountryId { get; set; }
    public string? Name { get; set; }
    public List<Person>? People { get; set; }
}

public class PeopleContext : DbContext
{
    private readonly string pathToDb;

    public DbSet<Person> People { get; set; }
    public DbSet<Country> Countries { get; set; }

    public PeopleContext(string pathToDb = @"c:\temp\people.db")
    {
        this.pathToDb = pathToDb;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=" + pathToDb);
        optionsBuilder.LogTo(a => System.IO.File.AppendAllText(@"c:\temp\efdemo.log", a));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>(e =>
        {
            e.ToTable("Person");
            // Binder enum til en int i databasen
            e.Property(i => i.Gender).HasConversion(x => x.ToString(), x => (GenderType)Enum.Parse(typeof(GenderType), x));
            // Hvis man ønsker at have en liste af personer fra Country
            e.HasOne(p => p.Country).WithMany(b => b.People).HasForeignKey(p => p.CountryId);
        });

        modelBuilder.Entity<Country>(e =>
        {
            e.ToTable("Country");
        });

        base.OnModelCreating(modelBuilder);
    }
}

Opgaver