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.
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.
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ışırIEnumerable<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 filtrelemeList<T>
- Sorgular memory üzerinde yapılır
- IEnumerable'ın somut implementasyonu
- .Add,- .Removegibi 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önerSingleOrDefault
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 InvalidOperationExceptionSı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.
One-to-Many (Bire Çok)
Bir tablodaki kayıt, diğer tabloda birden çok karşılığa sahip olabilir.
Many-to-Many (Çoğa Çok)
Her iki tablodaki kayıtlar, karşı tabloda birden çok karşılığa sahip olabilir.
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.SqlServerBelirli Tabloları İçe Aktarma
dotnet ef dbcontext scaffold "Server=localhost;Database=MyDb;User Id=sa;Password=123;" Microsoft.EntityFrameworkCore.SqlServer -o Models -t Products -t CategoriesDesteklenen Veritabanları
NuGet Paketleri
EF Core kullanmak için gerekli NuGet paketlerini aşağıdaki görsel üzerinden inceleyebilirsiniz:
 
                SQL Server
Microsoft.EntityFrameworkCore.SqlServer
                            Azure Cosmos DB
Microsoft.EntityFrameworkCore.Cosmos
                            SQLite
Microsoft.EntityFrameworkCore.Sqlite
                            In-Memory
Microsoft.EntityFrameworkCore.InMemory
                            PostgreSQL
Npgsql.EntityFrameworkCore.PostgreSQL
                            MySQL
Pomelo.EntityFrameworkCore.MySql
                            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