İçeriğe geç

Çoklu Eşleme (Multi-Mapping): Veritabanında ‘Bire Çok’ İlişkileri Nasıl Yönetilir?

Merhaba sevgili teknoloji dostları! Nasılsınız bu aralar? Ben yine kodların başında, kafamda binbir türlü proje ile koşturup duruyorum. Bu aralar özellikle veritabanı ilişkileriyle uğraşırken aklıma takılan bir konu var: Çoklu eşleme, yani İngilizce tabiriyle ‘Multi-Mapping’. Hani bazen bir veritabanı tablosundaki bir kaydın, başka bir tablodaki birden fazla kayda bağlanması gerekir ya, işte tam da bu noktada işler biraz karışabiliyor. Ne güzel değil mi? Basit gibi görünen ama aslında derinlikli bir konu.

Şimdi düşününce, bu konuyu ilk ne zaman kafama takmıştım hatırlamıyorum ama sanırım o zamanlar bir e-ticaret sitesi üzerinde çalışıyorduk. Bir ürünün birden fazla kategoride yer alması gerekiyordu. Yani bir ‘Laptop’ hem ‘Bilgisayarlar’ kategorisinde olmalıydı, hem de belki ‘Elektronik’ kategorisinde. Ama aynı zamanda ‘Bilgisayarlar’ kategorisinde de başka bir sürü ürün olabilirdi. İşte o zaman anladım ki, bu ‘bire çok’ ilişkisini kurarken ortada başka bir ‘bağlayıcı’ tabloya ihtiyaç duyuluyor. Hani böyle bir köprü gibi düşünün, iki tarafı da birbirine bağlıyor ama kendi başına da bir anlamı var.

Bu bağlayıcı tabloya ‘ara tablo’ da diyebiliriz, ‘ilişki tablosu’ da. Adı ne olursa olsun, işlevi aynı: Bir tarafta ana tablo, diğer tarafta ilişkilendirilecek tablo ve ortada da bu ikisini birbirine bağlayan, her iki taraftan da birer ID taşıyan bir yapı. Bu sayede bir ürün, istediği kadar kategoriye bağlanabiliyor ve bir kategori de istediği kadar ürüne sahip olabiliyor. Harika değil mi? Yani artık ‘bir ürün tek kategoriye bağlıdır’ gibi kısıtlamalarla uğraşmıyoruz.

Peki, bu işi nasıl yapıyoruz pratikte? Çoğu zaman ORM (Object-Relational Mapping) araçları bu işi bizim için kolaylaştırıyor. Mesela C# dünyasında Entity Framework Core kullanıyorsanız, bu çoklu eşlemeyi tanımlamak oldukça basit. İki ana varlığınız (örneğin `Product` ve `Category`) ve aralarında bir ‘çoktan çoğa’ ilişki olduğunu belirtiyorsunuz. Entity Framework Core, sizin için otomatik olarak bu ara tabloyu oluşturuyor ve ilişkilendirmeleri yönetiyor. Bu arada, Dapper gibi daha düşük seviyeli ORM’ler kullanıyorsanız, bu ilişkiyi manuel olarak yönetmeniz gerekebiliyor. Ama korkmayın, onun da kendine göre avantajları var, özellikle performans konusunda.

Şimdi gelelim işin kod tarafına. Diyelim ki elimizde bir `Product` (Ürün) tablomuz ve bir `Category` (Kategori) tablomuz var. Bir ürün birden fazla kategoride olabilir, bir kategori de birden fazla ürüne sahip olabilir. Bu ‘çoktan çoğa’ ilişkiyi kurmak için araya bir `ProductCategory` (ÜrünKategori) tablosu sokacağız. Bu tabloda `ProductId` ve `CategoryId` adında iki sütun olacak. Böylece her bir satır, belirli bir ürünü belirli bir kategoriye bağlayacak.

Örnek olarak C# ve Dapper ile bunu nasıl yapabileceğimize bakalım. Öncelikle veritabanı tablolarımızı oluşturalım. Basit tutalım:

-- PostgreSQL Örneği

CREATE TABLE Categories ( CategoryId SERIAL PRIMARY KEY, CategoryName VARCHAR(100) NOT NULL );

CREATE TABLE Products ( ProductId SERIAL PRIMARY KEY, ProductName VARCHAR(100) NOT NULL, Price DECIMAL(10, 2) );

-- Çoktan Çoğa İlişki Tablosu

CREATE TABLE ProductCategory ( ProductId INT REFERENCES Products(ProductId), CategoryId INT REFERENCES Categories(CategoryId), PRIMARY KEY (ProductId, CategoryId) -- Hem ürüne hem kategoriye aynı anda bağlanmasın diye );

Bu tabloları oluşturduktan sonra, artık bu ilişkiyi kodumuzda nasıl yöneteceğimize bakalım. Diyelim ki bir ürünün birden fazla kategorisini almak istiyoruz. Normalde Dapper ile bunu yaparken biraz daha karmaşık sorgular yazmamız gerekebilir. Ama gelin size hem ‘hatalı’ hem de ‘doğru’ bir yaklaşım göstereyim. Hatalı dediğim, her ilişki için ayrı ayrı sorgu çekmek gibi düşünebilirsiniz. Bu hem performans açısından kötü hem de kod karmaşıklığını artırır.

İşte burada benim eski bir kodumdan bir kesit. Hatırlıyorum da, o zamanlar bu işleri yeni yeni öğreniyordum ve her ilişki için ayrı sorgu çekiyordum. Sonra bir baktım, performans yerlerde sürünüyor. Kod da okunmaz hale gelmişti açıkçası. Neyse efendim, size o dönemin ‘yanlış’ mantığını göstermeyeceğim şimdi, çünkü zaten o kodlar yok oldu gitti 🙂

Bunun yerine, Dapper ile birden çok ilişkiyi tek sorguda nasıl alabileceğimize dair güzel bir örnek vereyim. Bu yaklaşımda, ana tabloyu (Products) alırken, ilgili ara tablo (ProductCategory) ve oradan da diğer ana tabloyu (Categories) JOIN ile getiriyoruz. Dapper’ın ‘Split On’ özelliği burada çok işimize yarıyor. Bu sayede tek bir sorguyla hem ürünleri hem de onlara bağlı kategorileri alabiliyoruz.

Şöyle bir kod parçası düşünün:

public class ProductWithCategoriesViewModel {     public int ProductId { get; set; }     public string ProductName { get; set; }     public decimal Price { get; set; }     public List Categories { get; set; } = new List(); }

public class Category { public int CategoryId { get; set; } public string CategoryName { get; set; } }

// Veritabanı erişim metodu örneği public async Task<IEnumerable<ProductWithCategoriesViewModel>> GetProductsWithCategoriesAsync(IDbConnection db) { var sql = @" SELECT p.ProductId, p.ProductName, p.Price, c.CategoryId, c.CategoryName FROM Products p LEFT JOIN ProductCategory pc ON p.ProductId = pc.ProductId LEFT JOIN Categories c ON pc.CategoryId = c.CategoryId ORDER BY p.ProductId; ";

var products = await db.QueryAsync<ProductWithCategoriesViewModel, Category, ProductWithCategoriesViewModel>(sql, (product, category) => { if (category != null) { product.Categories.Add(category); } return product; }, splitOn: "CategoryId"); // CategoryId'den sonraki sütunlar Category nesnesine ait

// Aynı ProductId'ye sahip ürünleri gruplayalım var groupedProducts = products.GroupBy(p => p.ProductId) .Select(g => { var product = g.First(); // Gelen tüm kategorileri tekilleştir product.Categories = g.SelectMany(p => p.Categories) .Where(c => c != null) // Null olmayanları al .ToList(); return product; });

return groupedProducts; }

Bu kodda, `QueryAsync` metodu ile hem `ProductWithCategoriesViewModel` hem de `Category` sınıflarını temsil eden verileri çekiyoruz. `splitOn: “CategoryId”` parametresi, Dapper’a `CategoryId` sütunundan sonraki tüm sütunların `Category` nesnesine ait olduğunu söylüyor. Sonra gelen `GroupBy` işlemi ise, aynı ürün ID’sine sahip olanları gruplayıp, kategorileri tek bir ürün nesnesi altında topluyor. Ne güzel değil mi? Tek bir sorgu, iki tablo ve aradaki ara tablo ile verileri çekmiş oluyoruz.

Bu multi-mapping olayı, veritabanı tasarımının temel taşlarından biri aslında. Özellikle karmaşık ilişkileri olan projelerde işleri çok ama çok kolaylaştırıyor. Aksi takdirde, her bir ilişki için ayrı ayrı sorgular yazmak, hem kod kalabalığına yol açar hem de uygulamanın performansını ciddi şekilde düşürür. Yani sonuç olarak, bu tür ilişkileri doğru kurmak ve yönetmek, hem geliştirme sürecini hızlandırır hem de projenin daha stabil ve performanslı olmasını sağlar. Bu arada, bu konuyu daha detaylı incelemek isterseniz, Dapper multi-mapping tutorial diye aratırsanız bolca kaynak bulabilirsiniz.

Tabi, her teknoloji gibi bunun da kendine göre ince detayları var. Mesela performans için doğru indekslemeler yapmak, sorguları optimize etmek gibi. Ama genel mantık bu şekilde. Yani neymiş efendim, karmaşık ilişkilerde ara tabloları kullanmaktan çekinmeyin, hatta kullanın!

Geçenlerde bir kamp etkinliğine katıldık eşimle. Bursa’nın Uludağ’ına çıktık. Hava buz gibiydi ama manzara inanılmazdı. Akşam kamp ateşinin başında otururken, yıldızları izlerken aklıma geldi, acaba kaç tane veritabanı tablosu bu kadar mükemmel bir uyum içinde çalışabiliyordu? Belki de bu kadar kusursuz bir uyum yakalamak için, tıpkı bu kamp alanındaki gibi, her parçanın doğru yere oturduğu bir yapı kurmak gerekiyor. Hani bir yapboz gibi düşünün.

Neyse efendim, umarım bu anlattıklarım kafanızda bir ışık yakmıştır. Multi-mapping konusu gerçekten de üzerinde durulmaya değer bir konu. Eğer siz de veritabanı ilişkileriyle uğraşıyorsanız, bu yöntemi mutlaka deneyin. Özellikle Entity Framework Core kullanıyorsanız, işiniz çok daha kolay olacak. Dapper ile de yapılabiliyor ama biraz daha dikkatli olmak gerekiyor tabii ki.

Unutmayın, kod yazmak sadece komutları sıralamak değil, aynı zamanda problemleri çözme sanatı. Ve bu sanatın bir parçası da, verileri en verimli şekilde yönetebilmek. Multi-mapping de tam olarak bunun için var. Yani şey gibi… verileri bir araya getirmenin en akıllı yolu gibi. 🙂 Anladınız siz onu.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.