Bu kapsamlı rehberde, ADO.NET'in karmaşık SQL sorguları ve manuel veritabanı yönetiminden Entity Framework Core'un modern ORM yaklaşımına geçiş sürecini detaylı olarak inceleyeceğiz. Pratik örnekler ve restoran analojisi ile bu teknolojiler arasındaki farkları keşfedin.

Restoran Analojisi: ADO.NET vs EF Core

Veri erişimi konusunu anlamak için bir restoran benzetmesi kullanalım. Elinizdeki proje bir restoranda sipariş alma sistemi olsun.

ADO.NET Yaklaşımı

Kendin Mutfağa Gitmek

Her seferinde mutfağa kendin gidip siparişini elle veriyorsun. Tabakları taşırken dikkat etmen, yemeği masaya getirmen gerekir.

Tam kontrol Fazla kod Karmaşık

EF Core Yaklaşımı

Garsona Sipariş Vermek

Sadece "Bana tüm menüyü getir" diyorsun, gerisini garson (EF Core) halleder. Mutfağa gider, yemeği hazırlar, getirir.

Kolay kullanım Az kod Bakım kolay

Pratik Karşılaştırma: CRUD İşlemleri

Veri Okuma (Read)

ADO.NET ile

private SqlConnection connection = 
    new SqlConnection("Server=localhost,1433;Database=Database1;User Id=SA; Password=Alperen123@;Encrypt=False;");

[HttpGet("[action]")]
public IActionResult GetList()
{
    SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM SimpleTable", connection);
    DataTable dataTable = new DataTable();
    dataTable.Clear();
    adapter.Fill(dataTable);
    
    var result = Newtonsoft.Json.JsonConvert.SerializeObject(dataTable);
    return Ok(result);
}

EF Core ile

public class EFController : Controller
{
    private DenemeContext context = new();
    
    [HttpGet("[action]")]
    public IActionResult GetList()
    {
        var result = context.SimpleTable.ToList();
        return Ok(result);
    }
}

Veri Ekleme (Create)

ADO.NET ile

[HttpPost("[action]")]
public IActionResult Add(SimpleTable simpleTable)
{
    SqlCommand command = new SqlCommand(
        "insert into SimpleTable(Name) values(@parametre1)", connection);
    command.Parameters.AddWithValue("@parametre1", simpleTable.Name);
    connection.Open();
    command.ExecuteNonQuery(); 
    connection.Close();
    return Ok("Kayıt işlemi başarıyla tamamlandı");
}

EF Core ile

[HttpPost("[action]")]
public IActionResult Add(SimpleTable simpleTable)
{
    context.SimpleTable.Add(simpleTable); // Memory'e yazdık
    context.SaveChanges(); // DB'ye yazdık
    
    return Ok("Your post succeed");
}

Veri Güncelleme (Update)

ADO.NET ile

[HttpPost("[action]")]
public IActionResult Update(SimpleTable simpleTable)
{
    SqlCommand command = new SqlCommand(
        "update SimpleTable set Name=@parametreName where Id=@parametreId", connection);
    command.Parameters.AddWithValue("@parametreName", simpleTable.Name);
    command.Parameters.AddWithValue("@parametreId", simpleTable.Id);
    connection.Open();
    command.ExecuteNonQuery();
    connection.Close();
    return Ok("your changes has been updated");
}

EF Core ile

[HttpPost("[action]")]
public IActionResult Update(SimpleTable simpleTable)
{
    context.SimpleTable.Update(simpleTable);
    context.SaveChanges();
    return Ok();
}

Veri Silme (Delete)

EF Core ile

[HttpDelete("[action]")]
public IActionResult Delete(SimpleTable simpleTable)
{
    context.SimpleTable.Remove(simpleTable);
    context.SaveChanges();
    return Ok();
}

// Çoklu silme işlemi
[HttpDelete("[action]")]
public IActionResult RemoveRange(SimpleTable[] simpleTable)
{
    context.SimpleTable.RemoveRange(simpleTable);
    context.SaveChanges();
    return Ok();
}

Önemli Not

Delete işleminde sadece ID doğru girildiğinde yeterli olur. EF Core arka planda DELETE FROM SimpleTables WHERE ID=parametre şeklinde çalışır.

Gelişmiş Sorgulama Teknikleri

IQueryable vs IEnumerable vs List

IQueryable<T>

  • AsQueryable() ile kullanılır
  • Sorgular SQL üzerinde yapılır
  • Yüksek performanslıdır
  • ToList() ile sonuçlandırılır
IQueryable<SimpleTable> result = context.SimpleTable.AsQueryable();
result = result.Where(s => s.Id == 1 && s.Name.Contains("Alperen"));
var finalResult = result.ToList(); // Bu noktada SQL çalışır

IEnumerable<T>

  • Sorgular memory üzerinde yapılır
  • Interface olduğundan veriyi saklayamaz
  • .ToList() ile çağrılır
  • Listede sadece ileri gidebilir
IEnumerable<SimpleTable> result1 = context.SimpleTable.ToList();
result1 = result1.Where(p => p.Id == 1); // Memory'de filtreleme

List<T>

  • Sorgular memory üzerinde yapılır
  • IEnumerable'ın somut implementasyonu
  • .Add, .Remove gibi metodlar
  • Index mantığı ile navigasyon
List<SimpleTable> result2 = context.SimpleTable.ToList();
result2.Where(p => p.Id == 1);
result2.Add(new SimpleTable { Name = "Yeni" });

FirstOrDefault vs SingleOrDefault

FirstOrDefault

Koleksiyondaki ilk elemanı getirir. Hiç eleman yoksa default döner.

var result = context.SimpleTable.FirstOrDefault(p => p.Id == 2);
// İlk ID=2 olanı getirir, yoksa null döner

SingleOrDefault

Koleksiyondaki tam 1 tane eleman varsa onu getirir. Birden fazla bulursa hata fırlatır.

var result = context.SimpleTable.SingleOrDefault(p => p.Id == 2);
// ID=2 olanı getirir, birden fazla varsa InvalidOperationException

Sıralama ve Limitleme

İlk 5 Kayıt

var result = context.SimpleTable
    .Where(p => p.Name.Contains("a"))
    .Take(5);

Sıralı İlk 5 Kayıt

var result = context.SimpleTable
    .Where(p => p.Name.Contains("a"))
    .OrderBy(p => p.Id)
    .Take(5);

Son 5 Kayıt

var result = context.SimpleTable
    .Where(p => p.Name.Contains("a"))
    .OrderByDescending(p => p.Id)
    .Take(5);

Maksimum Değer

var maxPrice = context.Products.Max(p => p.Price);
var minPrice = context.Products.Min(p => p.Price);

Data Transfer Object (DTO) ve Join İşlemleri

DTO'lar SQL'deki view'lere karşılık gelir. Veritabanında olmayan ama istediğimiz şekilde veri döndürebilen yapılardır.

DTO Tanımı

public class ProductDto : Product
{
    public string CategoryName { get; set; } // Veritabanında yok, join ile gelecek
}

LINQ Join Sorgusu

var result = from product in context.Products
             join category in context.Categories 
             on product.CategoryId equals category.Id
             select new ProductDto
             {
                 Id = product.Id,
                 CategoryId = product.CategoryId,
                 CategoryName = category.Name,
                 Name = product.Name,
                 Price = product.Price
             };

Code First Yaklaşımı

Code First yaklaşımında veritabanını koddan üretirsiniz. C# sınıflarınızı yazarsınız, EF Core bu sınıflara bakarak veritabanı şemasını oluşturur.

1. Entity Sınıfı Oluşturma

Student Entity

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

2. DbContext Oluşturma

DbContext Konfigürasyonu

public class DenemeContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=localhost;Database=SchoolDb;Trusted_Connection=True;");
    }
    
    public DbSet<Student> Students { get; set; }
}

3. Migration ve Update

# Migration oluştur
add-migration InitialCreate

# Veritabanını güncelle
update-database

Data Annotations vs Fluent API

Data Annotations

Entity sınıfındaki property'lere attribute ekleyerek konfigürasyon yapma:

Data Annotations Örneği

public class Product
{
    [Key] // Primary key
    public int Id { get; set; }

    [Required] // NOT NULL
    [MaxLength(100)] // Maksimum 100 karakter
    public string Name { get; set; }

    [Column(TypeName = "decimal(18,2)")] // SQL tip belirtme
    public decimal Price { get; set; }

Fluent API

OnModelCreating metodunda konfigürasyon yapma:

Fluent API Örneği

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasKey(p => p.Id); // Primary key
        
    modelBuilder.Entity<Product>()
        .Property(p => p.Name)
        .IsRequired() // NOT NULL
        .HasMaxLength(100); // Maksimum 100 karakter
        
    modelBuilder.Entity<Product>()
        .Property(p => p.Price)
        .HasColumnType("decimal(18,2)"); // SQL tip belirtme
}

IEntityTypeConfiguration Kullanımı

Projede entity sayısı arttıkça OnModelCreating şişer. Bu durumda her entity için ayrı configuration sınıfları kullanabilirsiniz:

Configuration Sınıfı

public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.ToTable("Products");
        builder.HasKey(p => p.Id);
        
        builder.Property(p => p.Name)
               .IsRequired()
               .HasMaxLength(100);
               
        builder.Property(p => p.Price)
               .HasColumnType("decimal(18,2)");
               
        builder.HasOne(p => p.Category)
               .WithMany(c => c.Products)
               .HasForeignKey(p => p.CategoryId);
    }
}

DbContext'te Kullanım

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Tek tek uygulama
    modelBuilder.ApplyConfiguration(new ProductConfiguration());
    
    // Tüm configuration'ları otomatik bulma
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}

Tablolar Arası İlişkiler

One-to-One (Bire Bir)

Bir tablodaki kayıt, diğer tabloda sadece bir karşılığa sahiptir.

Kullanıcı ↔ KullanıcıProfili

One-to-Many (Bire Çok)

Bir tablodaki kayıt, diğer tabloda birden çok karşılığa sahip olabilir.

Kategori → Ürünler

Many-to-Many (Çoğa Çok)

Her iki tablodaki kayıtlar, karşı tabloda birden çok karşılığa sahip olabilir.

Öğrenciler ↔ Dersler

One-to-Many İlişki Örneği

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; } // Foreign key
    public Category Category { get; set; } // Navigation property
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // Navigation Property - 1 kategori, çok ürün
    public ICollection<Product> Products { get; set; }
}

Connection String Yönetimi

1. OnConfiguring (Hızlı Yöntem)

Hızlı ve kolay, ancak connection string kodda durur:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer("Server=localhost;Database=MyDb;Trusted_Connection=True;");
}

2. appsettings.json + DI (Güvenli Yöntem)

Connection string'i ayrı dosyada tutma:

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=MyAppDb;Trusted_Connection=True;"
  }
}

DbContext

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    
    public DbSet<Product> Products => Set<Product>();
}

Program.cs

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});

Scaffold ile Database First

Mevcut bir veritabanından C# model sınıflarını otomatik oluşturma (Reverse Engineering):

Tüm Tabloları İçe Aktarma

dotnet ef dbcontext scaffold "Server=localhost;Database=MyDb;User Id=sa;Password=MyPassword123;" Microsoft.EntityFrameworkCore.SqlServer

Belirli Tabloları İçe Aktarma

dotnet ef dbcontext scaffold "Server=localhost;Database=MyDb;User Id=sa;Password=123;" Microsoft.EntityFrameworkCore.SqlServer -o Models -t Products -t Categories

Desteklenen Veritabanları

NuGet Paketleri

EF Core kullanmak için gerekli NuGet paketlerini aşağıdaki görsel üzerinden inceleyebilirsiniz:

EF Core NuGet Paketleri

SQL Server

Microsoft.EntityFrameworkCore.SqlServer
.UseSqlServer(connectionString)

Azure Cosmos DB

Microsoft.EntityFrameworkCore.Cosmos
.UseCosmos(connectionString, databaseName)

SQLite

Microsoft.EntityFrameworkCore.Sqlite
.UseSqlite(connectionString)

In-Memory

Microsoft.EntityFrameworkCore.InMemory
.UseInMemoryDatabase(databaseName)

PostgreSQL

Npgsql.EntityFrameworkCore.PostgreSQL
.UseNpgsql(connectionString)

MySQL

Pomelo.EntityFrameworkCore.MySql
.UseMySql(connectionString)

En İyi Uygulamalar ve Sonuç

Performans

  • IQueryable kullanarak lazy evaluation yapın
  • AsNoTracking() ile read-only sorgular
  • Projection ile sadece gerekli alanları alın

Güvenlik

  • Connection string'leri appsettings'te tutun
  • Parameterized queries (EF Core otomatik yapar)
  • Validation attribute'ları kullanın

Kod Kalitesi

  • IEntityTypeConfiguration kullanın
  • Repository pattern uygulayın
  • DTO'larla data shaping yapın

Veritabanı

  • Migration'ları version control'e ekleyin
  • Seed data için HasData kullanın
  • İndexleri Fluent API ile tanımlayın

Entity Framework Core, modern .NET uygulamalarında veri erişimini kolaylaştıran güçlü bir ORM'dir. ADO.NET'in manuel yaklaşımından EF Core'un otomatik yaklaşımına geçiş, geliştirme sürecini hızlandırır ve kod kalitesini artırır.

Bu rehberde gördüğünüz gibi, EF Core ile:

  • Daha az kod yazarak daha fazla iş yapabilirsiniz
  • SQL injection gibi güvenlik sorunlarından korunursunuz
  • Code First yaklaşımıyla veritabanını koddan yönetebilirsiniz
  • Farklı veritabanı sistemleri arasında kolayca geçiş yapabilirsiniz

Sonraki Adımlar:

  • EF Core ile gerçek projeler geliştirerek pratik yapın
  • Advanced LINQ operatörlerini öğrenin
  • Performance tuning teknikleriyle sorgu optimizasyonu yapın
  • Unit of Work pattern'ı ile repository implementasyonu geliştirin