MODUL 2 - LEKCIJA 1

Uvod u Entity Framework 6

Database-First vs Code-First pristup i kreiranje DbContext klase

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

📖 Šta je Entity Framework?

Entity Framework (EF) je Microsoft-ov Object-Relational Mapping (ORM) framework koji omogućava razvojima da rade sa bazom podataka koristeći .NET objekte umjesto direktnog pisanja SQL upita. EF automatski prevodi LINQ upite u SQL i mapira rezultate nazad u objekte.

🔑 Zašto Koristiti Entity Framework?

  • Produktivnost: Manje koda - ne morate pisati SQL upite ručno
  • Type Safety: Kompajler hvata greške prije runtime-a
  • IntelliSense: Automatsko dovršavanje koda u Visual Studiju
  • Migracije: Automatsko upravljanje promjenama u bazi podataka
  • Cross-Database: Isti kod radi sa SQL Server, MySQL, PostgreSQL, itd.

🏗️ Arhitektura Entity Framework-a

Za dublje razumijevanje, inženjeri moraju poznavati slojeve apstrakcije koje pruža Entity Framework. EF funkcioniše kao prevodilac i sloj apstrakcije između aplikacije (biznis logike) i same relacione baze podataka. To omogućava izolaciju baze od koda, što je izuzetno važno u razvoju kompleksnih platformi kakve čine e-Uprava.

EF Arhitektura

Slika: Slojevi Entity Framework Arhitekture

Ključne Komponente Arhitekture:

  • Entity Data Model (EDM): Centralni dio EF-a koji se sastoji od tri modela:
    • Conceptual Model: C# klase (Entiteti) na visokom nivou (npr. Department, Employee).
    • Storage Model: Reprezentacija fizičke sheme baze podataka (Tabele, Kolone, Ključevi).
    • Mapping Model: Pravila koja definišu kako se Conceptual Model mapira na Storage Model.
  • Object Services (DbContext): Omogućavaju interakciju koda sa EDM-om. One pretvaraju podatke vraćene iz baze u konkretne .NET objekte, prate promjene na tim objektima (Change Tracking - ključno za naknadno spašavanje) i upravljaju učitavanjem relacija.
  • ADO.NET Data Provider: Prevodi generisani Query/Command u SQL specifičan za korištenu bazu (npr. T-SQL za MS SQL Server) i izvršava ga direktno komunicirajući s bazom kroz običnu ADO.NET konekciju.

📦 Entity Framework Verzije

U ovom tutorijalu koristimo Entity Framework 6, posljednju verziju za .NET Framework. Postoji i EF Core za .NET Core/.NET 5+, ali to ćemo pokriti u Modulu 9.

Verzija Platforma Status
EF 6.x .NET Framework 4.x ✅ Maintenance mode
EF Core .NET Core/.NET 5+ ✅ Aktivno razvijanje

🔄 Database-First vs Code-First

Entity Framework podržava tri pristupa razvoju: Database-First, Code-First, i Model-First. U praksi se najčešće koriste Database-First i Code-First.

1. Database-First Pristup

Database-First znači da već imate bazu podataka i želite generisati C# klase iz nje. EF kreira Model klase i DbContext automatski na osnovu postojeće baze.

💡 Kada Koristiti Database-First?

  • Kada već imate postojeću bazu podataka
  • Kada DBA (Database Administrator) upravlja bazom
  • Kada radite sa legacy sistemima
  • Kada preferirate da baza definiše strukturu

Detaljni Koraci za Database-First (Visual Studio)

Database-First pristup podrazumijeva da EF automatski generiše C# klase na osnovu postojeće baze. Evo kompletne procedure:

🛠️ Visual Studio Wizard - Korak po Korak
KORAK 1: Dodavanje ADO.NET Entity Data Model-a
═══════════════════════════════════════════════
1. Desni klik na projekat (u Solution Explorer)
2. Add → New Item...
3. U prozoru "Add New Item":
   - Odaberite kategoriju: Data
   - Odaberite template: ADO.NET Entity Data Model
   - Name: StateStatisticsModel.edmx
   - Kliknite "Add"

KORAK 2: Izbor Pristupa (Entity Data Model Wizard)
═══════════════════════════════════════════════════
U "Choose Model Contents" dijalogu:
├─○ EF Designer from database ← ODABERITE OVO za Database-First
├─○ Empty EF Designer model
├─○ Empty Code First model
└─○ Code First from database

Kliknite "Next"

KORAK 3: Konekcija na Bazu
═══════════════════════════
U "Choose Your Data Connection" dijalogu:

A) AKO NEMATE POSTOJEĆU KONEKCIJU:
   1. Kliknite "New Connection..."
   2. U "Choose Data Source":
      - Data source: Microsoft SQL Server
      - Data provider: .NET Framework Data Provider for SQL Server
      - Kliknite "Continue"
   3. U "Connection Properties":
      - Server name: (LocalDb)\MSSQLLocalDB    // za LocalDB
      - ili: .\SQLEXPRESS                      // za SQL Express
      - ili: sql-server.vlada.local            // za production
      - Authentication: Windows Authentication  // PREPORUČENO
      - Select database: StateStatisticsDB
      - Kliknite "Test Connection" - treba pokazati "Test connection succeeded."
      - Kliknite "OK"

B) AKO IMATE POSTOJEĆU KONEKCIJU:
   1. Odaberite iz dropdown liste
   2. Provjerite "Save connection settings in Web.config as:"
      - Ime: StateStatisticsDBEntities (EF dodaje "Entities" sufiks)
   
Kliknite "Next"

KORAK 4: Izbor EF Verzije
══════════════════════════
U "Choose Your Version" dijalogu:
- Odaberite: Entity Framework 6.x
Kliknite "Next"

KORAK 5: Izbor Database Objekata
═════════════════════════════════
U "Choose Your Database Objects and Settings" dijalogu:

✅ Tables
   └─ ✅ Stats (schema)
         ├─ ✅ Departments
         ├─ ✅ Employees
         ├─ ✅ Managers
         ├─ ✅ Projects
         └─ ✅ ProjectEmployees

□ Views (opciono - možete odabrati)
□ Stored Procedures and Functions (opciono)

Opcije (VAŽNO):
✅ Pluralize or singularize generated object names
   (Department klasa = Departments tabela, automatski)
✅ Include foreign key columns in the model
   (Uključuje DepartmentID u Employee klasu)
□ Import selected stored procedures and functions into the entity model

Model Namespace: StateStatisticsModel

Kliknite "Finish"

KORAK 6: Generisanje
═════════════════════
Visual Studio će:
1. Kreirati .edmx fajl
2. Generisati sve Model klase
3. Generisati DbContext klasu
4. Dodati connection string u Web.config
5. Dodati EntityFramework NuGet package (ako već nije)

⏳ Proces traje 5-30 sekundi zavisno od veličine baze.

Generisani Fajlovi - Detaljna Analiza

Nakon što wizard završi, Visual Studio generiše nekoliko fajlova. Evo šta svaki radi:

📂 Struktura Generisanih Fajlova

Fajl Svrha Editable?
StateStatisticsModel.edmx XML fajl sa metadata o bazi (tabele, relacije, mapiranja) ⚠️ Rijetko
StateStatisticsModel.edmx.diagram Vizualni dijagram (double-click na .edmx da vidite) ✅ Da (vizualno)
StateStatisticsModel.Context.tt T4 Template za generisanje DbContext klase ❌ Ne
StateStatisticsModel.Context.cs DbContext klasa (auto-generated iz .tt) ⚠️ Izgubićete promjene kod regenerisanja
StateStatisticsModel.tt T4 Template za generisanje Model klasa ❌ Ne
Department.cs, Employee.cs, ... Model klase za svaku tabelu ⚠️ Partial classes - možete dodati kroz partial

⚠️ VAŽNO: Database-First Ograničenja

  • 🚫 NE EDITUJTE auto-generisane klase direktno - promjene će biti izgubljene kod regenerisanja
  • ✅ Koristite partial classes za dodavanje custom logike:
    // CustomDepartment.cs (vaš fajl)
    public partial class Department
    {
        public string DisplayName => $"{DepartmentCode} - {DepartmentName}";
    }
  • ✅ Kada se baza promijeni: desni klik na .edmx → "Update Model from Database"

Primjer Auto-Generisanog DbContext-a

🛠️ StateStatisticsModel.Context.cs (Auto-Generated)
// --------------------------------------------------
// AUTO-GENERATED - NE EDITUJTE DIREKTNO!
// Regeneriše se svaki put kad update-ujete model iz baze
// --------------------------------------------------

namespace JavnaUprava.Models
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    // Napomena: Ime klase dolazi iz "Model Namespace" u wizard-u
    public partial class StateStatisticsDBEntities : DbContext
    {
        // Konstruktor - koristi connection string name iz Web.config
        // "name=StateStatisticsDBEntities" znači traži  u Web.config
        public StateStatisticsDBEntities()
            : base("name=StateStatisticsDBEntities")
        {
        }
    
        // DbContext konfigurisanje konekcije
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // EF automatski poziva ovu metodu pri inicijalizaciji
            // Database-First OBIČNO ne dodaje ništa ovdje jer sve čita iz .edmx fajla
            throw new UnintentionalCodeFirstException();
        }
    
        // DbSet za svaku tabelu koju ste odabrali u wizard-u
        // EF pluralizuje imena: Departments (tabela) → Departments (DbSet)
        public virtual DbSet<Department> Departments { get; set; }
        public virtual DbSet<Employee> Employees { get; set; }
        public virtual DbSet<Manager> Managers { get; set; }
        public virtual DbSet<Project> Projects { get; set; }
        
        // Junction tabela - EF automatski kreira many-to-many relaciju
        public virtual DbSet<ProjectEmployee> ProjectEmployees { get; set; }
    }
}

Primjer Auto-Generisane Model Klase

🛠️ Department.cs (Auto-Generated iz Baze)
// --------------------------------------------------
// AUTO-GENERATED IZ BAZE: StateStatisticsDB.Stats.Departments
// --------------------------------------------------

namespace JavnaUprava.Models
{
    using System;
    using System.Collections.Generic;
    
    // partial omogućava vam da dodajete custom kod u zasebnom fajlu
    public partial class Department
    {
        // Konstruktor - EF kreira praznu kolekciju za navigation properties
        // Ovo sprječava NullReferenceException kada pristupate dept.Employees
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Department()
        {
            this.Employees = new HashSet<Employee>();
            this.Managers = new HashSet<Manager>();
        }
    
        // Primary key - EF detektuje iz baze (IDENTITY column)
        public int DepartmentID { get; set; }
        
        // NOT NULL columns postaju required properties
        public string DepartmentName { get; set; }
        
        // NULLABLE columns postaju nullable properties
        public string DepartmentCode { get; set; }
        
        // decimal(18, 2) u SQL → decimal u C#
        public decimal Budget { get; set; }
        
        // Navigation Properties - EF generiše na osnovu Foreign Key relacija
        // virtual omogućava Lazy Loading (učitavanje po potrebi)
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Employee> Employees { get; set; }
        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Manager> Managers { get; set; }
    }
}

💡 Zašto 'virtual' Navigation Properties?

Keyword virtual omogućava EF-u da kreira proxy klase za:

  • Lazy Loading: Povezani podaci se učitavaju tek kada im pristupite:
    var dept = db.Departments.Find(1);
    // Employees se NE učitavaju odmah
    
    foreach (var emp in dept.Employees)  // ← SADA se učitavaju iz baze
    {
        Console.WriteLine(emp.FirstName);
    }
  • Change Tracking: EF prati promjene za optimističko concurrency kontrolu

Bez virtual, morate koristiti Eager Loading:

var dept = db.Departments.Include(d => d.Employees).FirstOrDefault();

2. Code-First Pristup

Code-First znači da pišete C# klase prvo, a EF kreira bazu podataka na osnovu tih klasa. Ovo je moderniji pristup koji daje više kontrole razvojima.

🔑 Kada Koristiti Code-First?

  • Kada kreirate novu aplikaciju od nule
  • Kada želite potpunu kontrolu nad kodom
  • Kada koristite Migracije za upravljanje promjenama
  • Kada preferirate da kod definiše strukturu baze

💡 Code-First je Preporučen Pristup

Za nove projekte, Code-First je preporučen jer:

  • Omogućava version control nad strukturom baze (kroz Migracije)
  • Lakše za refactoring
  • Bolja integracija sa development workflow-om
  • Podržava TDD (Test-Driven Development)

🏗️ DbContext: Most između Koda i Baze

DbContext je ključna klasa u Entity Framework-u. On predstavlja sesiju sa bazom podataka i omogućava vam da:

Kreiranje DbContext Klase (Code-First)

🛠️ Osnovni DbContext
// Models/JavnaUpravaDbContext.cs
using System.Data.Entity;

namespace JavnaUprava.Models
{
    public class JavnaUpravaDbContext : DbContext
    {
        // Konstruktor - prima connection string ime iz Web.config
        public JavnaUpravaDbContext() 
            : base("StateStatisticsDB")
        {
        }

        // DbSet predstavlja kolekciju entiteta u bazi
        public DbSet<Department> Departments { get; set; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Project> Projects { get; set; }

        // OnModelCreating - za konfiguraciju modela
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Konfiguracija Stats sheme (jer koristimo postojeću bazu)
            modelBuilder.Entity<Department>().ToTable("Departments", "Stats");
            modelBuilder.Entity<Employee>().ToTable("Employees", "Stats");
            modelBuilder.Entity<Project>().ToTable("Projects", "Stats");
            
            base.OnModelCreating(modelBuilder);
        }
    }
}

Connection String u Web.config

🛠️ Connection String Konfiguracija



  
    
    
  

🔑 Connection String Ime

Ime connection string-a u Web.config mora odgovarati imenu koje prosljeđujete u DbContext konstruktoru. U primjeru iznad, koristimo "DefaultConnection".

Model Klase (Entiteti)

Model klase predstavljaju tabele u bazi podataka. Svaka klasa postaje tabela, a svojstva postaju kolone.

🛠️ Primjer Model Klasa
// Models/Department.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace JavnaUprava.Models
{
    [Table("Departments", Schema = "Stats")]
    public class Department
    {
        [Key]
        public int DepartmentID { get; set; }
        
        [Required]
        [StringLength(100)]
        public string DepartmentName { get; set; }
        
        [StringLength(20)]
        public string DepartmentCode { get; set; }
        
        public decimal Budget { get; set; }
        
        // Navigation property - kolekcija zaposlenika
        public virtual ICollection<Employee> Employees { get; set; }
    }
}

// Models/Employee.cs
namespace JavnaUprava.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;

    [Table("Employees", Schema = "Stats")]
    public class Employee
    {
        [Key]
        public int EmployeeID { get; set; }
        
        [Required]
        [StringLength(50)]
        public string FirstName { get; set; }
        
        [Required]
        [StringLength(50)]
        public string LastName { get; set; }
        
        [Required]
        [StringLength(13)]
        public string JMBG { get; set; } // Specifično za državnu upravu
        
        // Foreign key
        public int DepartmentID { get; set; }
        
        // Navigation property
        public virtual Department Department { get; set; }
    }
}

💡 Navigation Properties

Navigation properties (npr. Department u Employee klasi) omogućavaju vam da pristupite povezanim entitetima bez eksplicitnih JOIN-ova. EF automatski učitava povezane podatke kada ih koristite.

📦 Instalacija Entity Framework 6

Korištenje NuGet Package Manager-a

🛠️ Instalacija preko NuGet-a
1. Desni klik na projekat → Manage NuGet Packages
2. Pretražite "EntityFramework"
3. Odaberite "EntityFramework" (verzija 6.4.4 ili novija)
4. Kliknite "Install"

Alternativno, koristite Package Manager Console:
PM> Install-Package EntityFramework

Provjera Instalacije

Nakon instalacije, provjerite da li je Entity Framework pravilno instaliran:

🔗 Detaljan Prikaz Relacija u Code-First Pristupu

Prilikom izgradnje sistema javne uprave, rijetko ćete imati entitete koji su izolovani. Najvažniji dio arhitekture softvera i Code-First dizajna je ispravno definisanje relacija (Relationships).

1. Jedan na Više (One-to-Many)

Ovo je najčešća relacija. Primjer: Jedno Ministarstvo (Department) ima više Zaposlenika (Employee). Strani ključ (Foreign Key) se nalazi u zavisnoj tabeli.

🛠️ 1:N Relacija
public class Department 
{
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    
    // Parent klasa sadrži ICollection (listu) dječijih entiteta.
    // Oznaka VIRTUAL omogućava funkciju "Lazy Loading" (učitavanje na zahtjev).
    public virtual ICollection<Employee> Employees { get; set; }
}

public class Employee 
{
    public int EmployeeID { get; set; }
    public string FullName { get; set; }
    
    // Child klasa sadrži Foreign Key (opciono ali snažno preporučeno zbog obrade u Views-ima)
    public int DepartmentID { get; set; }
    
    // Obavezna referenca na instancu parenta
    public virtual Department Department { get; set; }
}

2. Više na Više (Many-to-Many)

Primjer: Zaposlenik (Employee) može raditi na više radnih timova ili Projekata (Project), i obrnuto. EF automatski kreira takozvanu Junction (spojnu) tabelu u pozadini bez potrebe kreiranja modela, ako su u obje klase definisane ICollection.

🛠️ N:M Relacija
public class Employee 
{
    public int EmployeeID { get; set; }
    // Radnik je angažovan na više projekata istovremeno
    public virtual ICollection<Project> Projects { get; set; }
}

public class Project 
{
    public int ProjectID { get; set; }
    public string ProjectName { get; set; }
    // Na jednom projektu radi više radnika
    public virtual ICollection<Employee> Employees { get; set; }
}

3. Jedan na Jedan (One-to-One)

Koristi se za dijeljenje velikih struktura u manje, obično radi sigurnosnog maskiranja podataka ili optimizacije. Primjer: Zaposlenik i njegov ZdravstveniKarton (osjetljivi lični podatak). Da bi EF mapirao 1:1, Primary Key child tabele mora istovremeno biti i Foreign Key koji pokazuje na Parent tabelu (oznaka Key i ForeignKey zajedno).

🛠️ 1:1 Relacija (Putem Data Annotations)
public class Employee 
{
    public int EmployeeID { get; set; }
    
    // Reference na child komponentu
    public virtual HealthRecord HealthRecord { get; set; } 
}

public class HealthRecord 
{
    // Key istovremeno služi i kao PK (u svojoj tabeli) i kao FK prema Employee tabeli
    [Key, ForeignKey("Employee")]
    public int EmployeeID { get; set; }
    public string BloodType { get; set; }
    
    // Reference na parenta
    public virtual Employee Employee { get; set; }
}

🎯 Praktična Vježba: Kreiranje Prvog DbContext-a

Zadatak: Kreirajte Model za Registar Zaposlenih

Kreirajte Code-First modele za evidenciju zaposlenih u javnoj upravi.

Zadatak 1: Kreirajte Department klasu (ID, Name, Code, Budget).

Zadatak 2: Kreirajte Employee klasu (ID, FirstName, LastName, JMBG, DepartmentID).

Zadatak 3: Kreirajte JavnaUpravaDbContext i konfigurišite mapiranje na Stats shemu.

💡 Rješenje: DbContext
public class JavnaUpravaDbContext : DbContext
{
    public JavnaUpravaDbContext() : base("StateStatisticsDB") { }

    public DbSet<Department> Departments { get; set; }
    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Department>().ToTable("Departments", "Stats");
        modelBuilder.Entity<Employee>().ToTable("Employees", "Stats");
    }
}

✅ Sljedeći Korak

U Lekciji 2.2 ćemo naučiti kako kreirati bazu podataka iz ovih Model klasa koristeći EF Migracije.

✅ Zaključak

U ovoj lekciji ste naučili:

📚 Sljedeća Lekcija

U Lekciji 2.2 ćemo naučiti kako koristiti EF Migracije za kreiranje i upravljanje promjenama u bazi podataka. Naučićete komande kao što su Enable-Migrations, Add-Migration, i Update-Database.