📖 Uvod u CRUD Operacije
CRUD (Create, Read, Update, Delete) su osnovne operacije koje svaka aplikacija mora izvršavati nad podacima. Entity Framework omogućava vam da izvršavate ove operacije koristeći LINQ to Entities - LINQ sintaksu koja se prevodi u SQL upite.
🔑 LINQ to Entities
LINQ (Language Integrated Query) omogućava vam da pišete upite koristeći C# sintaksu. Entity Framework prevodi LINQ upite u SQL i izvršava ih na bazi podataka. Ovo daje type safety, IntelliSense podršku, i kompajlersku provjeru grešaka.
📖 READ Operacije: Čitanje Podataka
1. Dohvatanje Svih Entiteta
// Controllers/EmployeesController.cs
using System.Linq;
using System.Web.Mvc;
using JavnaUprava.Models;
public class EmployeesController : Controller
{
private JavnaUpravaDbContext db = new JavnaUpravaDbContext();
public ActionResult Index()
{
// Dohvatanje svih zaposlenika
var employees = db.Employees.ToList();
return View(employees);
}
}
2. Dohvatanje Jednog Entiteta po ID-u
public ActionResult Details(int id)
{
// Find() metoda traži po primarnom ključu
var employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
// Alternativno, koristeći LINQ:
public ActionResult Details(int id)
{
var employee = db.Employees.FirstOrDefault(e => e.Id == id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
3. Filtriranje i Pretraživanje
// Filtriranje zaposlenika po ID-u ministarstva
public ActionResult ByDepartment(int departmentId)
{
var employees = db.Employees
.Where(e => e.DepartmentID == departmentId)
.ToList();
return View(employees);
}
// Pretraživanje po JMBG-u ili imenu
public ActionResult Search(string searchTerm)
{
var employees = db.Employees
.Where(e => e.JMBG.Contains(searchTerm) ||
e.FirstName.Contains(searchTerm) ||
e.LastName.Contains(searchTerm))
.ToList();
return View(employees);
}
// Napredna pretraga: Zaposlenici u ministarstvu sa prezimenom na 'A'
public ActionResult FilterInDepartment(int deptId)
{
var employees = db.Employees
.Where(e => e.DepartmentID == deptId &&
e.LastName.StartsWith("A"))
.OrderBy(e => e.LastName)
.ToList();
return View(employees);
}
4. Sortiranje
// Sortiranje po prezimenu (rastuće)
var employees = db.Employees
.OrderBy(e => e.LastName)
.ToList();
// Sortiranje po datumu zaposlenja (opadajuće)
var employees = db.Employees
.OrderByDescending(e => e.HireDate)
.ToList();
// Višestruko sortiranje
var employees = db.Employees
.OrderBy(e => e.DepartmentId)
.ThenBy(e => e.LastName)
.ToList();
➕ CREATE Operacije: Dodavanje Podataka
Dodavanje Novog Entiteta
// GET: Employees/Create
[HttpGet]
public ActionResult Create()
{
// Priprema liste ministarstava za dropdown
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "DepartmentName");
return View();
}
// POST: Employees/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Employee employee)
{
if (ModelState.IsValid)
{
// Validacija JMBG-a (primjer poslovne logike)
if (employee.JMBG.Length != 13) {
ModelState.AddModelError("JMBG", "JMBG mora imati 13 cifara");
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "DepartmentName", employee.DepartmentID);
return View(employee);
}
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "DepartmentName", employee.DepartmentID);
return View(employee);
}
🔑 SaveChanges() Metoda i Unit of Work Šablon
SaveChanges() je ključna metoda koja stvarno šalje promjene u bazu
podataka. Sve promjene (Add, Update, Delete) se čuvaju u memoriji dok ne pozovete
SaveChanges().
Ovo ponašanje implementira takozvani Unit of Work obrazac. Šta to znači u praksi za sisteme državne uprave?
- Transakciona obrada: EF obavija sve operacije unutar jedne SQL transakcije
prilkom poziva
SaveChanges(). - Sve ili ništa: Ako pokušavate dodati 50 novih zaposlenika, a kod 49. dođe do greške validacije baze (npr. isti JMBG), Cijela transakcija se poništava i nijedan radnik se ne upisuje. Sistem nikad ne ostaje u nedosljednom stanju.
✏️ UPDATE Operacije: Ažuriranje Podataka
Ažuriranje Postojećeg Entiteta
// GET: Employees/Edit/5
[HttpGet]
public ActionResult Edit(int id)
{
var employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "DepartmentName", employee.DepartmentID);
return View(employee);
}
// POST: Employees/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
db.Entry(employee).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentID = new SelectList(db.Departments, "DepartmentID", "DepartmentName", employee.DepartmentID);
return View(employee);
}
💡 EntityState
Entity Framework prati stanje svakog entiteta:
Added- novi entitetModified- entitet je promijenjenDeleted- entitet je obrisanUnchanged- entitet nije promijenjen
⏱️ Rješavanje Konflikata u Višekorisničkom Okruženju (Concurrency)
U sistemima javne uprave česta je situacija da dva referenta istovremeno otvore isti zapis (npr. profil zaposlenika). Ako oboje naprave izmjene i kliknu "Spasi", čije se izmjene primjenjuju?
Entity Framework podrazumijevano koristi princip Pobjeđuje Onaj Koji Posljednji Sačuva (Last In Wins). Međutim, to dovodi do tihog gubitka podataka prvog referenta. Da bismo ovo spriječili, implementiramo Optimistično Zaključavanje (Optimistic Concurrency).
Implementacija uz pomoć atributa [Timestamp]
// 1. Dodavanje RowVersion kolone u Model
public class Employee
{
public int EmployeeID { get; set; }
public string FirstName { get; set; }
// Ovo polje MS SQL Server automatski mijenja pri svakom Update-u objekta (bilo koje kolone)
[Timestamp]
public byte[] RowVersion { get; set; }
}
// 2. Tretiranje greške u Controlleru
[HttpPost]
public ActionResult Edit(Employee employee)
{
try
{
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges(); // Pokušaj snimanja
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException ex)
{
// Okida se ako je neko drugi u međuvremenu promijenio zapis u bazi!
ModelState.AddModelError(string.Empty, "Zapis je izmijenjen od strane drugog korisnika nakon što ste ga učitali. Molimo osvježite stranicu, pregledajte tuđe izmjene i pokušajte ponovo.");
return View(employee);
}
}
💡 Kako funkcionira [Timestamp]
EF će na osnovu [Timestamp] prilikom generisanja SQL Update skripte unutar
WHERE klauzule uvijek provjeriti da li je RowVersion u bazi jednak onom
starom koji ste vi povukli na formu. Ako nije jednak, znači da je kolega obavio UPDATE
u sekundi kada ste vi ukucavali vaše tekstove, i baza automatski odbija vašu operaciju i sprečava
gubitak koda.
🗑️ DELETE Operacije: Brisanje Podataka
// GET: Employee/Delete/5
[HttpGet]
public ActionResult Delete(int id)
{
var employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
// POST: Employee/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
var employee = db.Employees.Find(id);
if (employee != null)
{
// Brisanje entiteta
db.Employees.Remove(employee);
// Spremanje promjena
db.SaveChanges();
}
return RedirectToAction("Index");
}
⚠️ Cascade Delete
Ako imate foreign key veze sa cascadeDelete: true, brisanje roditeljskog entiteta
će automatski obrisati i povezane entitete. Budite oprezni!
🔄 Strategije Učitavanja: Eager, Lazy, Explicit
Entity Framework podržava tri strategije za učitavanje povezanih podataka (navigation properties):
1. Eager Loading - Učitavanje Odmah
Eager Loading učitava povezane entitete odmah u istom upitu koristeći
Include() metodu.
// Učitavanje zaposlenika sa njihovim ministarstvima
var employees = db.Employees
.Include(e => e.Department)
.ToList();
// Filtriranje pa učitavanje
var mfEmployees = db.Employees
.Where(e => e.Department.DepartmentCode == "MF")
.Include(e => e.Department)
.ToList();
💡 Kada Koristiti Eager Loading?
- Kada znaте da će vam trebati povezani podaci
- Kada želite smanjiti broj upita ka bazi
- Kada radite sa malim brojem povezanih entiteta
2. Lazy Loading - Učitavanje na Zahtjev
Lazy Loading automatski učitava povezane entitete kada im pristupite. Ovo je default ponašanje u EF 6.
// Učitavanje zaposlenika (bez Department-a)
var employee = db.Employees.Find(1);
// Department se učitava automatski kada mu pristupite
var departmentName = employee.Department.Name; // EF izvršava dodatni upit ovdje
// Ovo može rezultovati N+1 problemom!
foreach (var emp in db.Employees)
{
// Svaki pristup Department-u izvršava novi upit
Console.WriteLine(emp.Department.Name); // N upita!
}
⚠️ N+1 Problem
N+1 problem se dešava kada imate 1 upit za listu entiteta, a zatim N dodatnih upita za povezane entitete. Ovo može značajno usporiti aplikaciju!
Rješenje: Koristite Include() za Eager Loading.
3. Explicit Loading - Eksplicitno Učitavanje
Explicit Loading omogućava vam da eksplicitno učitavate povezane entitete kada vam
trebaju, koristeći Load() metodu.
var employee = db.Employees.Find(1);
// Eksplicitno učitavanje Department-a
db.Entry(employee)
.Reference(e => e.Department)
.Load();
// Sada možete pristupiti Department-u bez dodatnog upita
var departmentName = employee.Department.Name;
// Eksplicitno učitavanje kolekcije
db.Entry(employee)
.Collection(e => e.Projects)
.Load();
Onemogućavanje Lazy Loading
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
// Onemogućavanje lazy loading-a
this.Configuration.LazyLoadingEnabled = false;
}
}
🔍 Napredni LINQ Upiti
1. Agregacije
// Broj zaposlenih u MF
var count = db.Employees.Count(e => e.Department.DepartmentCode == "MF");
// Ukupan budžet svih ministarstava
var totalBudget = db.Departments.Sum(d => d.Budget);
// Najveći budžet
var maxBudget = db.Departments.Max(d => d.Budget);
// Grupisanje zaposlenih po ministarstvu
var employeesByDept = db.Employees
.GroupBy(e => e.DepartmentID)
.Select(g => new {
DeptId = g.Key,
Count = g.Count()
})
.ToList();
2. GroupBy
// Grupisanje zaposlenika po odjelu
var employeesByDept = db.Employees
.GroupBy(e => e.DepartmentId)
.Select(g => new {
DepartmentId = g.Key,
Employees = g.ToList(),
Count = g.Count()
})
.ToList();
3. Join Operacije
// Eksplicitni JOIN (rijetko potreban - koristite navigation properties)
var result = from emp in db.Employees
join dept in db.Departments on emp.DepartmentId equals dept.Id
select new {
EmployeeName = emp.FirstName + " " + emp.LastName,
DepartmentName = dept.Name
};
// Bolje: koristite navigation properties
var result = db.Employees
.Select(e => new {
EmployeeName = e.FirstName + " " + e.LastName,
DepartmentName = e.Department.Name
})
.ToList();
🧹 Disposing DbContext
Važno je pravilno zatvoriti DbContext kako biste oslobodili resurse.
Najbolji način je korištenje using statement-a.
// DOBRO: Korištenje using statement-a
public ActionResult Index()
{
using (var db = new ApplicationDbContext())
{
var employees = db.Employees.ToList();
return View(employees);
} // DbContext se automatski zatvara ovdje
}
// ILI: Implementacija IDisposable u Controller-u
public class EmployeeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
🎯 Praktična Vježba: Kompletan CRUD
Zadatak: Implementirajte CRUD za Ministarstva
Kreirajte kompletan CRUD (Create, Read, Update, Delete) za entitet Department.
Zadatak 1: Kreirajte DepartmentsController sa Index
akcijom koja prikazuje listu ministarstava sa brojem zaposlenih (koristite GroupBy ili Include).
Zadatak 2: Implementirajte Create akciju za dodavanje novog
ministarstva.
Zadatak 3: Implementirajte Delete akciju koja sprečava brisanje
ministarstva ako ima zaposlenih (Cascade Delete protection).
public ActionResult Index()
{
// Učitavamo ministarstva i brojimo zaposlene
var depts = db.Departments
.Include(d => d.Employees)
.OrderByDescending(d => d.Budget)
.ToList();
return View(depts);
}
✅ Zaključak
U ovoj lekciji ste naučili:
- ✅ Kako izvršavati READ operacije sa LINQ to Entities
- ✅ Kako kreirati, ažurirati i brisati entitete (CREATE, UPDATE, DELETE)
- ✅ Tri strategije učitavanja: Eager, Lazy, Explicit
- ✅ Kako izbjegavati N+1 problem
- ✅ Napredne LINQ upite (agregacije, grupisanje, join)
- ✅ Pravilno upravljanje DbContext resursima
📚 Sljedeći Modul
U Modulu 3 ćemo naučiti kako koristiti Controller-e i Action metode za obradu korisničkih zahtjeva. Naučićete Model Binding, ViewData, ViewBag, TempData, i Action Filtere.