ASP.NET Core Web API i baza podataka: Entity Framework Core

Lekcija 4 od 7 · ASP.NET Core

Proizvodi se sada čuvaju u bazi podataka. Koristimo Entity Framework Core (EF Core) – ORM koji C# klase pretvara u tabele, a LINQ upite u SQL. Za učenje je dovoljan SQLite (baza u jednom fajlu, bez instalacije servera); prelazak na SQL Server je promena jednog reda.

Pre nego što počneš: potrebno predznanje – Web API kontroleri, isti pojmovi u Django-u (za poređenje).
Kako da učiš: radi u svom projektu korak po korak i posle svake izmene pokreni aplikaciju; zadatak na kraju prvo uradi sam/a. Kod je u C#-u za .NET 8 ili noviji (preporuka: aktuelna LTS verzija); naziv fajla je u komentaru na vrhu svakog primera.

Paketi i alat

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef

Paketi se preuzimaju sa NuGet-a i upisuju u Prodavnica.csproj. Glavni broj verzije EF Core-a treba da odgovara verziji .NET-a – za projekat na .NET 8 dodaj --version 8.*.

DbContext: veza sa bazom

// Data/ProdavnicaContext.cs
using Microsoft.EntityFrameworkCore;
using Prodavnica.Models;

namespace Prodavnica.Data;

public class ProdavnicaContext(DbContextOptions<ProdavnicaContext> options) : DbContext(options)
{
    public DbSet<Proizvod> Proizvodi => Set<Proizvod>();       // tabela Proizvodi

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // početni podaci – upisuju se migracijom
        modelBuilder.Entity<Proizvod>().HasData(
            new Proizvod { Id = 1, Naziv = "Sveska", Cena = 120, Kolicina = 50 },
            new Proizvod { Id = 2, Naziv = "Olovka", Cena = 60, Kolicina = 200 },
            new Proizvod { Id = 3, Naziv = "Lenjir", Cena = 150, Kolicina = 30 });
    }
}
{
  "ConnectionStrings": {
    "Prodavnica": "Data Source=prodavnica.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
// Program.cs – Web API + baza (lekcija 4) + statičke strane (lekcija 5)
using Microsoft.EntityFrameworkCore;
using Prodavnica.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<ProdavnicaContext>(opcije =>
    opcije.UseSqlite(builder.Configuration.GetConnectionString("Prodavnica")));

var app = builder.Build();
app.UseHttpsRedirection();
app.UseDefaultFiles();                      // lekcija 5: "/" → wwwroot/index.html
app.UseStaticFiles();                       // lekcija 5: fajlovi iz wwwroot
app.MapControllers();
app.Run();

AddDbContext registruje bazu kao servis. Kontroler je dobija kroz konstruktor – to se zove ubrizgavanje zavisnosti (dependency injection): kontroler ne pravi vezu sa bazom sam, nego je traži, a ASP.NET Core je daje.

Migracije: od klase do tabele

dotnet ef migrations add Pocetna
dotnet ef database update

Prva komanda pravi folder Migrations sa opisom tabele, a druga pravi fajl prodavnica.db i upisuje početne podatke. Kada promeniš model (npr. dodaš svojstvo), ponovi obe komande sa novim imenom migracije. Bazu možeš pregledati alatom DB Browser for SQLite ili dodatkom za VS Code.

Kontroler koji radi sa bazom

// Controllers/ProizvodiController.cs – podaci iz baze (lekcija 4)
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Prodavnica.Data;
using Prodavnica.Models;

namespace Prodavnica.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProizvodiController(ProdavnicaContext db) : ControllerBase    // baza stiže kroz konstruktor
{
    [HttpGet]
    public async Task<List<Proizvod>> Svi() =>
        await db.Proizvodi.OrderBy(p => p.Naziv).ToListAsync();

    // GET /api/proizvodi/pretraga?q=ol
    [HttpGet("pretraga")]
    public async Task<List<Proizvod>> Pretraga(string q) =>
        await db.Proizvodi.Where(p => p.Naziv.Contains(q)).ToListAsync();

    [HttpGet("{id:int}")]
    public async Task<ActionResult<Proizvod>> Jedan(int id)
    {
        var p = await db.Proizvodi.FindAsync(id);
        if (p == null) return NotFound();
        return p;
    }

    [HttpPost]
    public async Task<ActionResult<Proizvod>> Dodaj(Proizvod novi)
    {
        novi.Id = 0;                                   // id dodeljuje baza
        db.Proizvodi.Add(novi);
        await db.SaveChangesAsync();                   // INSERT
        return CreatedAtAction(nameof(Jedan), new { id = novi.Id }, novi);
    }

    [HttpPut("{id:int}")]
    public async Task<IActionResult> Izmeni(int id, Proizvod izmenjen)
    {
        var p = await db.Proizvodi.FindAsync(id);
        if (p == null) return NotFound();
        p.Naziv = izmenjen.Naziv;
        p.Cena = izmenjen.Cena;
        p.Kolicina = izmenjen.Kolicina;
        await db.SaveChangesAsync();                   // UPDATE
        return NoContent();
    }

    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Obrisi(int id)
    {
        var p = await db.Proizvodi.FindAsync(id);
        if (p == null) return NotFound();
        db.Proizvodi.Remove(p);
        await db.SaveChangesAsync();                   // DELETE
        return NoContent();
    }
}
EF Core / LINQSQL (otprilike)
db.Proizvodi.ToListAsync()SELECT * FROM Proizvodi
.Where(p => p.Naziv.Contains(q))WHERE instr(Naziv, @q) > 0
.OrderBy(p => p.Naziv)ORDER BY Naziv
FindAsync(id)SELECT … WHERE Id = @id
Add / izmena / Remove + SaveChangesAsync()INSERT / UPDATE / DELETE

async/await: dok čeka bazu, server ne „stoji“ nego obrađuje druge zahteve. EF Core uvek šalje vrednosti kao parametre, pa je otporan na SQL injekciju (Bezbednost). U SQLite-u je Contains osetljiv na velika i mala slova.

Program.cs i ostali zahtevi iz .http fajla ostaju isti – promenio se samo način čuvanja podataka. Podaci sada preživljavaju ponovno pokretanje servera.

SQL Server i Dapper

Želiš…Promena
SQL Server umesto SQLite-apaket Microsoft.EntityFrameworkCore.SqlServer, u Program.cs UseSqlServer(...), adresa npr. Server=(localdb)\\MSSQLLocalDB;Database=Prodavnica;Trusted_Connection=True;TrustServerCertificate=True; zatim nova migracija
SQL upite pisati sam/abiblioteka Dapper (koristi je prva verzija ove lekcije): brza, ali SQL pišeš ručno – uvek sa parametrima (@id)

Za školske projekte preporučujemo EF Core + SQLite; SQL Server i SQL Server Management Studio koriste se u firmama.

Videi iz prve verzije lekcije (SQL Server i Dapper)

Napomena uz video: snimljen je za prvu verziju lekcija (.NET 5/6, klasa Startup, SQL Server i Dapper). Ideje su iste, ali je kod na ovoj strani pisan za današnji .NET (sve u Program.cs) – kada se razlikuju, važi kod sa strane.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje.

ZADATAK 01Težina: teže

Kategorije proizvoda

Dodaj model Kategorija (Id, Naziv) i vezu: svaki proizvod pripada jednoj kategoriji. Napravi migraciju i akciju GET /api/proizvodi/kategorija/2 koja vraća proizvode iz kategorije 2.

Česte greške i saveti
  • Greška no such table: Proizvodi znači da nije pokrenut dotnet ef database update.
  • Ne zaboravi SaveChangesAsync() – bez njega izmene ostaju samo u memoriji.
  • Fajl prodavnica.db ne stavljaj na GitHub (dodaj u .gitignore); baza se pravi iz migracija.
  • Adresu baze (connection string) sa lozinkom nikad ne upisuj u kod koji je javan.

Proveri znanje

Otvori kviz

Entity Framework Core

1. Šta radi dotnet ef database update?




2. Kako kontroler dobija objekat ProdavnicaContext?




3. Šta se dešava bez poziva SaveChangesAsync()?