MODUL 2 - LEKCIJA 2

EF Migracije

Enable-Migrations, Add-Migration, Update-Database - Upravljanje promjenama u bazi

⏱️ Trajanje: ~3 časa 📚 Nivo: Srednji 🎯 Praktični primjeri: 3

📖 Šta su Migracije?

Entity Framework Migracije omogućavaju vam da upravljate promjenama u strukturi baze podataka kroz kod. Umjesto ručnog pisanja SQL skripti, migracije automatski generišu SQL kod na osnovu promjena u vašim Model klasama.

🔑 Zašto Koristiti Migracije?

  • Version Control: Migracije su C# fajlovi - možete ih commit-ovati u Git
  • Reproducibilnost: Ista migracija će kreirati istu bazu na svim okruženjima
  • Rollback: Možete vratiti promjene ako nešto pođe po zlu
  • Timski Rad: Svaki developer može primijeniti iste promjene
  • Automatski SQL: Ne morate pisati CREATE TABLE, ALTER TABLE, itd. ručno

🔄 Životni Ciklus Migracija i __MigrationHistory

Da biste razumjeli kako Entity Framework zna koje su migracije primijenjene, potrebno je razumjeti njegov unutrašnji mehanizam i tabelu __MigrationHistory.

EF Migrations Lifecycle

Slika: Životni ciklus EF Migracija

Šta je __MigrationHistory?

Prilikom prvog pokretanja Update-Database, EF kreira sistemsku tabelu pod imenom __MigrationHistory u vašoj bazi. Njena uloga je esencijalna:

  • Prati MigrationId (npr. 20231015120000_InitialCreate) svake izvršene migracije.
  • Sadrži Model kolonu (baza čuva binarni potpis vaših entiteta) putem koje EF provjerava jesu li klase i baza sinhronizovane.
  • Sprečava ponovno pokretanje već izvršenih SQL skripti prilikom svakog poziva Update-Database.

🚀 Enable-Migrations: Aktiviranje Migracija

Prije nego što možete koristiti migracije, morate ih omogućiti u vašem projektu. Ovo se radi jednom po projektu.

Koraci za Omogućavanje Migracija

  1. Otvorite Package Manager Console
    • Tools → NuGet Package Manager → Package Manager Console
    • Ili View → Other Windows → Package Manager Console
  2. Izvršite komandu:
    🛠️ Enable-Migrations Komanda
    PM> Enable-Migrations
  3. Provjerite da li je kreiran Migrations folder
    • U Solution Explorer-u trebate vidjeti Migrations/ folder
    • U njemu će biti Configuration.cs fajl

Configuration.cs Fajl

Nakon Enable-Migrations, EF kreira Configuration.cs fajl koji sadrži postavke za migracije.

🛠️ Configuration.cs
// Migrations/Configuration.cs
namespace JavnaUprava.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<JavnaUprava.Models.JavnaUpravaDbContext>
    {
        public Configuration()
        {
            // Automatske migracije - preporučeno false za produkciju
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(JavnaUprava.Models.JavnaUpravaDbContext context)
        {
            // Seediranje početnih ministarstava
            context.Departments.AddOrUpdate(
                d => d.DepartmentCode,
                new Models.Department { DepartmentName = "Ministarstvo Finansija", DepartmentCode = "MF", Budget = 50000000 },
                new Models.Department { DepartmentName = "Ministarstvo Pravde", DepartmentCode = "MP", Budget = 35000000 }
            );
            
            // Seediranje administratora
             context.Employees.AddOrUpdate(
                e => e.JMBG,
                new Models.Employee 
                { 
                    FirstName = "Admin", 
                    LastName = "Adminić", 
                    JMBG = "1234567890123", 
                    DepartmentID = 1 // Vežemo za MF
                }
            );
        }
    }
}

💡 AutomaticMigrationsEnabled

AutomaticMigrationsEnabled = false znači da morate eksplicitno kreirati migracije. Preporučeno je ostaviti na false jer daje više kontrole i omogućava review migracija prije primjene.

➕ Add-Migration: Kreiranje Migracije

Kada promijenite Model klase (dodate svojstvo, promijenite tip, itd.), morate kreirati novu migraciju koja će zabilježiti te promjene.

Osnovna Sintaksa

🛠️ Add-Migration Komanda
PM> Add-Migration MigrationName

# Primjer:
PM> Add-Migration InitialCreate
PM> Add-Migration AddEmailToEmployee
PM> Add-Migration CreatePostsTable

Primjer: Kreiranje Prve Migracije

Pretpostavimo da imate Model klase Employee i Department koje ste kreirali u prethodnoj lekciji, ali još nema baze podataka.

🛠️ Kreiranje Prve Migracije
# 1. Omogućite migracije (ako već niste)
PM> Enable-Migrations

# 2. Kreirajte prvu migraciju koja će kreirati sve tabele
PM> Add-Migration InitialCreate

EF će kreirati novi fajl u Migrations/ folderu sa nazivom kao što je 20240101120000_InitialCreate.cs (timestamp + ime migracije).

Struktura Migracije Fajla

🛠️ Primjer Migracije Fajla
// Migrations/20240101120000_InitialCreate.cs
namespace JavnaUprava.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            // Kreiranje tabela u 'Stats' shemi
            CreateTable(
                "Stats.Departments",
                c => new
                    {
                        DepartmentID = c.Int(nullable: false, identity: true),
                        DepartmentName = c.String(nullable: false, maxLength: 100),
                        DepartmentCode = c.String(maxLength: 20),
                        Budget = c.Decimal(nullable: false, precision: 18, scale: 2),
                    })
                .PrimaryKey(t => t.DepartmentID);

            CreateTable(
                "Stats.Employees",
                c => new
                    {
                        EmployeeID = c.Int(nullable: false, identity: true),
                        FirstName = c.String(nullable: false, maxLength: 50),
                        LastName = c.String(nullable: false, maxLength: 50),
                        JMBG = c.String(nullable: false, maxLength: 13),
                        DepartmentID = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.EmployeeID)
                .ForeignKey("Stats.Departments", t => t.DepartmentID, cascadeDelete: true)
                .Index(t => t.DepartmentID);
        }

        public override void Down()
        {
            DropForeignKey("Stats.Employees", "DepartmentID", "Stats.Departments");
            DropIndex("Stats.Employees", new[] { "DepartmentID" });
            DropTable("Stats.Employees");
            DropTable("Stats.Departments");
        }
    }
}

🔑 Up() i Down() Metode

  • Up(): Primjenjuje promjene (kreira tabele, dodaje kolone, itd.)
  • Down(): Vraća promjene (briše tabele, uklanja kolone, itd.)

Down() metoda omogućava rollback - vraćanje na prethodnu verziju baze.

Primjer: Dodavanje Nove Kolone

Pretpostavimo da želite dodati PhoneNumber svojstvo u Employee klasu.

🛠️ 1. Promjena Model Klase
// Models/Employee.cs
public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    
    // Nova kolona
    [StringLength(20)]
    public string PhoneNumber { get; set; }
    
    public DateTime HireDate { get; set; }
    public int DepartmentId { get; set; }
    public virtual Department Department { get; set; }
}
🛠️ 2. Kreiranje Migracije
PM> Add-Migration AddPhoneNumberToEmployee
🛠️ 3. Generisana Migracija
// Migrations/20240101130000_AddBudgetToDepartment.cs
public partial class AddBudgetToDepartment : DbMigration
{
    public override void Up()
    {
        AddColumn("Stats.Departments", "Budget", c => c.Decimal(nullable: false, precision: 18, scale: 2));
    }

    public override void Down()
    {
        DropColumn("Stats.Departments", "Budget");
    }
}

💾 Update-Database: Primjena Migracija

Nakon što kreirate migraciju, morate je primijeniti na bazu podataka. Ovo će izvršiti SQL kod iz Up() metode.

Osnovna Sintaksa

🛠️ Update-Database Komande
# Primjenjuje sve migracije koje još nisu primijenjene
PM> Update-Database

# Primjenjuje migraciju do određene migracije
PM> Update-Database -TargetMigration MigrationName

# Vraća bazu na određenu migraciju (rollback)
PM> Update-Database -TargetMigration PreviousMigrationName

# Prikazuje SQL koji će biti izvršen (bez primjene)
PM> Update-Database -Script

# Primjenjuje migraciju na određeni connection string
PM> Update-Database -ConnectionString "..." -ConnectionProviderName "System.Data.SqlClient"

Primjer: Primjena Prve Migracije

🛠️ Kreiranje Baze Podataka
# 1. Kreirajte migraciju
PM> Add-Migration InitialCreate

# 2. Primijenite migraciju (kreira bazu i tabele)
PM> Update-Database

Nakon Update-Database, EF će:

  1. Kreirati bazu podataka (ako ne postoji)
  2. Kreirati tabele na osnovu Model klasa
  3. Kreirati foreign key veze
  4. Kreirati indekse

💡 Provjera Rezultata

Nakon Update-Database, možete provjeriti da li je baza kreirana:

  • Otvorite Server Explorer u Visual Studiju
  • Proširite Data Connections
  • Trebate vidjeti vašu bazu sa tabelama

Primjer: Primjena Dodatne Migracije

🛠️ Dodavanje Nove Kolone
# 1. Promijenite Model klasu (dodajte PhoneNumber)

# 2. Kreirajte migraciju
PM> Add-Migration AddPhoneNumberToEmployee

# 3. Primijenite migraciju
PM> Update-Database

↩️ Rollback Migracija

Ponekad možete htjeti vratiti promjene u bazi. EF omogućava rollback kroz Update-Database -TargetMigration komandu.

🛠️ Rollback Primjer
# Pretpostavimo da imate migracije:
# - InitialCreate
# - AddPhoneNumberToEmployee
# - AddSalaryToEmployee

# Vraćanje na AddPhoneNumberToEmployee (uklanja AddSalaryToEmployee promjene)
PM> Update-Database -TargetMigration AddPhoneNumberToEmployee

# Vraćanje na početak (briše sve tabele)
PM> Update-Database -TargetMigration $InitialDatabase

⚠️ Opasnost Rollback-a

Rollback može obrisati podatke! Ako vratite migraciju koja je dodala kolonu, svi podaci u toj koloni će biti izgubljeni. Uvijek napravite backup prije rollback-a u produkciji.

👥 Timski Rad i Rješavanje Konflikata

Radeći na sistemima u javnoj upravi, često više inženjera radi na istoj bazi na različitim modulima infrastrukture, što dovodi do konflikata u migracijama (npr. dva inženjera naprave i prime različite migracije u isto vrijeme).

Rješavanje Konflikata u Migracijama (Best Practice)

Ako povučete kod (git pull) koji sadrži novu migraciju od kolege, a vi ste u međuvremenu lokalno definisali vlastitu na čekanju, Entity Framework će prijaviti konflikt jer hash modeli nisu isti. Pratite ove korake:

  1. Vratite bazu nazad (Rollback): Izvršite Update-Database -TargetMigration ImeZadnjeZajednickeMigracije da obrišete vaš dio iz lokalne baze.
  2. Uklonite vašu lokalnu migraciju: Obrišite konfliktan .cs fajl i uklonite ga iz projekta.
  3. Preuzmite promjene: Primijenite kod svog kolege upotrebom Update-Database.
  4. Ponovo re-kreirajte: Pozovite Add-Migration ImeVaseMigracije -Force. Ovo ponovo kreira migraciju tako da se nadovezuje na zadnju ispravnu strukturu bez narušavanja historije.

🌱 Seed Data: Početni Podaci

Seed data su početni podaci koji se ubacuju u bazu kada se primjenjuje migracija. Ovo je korisno za test podatke, admin korisnike, lookup tabele, itd.

Korištenje Seed() Metode

🛠️ Seed Data Primjer
// Migrations/Configuration.cs
protected override void Seed(JavnaUprava.Models.JavnaUpravaDbContext context)
{
    // Dodavanje početnih ministarstava
    context.Departments.AddOrUpdate(
        d => d.DepartmentCode,
        new Department { DepartmentName = "Ministarstvo Finansija", DepartmentCode = "MF" },
        new Department { DepartmentName = "Ministarstvo Zdravlja", DepartmentCode = "MZ" }
    );

    context.SaveChanges();
}

Primjena Seed Data

🛠️ Update-Database sa Seed Data
# Seed data se automatski primjenjuje kada izvršite Update-Database
PM> Update-Database

💡 AddOrUpdate Metoda

AddOrUpdate provjerava da li entitet već postoji (na osnovu određenog svojstva, npr. Email). Ako postoji, ažurira ga. Ako ne postoji, dodaje novi. Ovo osigurava da seed data ne kreira duplikate pri višestrukim izvršavanjima.

🎯 Praktična Vježba: Kompletna Migracija

Zadatak: Migracija Javne Uprave

Kreirajte i primijenite migracije za vaš JavnaUpravaDbContext.

Zadatak 1: Omogućite migracije i kreirajte InitialCreate koja formira Stats.Departments i Stats.Employees tabele.

Zadatak 2: Dodajte IsActive (bool) u Department model, te kreirajte migraciju AddIsActive.

Zadatak 3: Kreirajte Seed metodu koja puni tabelu sa 3 ministarstva i primijenite je pomoću Update-Database.

💡 Rješenje: Konzola
PM> Enable-Migrations
PM> Add-Migration InitialCreate
PM> Update-Database
PM> Add-Migration AddIsActive
PM> Update-Database

✅ Zaključak

U ovoj lekciji ste naučili:

📚 Sljedeća Lekcija

U Lekciji 2.3 ćemo naučiti kako izvršavati CRUD operacije koristeći Entity Framework. Naučićete LINQ to Entities, Eager/Lazy/Explicit loading, i kako efikasno raditi sa povezanim podacima.