guides

EF Core DB to API

En lille guide til hvordan data bevæger sig fra database til API.

1) Installer pakker

Fra en tom Web API:

CLI – Opret projekt og tilføj EF Core (SQL Server)

bash · 9 lines

bash
1dotnet new webapi -n Demo.Api
2
3// EF Core runtime
4dotnet add Demo.Api package Microsoft.EntityFrameworkCore
5dotnet add Demo.Api package Microsoft.EntityFrameworkCore.SqlServer
6
7// EF Core tooling til CLI
8dotnet tool install --global dotnet-ef
9dotnet add Demo.Api package Microsoft.EntityFrameworkCore.Design

Hvorfor disse pakker?

  • Microsoft.EntityFrameworkCore – selve EF Core runtime.
  • Microsoft.EntityFrameworkCore.SqlServer – provider til SQL Server.
  • Microsoft.EntityFrameworkCore.Design – krævet for migrations fra CLI.
  • dotnet-ef – giver dig dotnet ef kommandoerne i terminalen.
Fejlsøgning
  • No executable found matching command ‘dotnet-ef’”: Sørg for at tool’et er installeret globalt, og at din PATH er opdateret.
  • Hvis du har flere projekter, så brug --project for at pege på dit API-projekt.

2) Opret din model

Models/Product.cs

csharp · 9 lines

csharp
1namespace Demo.Api.Models;
2
3public class Product
4{
5 public int Id { get; set; } // PK
6 public string Name { get; set; } = "";
7 public decimal Price { get; set; }
8 public bool IsActive { get; set; } = true;
9}

Hvad er en model?

En model er din C#-klasse, der afspejler en tabel i databasen. EF Core map’er egenskaber til kolonner og sørger for, at ændringer i modeller kan omsættes til migrations.

  • Hold modeller enkle og uden forretningslogik.
  • Validering kan både ligge i API-laget og i databasen.

3) Opret DbContext

Data/AppDbContext.cs

csharp · 19 lines

csharp
1using Demo.Api.Models;
2using Microsoft.EntityFrameworkCore;
3
4namespace Demo.Api.Data;
5
6public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
7{
8 public DbSet<Product> Products => Set<Product>();
9
10 protected override void OnModelCreating(ModelBuilder b)
11 {
12 b.Entity<Product>(e =>
13 {
14 e.HasKey(x => x.Id);
15 e.Property(x => x.Name).HasMaxLength(200).IsRequired();
16 e.HasIndex(x => x.Name).IsUnique(false);
17 });
18 }
19}

Hvad er DbContext?

DbContext er EF Cores gateway til databasen. Den sporer ændringer, oversætter LINQ til SQL og udfører queries. Hver DbSet<T> svarer typisk til en tabel.

  • Brug OnModelCreating til constraints, indexes og relationer.
  • I web apps er livstiden typisk scoped pr. request.
Ydelse & tracking
  • Brug AsNoTracking() til read-only queries.
  • EF Core kan batche flere kommandoer ved SaveChanges.

4) Connection string

appsettings.json

json · 5 lines

json
1{
2 "ConnectionStrings": {
3 "Default": "Server=(localdb)\\MSSQLLocalDB;Database=DemoApiDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
4 }
5}

Connection strings

Læg connection strings i appsettings.json og læs dem via IConfiguration. Undgå at committe produktionshemmeligheder.

  • LocalDB: nemt lokalt.
  • TrustServerCertificate: praktisk lokalt, men brug rigtigt certifikat i produktion.

5) Registrér EF Core i Program.cs

Program.cs – DbContext + Swagger

csharp · 23 lines

csharp
1using Demo.Api.Data;
2using Microsoft.EntityFrameworkCore;
3
4var builder = WebApplication.CreateBuilder(args);
5
6// 1) DbContext
7builder.Services.AddDbContext<AppDbContext>(opt =>
8 opt.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
9
10// 2) Swagger
11builder.Services.AddEndpointsApiExplorer();
12builder.Services.AddSwaggerGen();
13
14var app = builder.Build();
15
16if (app.Environment.IsDevelopment())
17{
18 app.UseSwagger();
19 app.UseSwaggerUI();
20}
21
22app.MapGet("/", () => "EF Core API kører!");
23app.Run();

DI og livstider

AddDbContext registrerer AppDbContext som scoped. Det betyder én context pr. HTTP request.

  • Swagger er godt til dokumentation og test under udvikling.
  • Overvej app.UseHttpsRedirection() og CORS når frontend kobles på.

6) Migration & database

CLI – Migration og database

bash · 9 lines

bash
1// generér migration baseret på DbContext + modeller
2dotnet ef migrations add InitialCreate --project Demo.Api
3
4// anvend migrationer på databasen
5dotnet ef database update --project Demo.Api
6
7// fremover ved ændringer:
8// dotnet ef migrations add AddSomething
9// dotnet ef database update

Hvad er en migration?

En migration er en versioneret ændring i databaseskemaet. Den beskriver, hvordan databasen skal ændres fra én tilstand til en anden.

  • Kør add når modeller ændrer sig.
  • Kør update for at anvende ændringerne.
Typiske problemer
  • Unable to create an object of type AppDbContext”: tjek Program.cs setup eller design-time factory.
  • Brug --startup-project hvis du har flere projekter.

7) Seed dev data

Program.cs – Seed

csharp · 16 lines

csharp
1// ...efter var app = builder.Build();
2using (var scope = app.Services.CreateScope())
3{
4 var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
5
6 if (!db.Products.Any())
7 {
8 db.Products.AddRange(
9 new() { Name = "Coffee Beans", Price = 79.95m, IsActive = true },
10 new() { Name = "Mechanical Keyboard", Price = 799.00m, IsActive = true },
11 new() { Name = "USB-C Cable", Price = 49.00m, IsActive = true }
12 );
13
14 db.SaveChanges();
15 }
16}

Hvorfor seede?

Seed giver dig realistiske testdata med det samme. Hold dev-seed adskilt fra produktion.

  • Brug små datasæt, der er gode nok til test.
  • Brug HasData() til statiske opslagsværdier.

8) API-endpoints

Program.cs – Products endpoints

csharp · 52 lines

csharp
1app.MapGroup("/api/products").WithTags("Products").MapProductsApi();
2
3public static class ProductsApi
4{
5 public static RouteGroupBuilder MapProductsApi(this RouteGroupBuilder group)
6 {
7 group.MapGet("/", async (AppDbContext db) =>
8 await db.Products.Where(p => p.IsActive).ToListAsync());
9
10 group.MapGet("/{id:int}", async (int id, AppDbContext db) =>
11 await db.Products.FindAsync(id) is { } p
12 ? Results.Ok(p)
13 : Results.NotFound());
14
15 group.MapPost("/", async (Product dto, AppDbContext db) =>
16 {
17 db.Products.Add(dto);
18 await db.SaveChangesAsync();
19
20 return Results.Created($"/api/products/{dto.Id}", dto);
21 });
22
23 group.MapPut("/{id:int}", async (int id, Product dto, AppDbContext db) =>
24 {
25 var p = await db.Products.FindAsync(id);
26
27 if (p is null) return Results.NotFound();
28
29 p.Name = dto.Name;
30 p.Price = dto.Price;
31 p.IsActive = dto.IsActive;
32
33 await db.SaveChangesAsync();
34
35 return Results.NoContent();
36 });
37
38 group.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
39 {
40 var p = await db.Products.FindAsync(id);
41
42 if (p is null) return Results.NotFound();
43
44 db.Products.Remove(p);
45 await db.SaveChangesAsync();
46
47 return Results.NoContent();
48 });
49
50 return group;
51 }
52}

Minimal API vs Controllers

Minimal API er god til små og mellemstore services. Controllers giver mere struktur i større projekter.

  • Brug MapGroup og WithTags for pæn Swagger.
  • Brug DTO’er til input og output i rigtige projekter.

9) Repository-lag

Data/IProductsRepository.cs

csharp · 10 lines

csharp
1using Demo.Api.Models;
2
3public interface IProductsRepository
4{
5 Task<List<Product>> GetAllAsync();
6 Task<Product?> GetByIdAsync(int id);
7 Task<Product> CreateAsync(Product p);
8 Task UpdateAsync(Product p);
9 Task DeleteAsync(Product p);
10}
Data/ProductsRepository.cs

csharp · 32 lines

csharp
1using Demo.Api.Data;
2using Demo.Api.Models;
3using Microsoft.EntityFrameworkCore;
4
5public class ProductsRepository(AppDbContext db) : IProductsRepository
6{
7 public Task<List<Product>> GetAllAsync()
8 => db.Products.Where(p => p.IsActive).ToListAsync();
9
10 public Task<Product?> GetByIdAsync(int id)
11 => db.Products.FirstOrDefaultAsync(p => p.Id == id);
12
13 public async Task<Product> CreateAsync(Product p)
14 {
15 db.Products.Add(p);
16 await db.SaveChangesAsync();
17
18 return p;
19 }
20
21 public async Task UpdateAsync(Product p)
22 {
23 db.Products.Update(p);
24 await db.SaveChangesAsync();
25 }
26
27 public async Task DeleteAsync(Product p)
28 {
29 db.Products.Remove(p);
30 await db.SaveChangesAsync();
31 }
32}
Program.cs – DI

csharp · 1 lines

csharp
1builder.Services.AddScoped<IProductsRepository, ProductsRepository>();

Hvorfor et repository-lag?

Et repository isolerer dataadgang, så endpoints forbliver tynde og nemmere at teste.

  • Brug repositories til domæneorienterede metoder.
  • Undgå bare at kopiere DbSet 1:1.

10) EF Core CLI – hurtig oversigt

EF Core – de vigtigste kommandoer

bash · 11 lines

bash
1// Ny migration ud fra dine modelændringer
2dotnet ef migrations add AddNewFields --project Demo.Api
3
4// Anvend migrationer på databasen
5dotnet ef database update --project Demo.Api
6
7// Gå tilbage til en specifik migration
8dotnet ef database update InitialCreate --project Demo.Api
9
10// Fjern seneste migration, hvis den ikke er applied
11dotnet ef migrations remove --project Demo.Api

Overblik

Det normale workflow er: add update → eventuelt remove hvis noget skal laves om.

Tip: SQLite

  • Erstat Microsoft.EntityFrameworkCore.SqlServer
  • Med Microsoft.EntityFrameworkCore.Sqlite
  • Skift til opt.UseSqlite(...) i Program.cs