3. SQL Server Express 2012 ile vaka çalışması
3.1. Introduction
İnternette Entity Framework ile ilgili bulunan örneklerin çoğu, SQL Server ile hazırlanmış örneklerdir. Bu oldukça normaldir. Muhtemelen bu, kurumsal dünyada en yaygın olarak kullanılan SGBD sürümüdür. Biz de bu eğilimi takip edeceğiz. Örnekler daha sonra 1.2. paragrafında bahsedilen tüm veritabanlarına genişletilecektir.
3.2. Araçların Kurulumu
Araçların kurulumunu burada açıklamayacağız. Zira bu, çok sayıda ekran görüntüsü gerektirecek ve bu görüntüler kısa sürede geçerliliğini yitirecektir. Bu görevi (her zaman kolay olmasa da) okuyucuya bırakıyoruz.
Aşağıdaki araçları yüklememiz gerekiyor:
- SGBD SQL Server Express 2012: [http://www.microsoft.com/fr-fr/download/details.aspx?id=29062]. SGBD yönetim aracını içeren "With Tools" sürümünü indirin:
SGBD kurulduktan sonra programı başlatın:
![]() |
![]() |
- [1]: Başlat Menüsünden "SQL Sunucu Yapılandırma Yöneticisi"ni başlatın;
- [2]: Bu yönetici içinde sunucuyu başlatın;
- [3]: Sunucu başlatıldı.
Şimdi SQL Sunucusu yönetim aracını başlatıyoruz:
![]() |
- [1]: Başlat Menüsünden "SQL Server Management Studio" programını başlatın;
- [2]: yönetim aracı.
Sunucuya bağlanacağız:
![]() |
- [1]'te, nesne gezgini açın;
- [2]'te bağlantı parametrelerini giriyoruz:
- [3]: (yerel) sunucu (gerekli parantezlere dikkat edin), makineye kurulu sunucuyu belirtir,
- [4]: Windows kimlik doğrulaması seçilir. Bu bağlantının başarılı olması için bilgisayarınızın yöneticisi olmanız gerekir,
![]() |
- [6]: Bağlantı kuruldu;
- [7]: Sunucunun belirli özelliklerini değiştirmek istiyoruz;
![]() |
- [8]: iki kimlik doğrulama modu talep ediliyor:
- az önce kullanıldığı gibi Windows kimlik doğrulaması. Böylece, uygun haklara sahip bir Windows kullanıcısı oturum açabilir,
- SQL Sunucu kimlik doğrulaması. Kullanıcı, SGBD'te kayıtlı kullanıcılar arasında yer almalıdır;
Bu işlem tamamlandıktan sonra, sunucu özelliklerini onaylayabiliriz;
- [9]: "sa" (sistem yöneticisi) kullanıcısının özelliklerini düzenleyin;
![]() |
- [10]'te bu kullanıcıya bir şifre atanır. Bu belgenin devamında bu şifre sqlserver2012 olarak belirtilmiştir;
![]() |
- [10]'te, kullanıcıya bağlanma izni verilir;
- [11]'te bağlantı etkinleştirilir. Böylece sihirbaz onaylanabilir;
- [12] adımında, sunucudan bağlantı kesilir.
Şimdi, sa/sqlserver2012 kullanıcı adıyla yeniden bağlanıyoruz:
![]() |
- [1]'te yeniden bağlanıyoruz;
- [2]'te, SQL Sunucusu kimlik doğrulaması yapılır;
- [3]'te kullanıcı adı sa;
- [4]'te, şifresi sqlserver2012'dir;
- 5'te oturum açılır;
![]() |
- [6]'te, oturum açılmış durumdayız.
Şimdi bir demo veritabanı oluşturacağız:
![]() |
- [1]'te, yeni bir BD oluşturulur;
- [2]'te, adını demo olarak belirleyeceğiz;
- [3]'te onaylıyoruz;
![]() |
- [4]'te veritabanı oluşturulur;
- 5'te, demo veritabanında yeni bir tablo oluşturulur;
![]() |
![]() |
![]() |
![]() |
- [6]'te, ID ve NOM adlı iki sütunlu bir tablo tanımlanır;
- [7]'te, [ID] sütunu birincil anahtar olarak belirlenir;
- [8]'te, birincil anahtar bir anahtar simgesiyle gösterilir;
- [9]'te tablo kaydedilir;
- [10]'te tabloya bir ad verilir;
- [11]'te, tablonun [demo] veritabanında görünmesi için veritabanının yenilenmesi gerekir;
- [12]'te, [PERSONNES] tablosu başarıyla oluşturulmuştur.
Şu an için SQL Server yönetim aracının kullanımı hakkında yeterince bilgiye sahibiz.
3.3. Yerleşik sunucu (localdb)\v11.0
VS Express 2012, yerleşik bir SQL sunucusuyla birlikte gelir. Burada, VS Express 2012'nin [http://www.microsoft.com/visualstudio/fra/downloads] ile kurulduğu varsayılmaktadır. VS 2012'yi [1] ile başlatıyoruz:
![]() |
SQL Server 2012 yönetim aracını başlatıp [2]'e bağlanıyoruz ve [3]'e giriş yapıyoruz.
![]() |
- [4]'te, (localdb)\v11.0 sunucusuna bağlanıyoruz;
- 5'te, Windows kimlik doğrulaması ile;
- [6]'te, başarılı bir şekilde oturum açıldığında sunucudaki veritabanları görüntülenir. Daha önce olduğu gibi yeni bir veritabanı oluşturabiliriz.
VS 2012'de bu yerleşik sunucuyu kullanmayacağız.
3.4. Varlıklardan veritabanı oluşturma
Entity Framework 5 Code First, varlıklar kullanılarak bir veritabanı oluşturulmasına olanak tanır. Şimdi bunu göreceğiz. VS Express 2012 ile C# dilinde ilk konsol projemizi oluşturuyoruz:
![]() |
![]() |
- [1], projenin tanımı;
- [2] dosyasında ise oluşturulan proje yer alıyor.
Tüm projelerimiz, Entity Framework 5'in DLL'sine ihtiyaç duyacak. Bunu ekliyoruz:
![]() |
- [1] dosyasında, NuGet aracı bağımlılıkları indirmeye olanak tanır;
![]() |
- [2] olarak değiştirildiğinde, Entity Framework bağımlılığı indirilir;
- [3]'te, referans projeye eklenmiştir.
Eklenen referansın özelliklerine bakarak daha fazla bilgi edinebiliriz:
![]() |
- [1]'te, DLL'in sürümü. 5 sürümü gereklidir;
- [2] dosyası için dosya sistemindeki konumu: <solution>\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll burada <solution>, VS çözümünün klasörüdür. NuGet tarafından eklenen tüm paketler <solution>/packages klasörüne yerleştirilecektir;
- [3] içinde, [packages.config] adlı bir dosya oluşturulmuştur. İçeriği şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="5.0.0" targetFramework="net45" />
</packages>
Bu dosya, NuGet tarafından içe aktarılan paketleri listeler.
VS projesine geri dönelim ve proje içinde [Models] adlı bir klasör oluşturalım:
![]() |
- [1]'te, projeye bir klasör eklenmesi;
- [2]'te, adı [Models] olacak.
Bundan sonra da varlık tanımlarımızı [Models] klasörüne koyma alışkanlığımızı sürdüreceğiz.
Varlıklarımızı oluşturmak için, NHibernate projesinde kullanılan MySQL 5 veritabanı tanımından yararlanacağız. EF varlıklarının rolünü hatırlayalım:
![]() |
Varlıklar, veritabanındaki tabloları yansıtmalıdır. Veri erişim katmanı, tablolarla doğrudan çalışmak yerine bu varlıkları kullanır. [MEDECINS] tablosuyla başlayalım:
3.4.1. [Medecin] varlığı
Bu varlık, [RdvMedecins] uygulaması tarafından yönetilen doktorlar hakkında bilgiler içerir.
![]() | ![]() |
- ID: doktoru tanımlayan numara - tablonun birincil anahtarı
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- NOM: doktorun soyadı
- PRENOM: doktorun adı
- TITRE: unvanı (Bayan, Bayan, Bay)
Aşağıdaki [Medecin] sınıfından yola çıkabiliriz:
using System;
[Table("MEDECINS", Schema = "dbo")]
namespace RdvMedecins.Entites
{
public class Medecin
{
// veri
public int Id { get; set; }
public string Titre { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
}
-
- satır: [Medecin] sınıfı, veritabanındaki [MEDECINS] tablosuyla ilişkilidir. Bu tablo, "dbo" adlı şemada yer alacaktır.
Bu sınıfı [Entites.cs] [1] dosyasına yerleştiriyoruz. Tüm varlıklarımızı buraya yerleştireceğiz.
![]() |
Yine [Models] klasöründe, aşağıdaki [Context.cs] dosyasını oluşturuyoruz:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// bağlam
public class RdvMedecinsContext : DbContext
{
// doktorlar
public DbSet<Medecin> Medecins { get; set; }
}
// veritabanının başlatılması
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
}
}
-
- satır: [RdvMedecinsContext] sınıfı, c.-à-d'in kalıcılık bağlamını temsil edecektir. ORM tarafından yönetilen tüm varlıklar. Bu sınıf, [System.Data.Entity.DbContext] sınıfından türetilmelidir;
-
- satır: [Medecins] alanı, kalıcılık bağlamındaki [Medecin] türündeki varlıkları temsil edecektir. Bu, DbSet<Medecin> türündedir. Genellikle veritabanındaki tablo sayısı kadar [DbSet] bulunur; her tablo için bir tane;
-
- satır: Oluşturulan veritabanını başlatmak için bir [RdvMedecinsInitializer] sınıfı tanımlanır. Burada bu sınıf, adından da anlaşılacağı gibi, veritabanı zaten mevcutsa onu silip yeniden oluşturan [DropCreateDataBaseAlways] sınıfından türetilmiştir. Bu, BD'in geliştirme aşamasında kullanışlıdır. [DropCreateDataBaseAlways] sınıfının parametresi, veritabanıyla ilişkili kalıcılık bağlamı türüdür. Başlatma sınıfı için [DropCreateDataBaseAlways] dışında başka üst sınıflar da kullanılabilir:
- [DropCreateDatabaseIfModelChanges]: varlıklar değişmişse veritabanını yeniden oluşturur,
- [CreateDatabaseIfNotExists]: veritabanı yoksa oluşturur;
Şimdi bir ana program oluşturmamız gerekiyor. Bu program şu şekilde olacaktır: [CreateDB_01.cs]:
using System;
using System.Data.Entity;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class CreateDB_01
{
static void Main(string[] args)
{
// veritabanı oluşturuluyor
Database.SetInitializer(new RdvMedecinsInitializer());
using (var context = new RdvMedecinsContext())
{
context.Database.Initialize(false);
}
}
}
}
-
- satır: [System.Data.Entity.DataBase], bir kalıcılık bağlamıyla ilişkili veritabanını yönetmek için statik yöntemler sunan bir sınıftır. [SetInitializer] statik yöntemi, veritabanının başlatma sınıfını belirtmeye olanak tanır. Bu, başlatma işlemini başlatmaz;
- satır 13: Bir kalıcılık bağlamıyla çalışmak için, bu bağlamın bir örneğini oluşturmak gerekir. Burada yapılan da budur. Bağlamın, using cümlesinden çıkıldığında otomatik olarak kapatılması için bir using cümlesi kullanılır. Dolayısıyla satır 17’de bağlam kapatılır;
-
- satır: [RdvMedecinsContext] kalıcılık bağlamıyla ilişkili veritabanının oluşturulmasını açıkça başlatıyoruz. false parametresi, bu işlem bu bağlam için daha önce yapılmışsa tekrarlanmaması gerektiğini belirtir. Burada true da yazılabilirdi.
Bir veritabanıyla çalışırken, bağlantı parametreleri genellikle [App.config] dosyasına kaydedilir. Şu anda bu parametrelerin dosyada bulunmadığını görüyoruz:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Entity Framework yapılandırması hakkında daha fazla bilgi için http://go.microsoft.com/fwlink/?LinkID=237468 adresini ziyaret edin -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
</configuration>
Yukarıdaki öğeler, proje referanslarına Entity Framework bağımlılığı eklendiğinde [App.config] dosyasına kaydedilmiştir.
SQL Server Express'i başlattıktan sonra (bu önemlidir) projeyi çalıştıralım (Ctrl-F5):
![]() | ![]() |
Çalıştırma işlemi hata vermeden tamamlanmalıdır. Şimdi SQL Server yönetim aracını açalım ve ekranı yenileyelim:
![]() |
[RdvMedecinsContext] sınıfının tam adını taşıyan bir veritabanının oluşturulduğunu ve bu veritabanının, [dbo.MEDECINS] (bu, veritabanına verdiğimiz addır) ve bu tablodaki sütunlar, [Medecin] varlığının alan adlarını yansıtmaktadır. Kod başarıyla çalıştırılmış olmasına rağmen yukarıdaki veritabanı görünmüyorsa, gömülü sunucuya (localdb)\v11.0 bakılmalıdır (bkz. sayfa 19). VS 2012 Pro sürümünde, kodun çalıştırıldığı sırada SQL sunucusu aktif değilse bu sunucu kullanılır. VS 2012 Express sürümünde ise kullanılmaz.
[MEDECINS] tablosunun yapısını inceleyelim:
- bu tablo, [Medecin] varlığının alan adlarını içerir;
- [Id] sütunu birincil anahtardır. Bu, EF'in bir kuralıdır: E varlığının bir Id veya Eid alanı (MedecinId) varsa, bu sütun ilişkili tabloda birincil anahtardır;
- tablodaki sütunların türleri, varlığın alanlarının türleridir;
- Başlık, Soyadı, Ad sütunları için [nvarchar(max)] türü kullanılmıştır. Daha kesin olmak gerekirse, başlık için 5 karakter, soyadı ve ad için 30 karakter;
- Başlık, Soyadı ve Ad sütunları NULL değerine sahip olabilir. Bunu değiştireceğiz.
[Id] birincil anahtarının özelliklerine bakalım:
![]() |
[1] örneğinde, birincil anahtarın [Identité] türünde olduğunu görüyoruz; bu, değerinin SQL Sunucusu tarafından otomatik olarak oluşturulduğu anlamına gelir. Tüm SGBD örnekleri için bu stratejiyi uygulayacağız.
Anotasyonlar kullanarak EF'teki kurallara daha az yer vereceğiz. [Entites.cs]'teki varlık kodu şu şekilde olur:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// veriler
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Required]
[Column("VERSION")]
public int Version { get; set; }
}
}
-
- ve 3. satırlar: Anotasyonlar, [System.ComponentModel.DataAnnotations] (Key, Required, MaxLength) ve [System.ComponentModel.DataAnnotations.Schema] (Column) ad alanlarında bulunur. Diğer açıklamalar ise URL ve [http://msdn.microsoft.com/en-us/data/gg193958.aspx] ad alanlarında bulunur;
-
- satır: [Key] birincil anahtarı belirtir;
-
- satır: [Column], alana karşılık gelen sütun adını belirler;
-
- satır: [Required], alanın zorunlu olduğunu belirtir (SQL, NOT, NULL);
-
- satır: [MaxLength], karakter dizisinin maksimum uzunluğunu, [MinLength] ise minimum uzunluğunu belirler;
[Medecin] varlık tanımını kullanarak projeyi çalıştıralım. Oluşturulan veritabanı şu şekildedir:
![]() |
- sütunlar, kendilerine atanan isimlere sahiptir;
- [Required] açıklaması, SQL, NOT ve NULL olarak çevrilmiştir;
- [MaxLength(N)] etiketinin türü SQL nvarchar(N) olarak çevrildi.
NHibernate uygulamasında, [VERSION] sütunu, bir tablonun aynı satırına eşzamanlı erişimleri önlemek için kullanılıyordu. İlke şöyledir:
- bir P1 işlemi, [MEDECINS] tablosundaki L satırını T1 zamanında okur. Satırın sürümü V1'tir;
- P2 süreci, [MEDECINS] tablosundaki aynı L satırını T2 zamanında okur. Satırın sürümü V1'tir, çünkü P1 işlemi henüz yaptığı değişikliği onaylamamıştır;
- P1 işlemi, L satırındaki değişikliğini onaylar. L satırının sürümü bu durumda V2=V1+1 olarak değişir;
- P2 süreci, L satırındaki değişikliğini onaylar. Bunun üzerine ORM, P2 işleminin L satırına ait V1 sürümünün, veritabanında bulunan V2 sürümünden farklı olması nedeniyle bir istisna oluşturur.
Buna "eşzamanlı erişimlerin iyimser yönetimi" denir. EF 5 ile, bu rolü üstlenen bir alanın [Timestamp] veya [ConcurrencyCheck] özniteliklerinden birine sahip olması gerekir. SQL Server, [timestamp] türündedir. Bu türe sahip bir sütunun değeri, bir satırın eklenmesi veya değiştirilmesi durumunda SQL Server tarafından otomatik olarak oluşturulur. Bu tür bir sütun, erişim rekabetini yönetmek için kullanılabilir. Önceki örneği ele alırsak, P2 işlemi, okuduğundan farklı bir timestamp bulacaktır; çünkü bu arada P1 işlemi tarafından yapılan değişiklik, onu değiştirmiş olacaktır.
[Medecin] varlığımız şu şekilde değişir:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RdvMedecins.Entites
{
[Table("MEDECINS", Schema = "dbo")]
public class Medecin
{
// veri
[Key]
[Column("ID")]
public int Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
}
- 26-28. satırlar: 27. satırdaki [Timestamp] özniteliğine sahip yeni sütun. Alanın türü byte[] olmalıdır (28. satır). Alanın adı herhangi bir şey olabilir. [Required] özniteliği eklenmez, çünkü bu değeri uygulama değil, SGBD'in kendisi sağlayacaktır.
Projeyi bu yeni varlıkla çalıştırırsak, veritabanı şu şekilde değişir:
![]() |
Son bir noktayı daha halletmemiz gerekiyor. Kalıcılık bağlamı, bir varlığın veritabanına eklenmesi gerektiğini "bilir", çünkü o anda birincil anahtarı null değerindedir. Birincil anahtara değer verecek olan, veritabanına eklenme işlemidir. Burada, int birincil anahtarına verilen [Id] türü uygun değildir; çünkü bu tür, null değerini kabul etmez. Bu durumda, int değerini ve null işaretçisini kabul eden int? türü atanır. Dolayısıyla, kullanılacak [Medecin] varlığı şu şekilde olacaktır:
public class Medecin
{
// veri
[Key]
[Column("ID")]
public int? Id { get; set; }
...
Şimdi, tablolar arasındaki yabancı anahtar kavramını bir varlıkta nasıl temsil edeceğimizi ele alalım.
3.4.2. [Creneau] varlığı
[CRENEAUX] tablosu, RV'lerin mümkün olduğu zaman aralıklarını listeler:
![]() |
![]() |
- ID: zaman aralığını tanımlayan numara – tablonun birincil anahtarı
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- ID_MEDECIN: Bu zaman diliminin ait olduğu doktoru tanımlayan numara – MEDECINS (ID) sütunundaki yabancı anahtar.
- HDEBUT: zaman aralığının başlangıç saati
- MDEBUT: zaman aralığının başlangıç dakikası
- HFIN: zaman aralığının bitiş saati
- MFIN: zaman aralığının bitiş dakikası
[CRENEAUX] tablosunun ikinci satırı (yukarıdaki [1]'e bakınız), örneğin, 2 numaralı zaman diliminin saat 08:20'de başlayıp 08:40'ta bittiğini ve 1 numaralı doktora ait olduğunu gösterir (Bayan Marie PELISSIER) ait olduğunu gösterir.
Elimizdeki bilgilerle, [Creneau] varlığını [Entites.cs]'te şu şekilde tanımlayabiliriz:
[Table("CRENEAUX", Schema = "dbo")]
public class Creneau
{
// veri
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("HDEBUT")]
public int Hdebut { get; set; }
[Required]
[Column("MDEBUT")]
public int Mdebut { get; set; }
[Required]
[Column("HFIN")]
public int Hfin { get; set; }
[Required]
[Column("MFIN")]
public int Mfin { get; set; }
[Required]
public virtual Medecin Medecin { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
Tek yenilik 20-21. satırlarda yer almaktadır. [CRENEAUX] tablosunun [MEDECINS] tablosuna yabancı anahtarı olması durumu, [Creneau] varlığında [Medecin] varlığına yapılan bir referansla yansıtılır; 21. satırda yansıtılır. Alanın adı önemli değildir, sadece türü önemlidir. Özellik, virtual anahtar sözcüğüyle sanal olarak tanımlanmalıdır. Nitekim, EF, tüm "navigasyonel" özellikleri, yani bir yabancı anahtara karşılık gelen ve bir tablodan diğerine geçişe olanak tanıyan özellikleri yeniden tanımlamak zorundadır.
Yeni varlığı test etmek için [Context.cs]'te birkaç değişiklik yapmamız gerekiyor:
using System.Data.Entity;
using RdvMedecins.Entites;
namespace RdvMedecins.Models
{
// bağlam
public class RdvMedecinsContext : DbContext
{
// varlıklar
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
}
// veritabanının başlatılması
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
}
}
12. satır, bağlamın yönetmesi gereken bir varlık daha olduğunu gösterir. Projeyi çalıştırdığımızda, aşağıdaki yeni veritabanını elde ederiz:
![]() |
[CRENEAUX] tablosu başarıyla oluşturulmuştur ve yeni olan, [1] ve [2] yabancı anahtarlarının varlığıdır. Bu anahtarın adı, varlıktaki (Medecin) ilgili alanın adına "_Id" son eki eklenerek oluşturulmuştur. Bu yabancı anahtarın özelliklerini öğrenmek için, onu [3] olarak değiştirmeyi deneyelim.
![]() |
Yukarıdaki ekran görüntüsü, [Medecin_Id]'in [CRENEAUX] tablosunun yabancı anahtarı olduğunu ve [MEDECINS] tablosunun birincil anahtarı olan [ID]'e referans verdiğini göstermektedir.
Mevcut bir veritabanı için nesneler oluşturulursa, yabancı anahtar sütununun adı mutlaka [Medecin_Id] olmayacaktır. Diğer sütunlar için, [Column] notunun bu sorunu çözdüğünü görmüştük. Garip bir şekilde, yabancı anahtar için durum daha karmaşıktır. Aşağıdaki şekilde ilerlemelisiniz:
public class Creneau
{
// veri
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
- 5-7. satırlar: yabancı anahtarın türünde (int) bir alan oluşturulur. [Column] özniteliği kullanılarak, varlıkla ilişkili tabloda yabancı anahtar olacak sütunun adı belirtilir;
- 9. satır: [Medecin] türündeki alana [ForeignKey] açıklaması eklenir. Bu açıklamanın argümanı, tablonun yabancı anahtar sütunuyla ilişkili olan alanın adıdır (sütunun adı değil).
Projenin çalıştırılması bu sefer aşağıdaki tabloyu oluşturur:
![]() |
Yukarıda, yabancı anahtar sütunu kendisine verilen adı taşıyor. Şu alanlara dikkat edilmelidir:
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
'nin yalnızca tek bir sütun, yani [MEDECIN_ID] sütununu oluşturduğu unutulmamalıdır. Bununla birlikte, [MedecinId] alanının varlığı önemlidir. [CRENEAUX] tablosundan bir satır okunduğunda, bu satır [MEDECIN_ID] sütunundaki değeri, yani [MEDECINS] tablosundaki yabancı anahtarın değerini alır. Bu durum genellikle yararlıdır.
Yukarıdaki [Medecin] alanı, [Creneau] varlığını [Medecin] varlığına bağlayan çok-bir ilişkisini yansıtır. Birden fazla [Creneau] nesnesi, aynı [Medecin] nesnesine bağlıdır. Tersine, bir [Medecin] nesnesinin birden fazla [Creneau] nesnesiyle ilişkili olduğu durum, [Medecin] varlığında ek bir alan kullanılarak modellenebilir:
public class Medecin
{
// veri
[Key]
[Column("ID")]
public int? Id { get; set; }
...
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
8. satırda, [Creneau] nesnelerinin bir koleksiyonu olan [Creneaux] alanı eklenmiştir. Bu alan, doktorun tüm randevu zaman aralıklarına erişim sağlayacaktır.
Projeyi yeniden çalıştırdığımızda, [MEDECINS] tablosunda herhangi bir değişiklik olmadığını görüyoruz:
![]() |
Hiçbir sütun eklenmemiştir. [CRENEAUX] tablosu ile [MEDECINS] tablosu arasında bulunan yabancı anahtar ilişkisi, EF'in buna bağlı alanları oluşturabilmesi için yeterlidir:
public class Medecin
{
...
public ICollection<Creneau> Creneaux { get; set; }
...
}
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
Temel bilgileri biliyoruz. Kalan iki varlığın oluşturulmasını tamamlayabiliriz.
3.4.3. [Client] ve [Rv] varlıklarını
Öğrendiklerimizi kullanarak [Client] ve [Rv] varlıklarını yazabiliriz. [Client] varlığı, [RdvMedecins] uygulaması tarafından yönetilen müşterilerle ilgili bilgileri içerir.
![]() | ![]() |
- ID: müşteriyi tanımlayan numara – tablonun birincil anahtarı
- VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
- NOM: müşterinin adı
- PRENOM: müşterinin adı
- TITRE: unvanı (Bayan, Bayan, Bay)
[Client] varlığı şu şekilde olabilir:
[Table("CLIENTS", Schema = "dbo")]
public class Client
{
// veriler
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// müşterinin Rvs'leri
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
[Client] sınıfı, [Medecin] sınıfıyla neredeyse aynıdır. Bunlar aynı üst sınıftan türetilebilir. Yenilik 21. satırda yer almaktadır. Bu, bir müşterinin birden fazla randevusu olabileceğini yansıtmaktadır ve [RVS] tablosundan [CLIENTS] tablosuna uzanan bir yabancı anahtarın varlığından kaynaklanmaktadır.
[Rv] nesnesi bir randevuyu temsil eder:
![]() |
- ID: RV'i benzersiz bir şekilde tanımlayan numara – birincil anahtar
- JOUR: RV'in günü
- ID_CRENEAU: RV'in zaman aralığı – [CRENEAUX] tablosundaki [ID] sütunundaki yabancı anahtar – hem zaman aralığını hem de ilgili doktoru belirler.
- ID_CLIENT: Rezervasyonun yapıldığı müşterinin numarası – [CLIENTS] tablosunun [ID] sütunundaki yabancı anahtar
[Rv] varlığı şu şekilde olabilir:
[Table("MEDECINS", Schema = "dbo")]
public class Rv
{
// veriler
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[Column("JOUR")]
public DateTime Jour { get; set; }
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
}
- 5-7. satırlar: birincil anahtar;
- 8-10. satırlar: randevu tarihi;
- 11-12. satırlar: [RVS] tablosundan [CLIENTS] tablosuna yabancı anahtar;
- satır 13-15: randevusu olan müşteri;
- satır 16-17: [RVS] tablosundan [CRENEAUX] tablosuna yabancı anahtar;
- satır 18-20: randevu zaman aralığı;
- satır 21-23: eşzamanlı erişim yönetimi alanı.
17. satırda, çoktan bire ilişki görülmektedir: bir zaman aralığına birden fazla randevu karşılık gelebilir (aynı gün içinde değil). Ters ilişki, [Creneau] varlığında yansıtılabilir:
public class Creneau
{
// zaman aralığına ait randevular
public ICollection<Rv> Rvs { get; set; }
...
}
4. satırda, bu zaman aralığında alınan randevuların koleksiyonu yer almaktadır.
Proje çalıştırıldığında, oluşturulan veritabanı şu şekildedir:
![]() |
[MEDECINS] ve [CRENEAUX] tablolarında değişiklik olmamıştır. [CLIENTS] ve [RVS] tabloları şu şekildedir:
![]() | ![]() |
Bu beklenen bir durumdu. Halletmemiz gereken birkaç ayrıntı kaldı:
- veritabanının adını yönetmek. Burada bu ad, EF tarafından oluşturulmuştur;
- veritabanını verilerle doldurmak.
3.4.4. Veritabanının adını belirleme
EF tarafından oluşturulan veritabanının adını belirlemek için, [App.config]'te tanımlanan bir bağlantı dizesi kullanacağız. Bu yapılandırma dosyası şu şekilde güncellenir:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Entity Framework yapılandırması hakkında daha fazla bilgi için http://go.microsoft.com/fwlink/?LinkID=237468 adresini ziyaret edin -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- veritabanı bağlantı dizesi -->
<connectionStrings>
<add name="RdvMedecinsContext"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- fabrika sağlayıcısı -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
- 15-19. satırlar: veritabanına bağlantı dizesi;
- 16. satır: [name] özniteliği, kalıcılık bağlamı için kullanılan [RdvMedecinsContext] sınıfının adını alır. Bunu hatırlamak önemlidir. Bu kısıtlama, bağlam oluşturucusunda aşılabilir:
// oluşturucu
public RdvMedecinsContext()
: base("monContexte")
{
}
Bu durumda, name= "monContexte" şeklinde bir değer elde edebiliriz. Belgenin ilerleyen kısımlarında bu değeri göreceğiz.
- 17. satır: bağlantı dizesi. [Data Source]: SGBD'in bulunduğu sunucunun adı, [Initial Catalog]: veritabanının adı, yani burada [rdvmedecins-ef]; [User Id]: bağlantının sahibi; [Password]: şifresi. Okuyucu bu dizgiyi kendi ortamına göre uyarlayacaktır;
- 21-29. satırlar: bir [DbProviderFactory] tanımlar. Bunun ne olduğunu bilmiyorum. Adından yola çıkarak, EF ile SGBD'i ayıran [ADO.NET] katmanını oluşturmaya yarayan bir sınıf olabilir:
![]() |
Aslında bu satırlar SQL Sunucusu için gereksizdir, ancak diğer SGBD'ler için bunları eklemek zorunda kaldım. Ayrıca, hatırlamak için buraya ekliyorum. Bir sakıncası yok. Tek önemli nokta, 27. satırdaki sürümdür. Bu, proje referanslarında bulunan DLL ve [System.Data] sürümlerindekiyle aynıdır:
![]() |
İşte bu kadar. Hazırız. Projeyi çalıştırıyoruz ve şu [rdvmedecins-ef] veritabanını elde ediyoruz:
![]() |
Bu, nihai veritabanımız olacak. Artık içine veri girmemiz gerekiyor.
3.4.5. Veritabanını doldurma
Veritabanına veri eklemek için veritabanı başlatma sınıfı kullanılabilir:
public class RdvMedecinsInitializer : DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
{
// veritabanının başlatılması
public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
{
protected override void Seed(RdvMedecinsContext context)
{
base.Seed(context);
// veritabanı başlatılıyor
// müşteriler
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// doktorlar
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// randevu aralıkları
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// Randevular
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
}
}
}
- 6. satır: Başlatma işlemi [Seed] yönteminde gerçekleşir. Bu yöntem ana sınıfta bulunur. Burada yeniden tanımlanmıştır. Argüman, uygulamanın [RdvMedecinsContext] kalıcılık bağlamıdır;
- 8. satır: Argüman üst sınıfa aktarılır; bu sınıfın kendisine aktarılan kalıcılık bağlamını açması muhtemeldir, çünkü bu açılış daha sonra gerekli olmayacaktır;
- 11-16. satırlar: 4 müşteri oluşturulur;
- 17-20. satırlar: Bu istemciler kalıcılık bağlamına, daha doğrusu bu bağlamın doktorlarına eklenir. Bunu sağlayan [Add] yöntemine dikkat edilmelidir. Burada bağlamın tanımını hatırlamak gerekir:
public class RdvMedecinsContext : DbContext
{
// kuruluşlar
public DbSet<Medecin> Medecins { get; set; }
public DbSet<Creneau> Creneaux { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Rv> Rvs { get; set; }
...
Müşterilerin bağlama eklendiği de söylenir, yani artık EF tarafından yönetiliyorlar. Önceden bağlamdan ayrılmışlardı. Nesneler olarak var olsalar da EF tarafından yönetilmiyorlardı;
- 21-27. satırlar: 4 doktorun oluşturulması;
- 28-31. satırlar: Bunlar kalıcılık bağlamına eklenir;
- satır 33-70: randevu zaman aralıklarının oluşturulması. Satır 34-57, medecins[0] doktoru için; satır 58-69, medecins[1] doktoru için. Diğer doktorların randevu zaman aralıkları yoktur;
- 71-74. satırlar: Bu zaman aralıkları kalıcı bağlama eklenir;
- 76. satır: İlk müşteri için ilk zaman aralığına bir randevu oluşturulur ve bu randevu kalıcılık bağlamına eklenir.
Proje çalıştırıldığında aşağıdaki veritabanı elde edilir:
![]() | ![]() |
Yukarıda, doldurulmuş [CLIENTS] tablosu görülmektedir.
3.4.6. Varlıkların değiştirilmesi
Şu anda, [Medecin] ve [Client] sınıfları neredeyse aynıdır. Aslında, EF 5 ile kalıcılık yönetimi için eklenen alanlar çıkarılırsa, bu sınıflar tamamen aynıdır. Bunları [Personne] sınıfından türeteceğiz. Böylece bu iki varlık şu şekilde olacaktır:
// bir kişi
public abstract class Personne
{
// veri
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// imza
public override string ToString()
{
return String.Format("[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
...
}
// yardımcı program
private string dump(byte[] timestamp)
{
...
}
}
[Table("MEDECINS", Schema = "dbo")]
public class Medecin : Personne
{
// doktorun randevu saatleri
public ICollection<Creneau> Creneaux { get; set; }
// imza
public override string ToString()
{
return String.Format("Medecin {0}", base.ToString());
}
}
[Table("CLIENTS", Schema = "dbo")]
public class Client : Personne
{
// müşterinin randevuları
public ICollection<Rv> Rvs { get; set; }
// imza
public override string ToString()
{
return String.Format("Client {0}", base.ToString());
}
}
Projeyi çalıştırdığımızda aynı veritabanını elde ederiz. EF 5, kalıtımın en alt sınıflarını her birini ayrı bir tabloya eşlemiştir. Aslında, EF 5, varlık kalıtımını temsil etmek için farklı tablo oluşturma stratejilerine sahiptir. Bunları burada ele almayacağız. Örneğin, " Entity Framework Code First Inheritance: Table Per Hierarchy and Table Per Type" başlıklı makaleyi URL ve [http://www.codeproject.com/Articles/393228/Entity-Framework-Code-First-Inheritance-Table-Per] adreslerinde okuyabilirsiniz.
Bundan sonra bu varlık sürümünü kullanacağız.
3.4.7. Veritabanına kısıtlamalar ekleme
Halletmemiz gereken bir ayrıntı daha var. Randevular tablosu [RVS] şu şekildedir:
![]() |
Bu tablonun bir tekillik kısıtlaması olması gerekir: belirli bir gün için, bir doktorun zaman aralığı bir randevu için yalnızca bir kez rezerve edilebilir. Tablo açısından bu, (JOUR,CRENEAU_ID) çiftinin benzersiz olması gerektiği anlamına gelir. Bu kısıtlamanın kodda doğrudan, ister varlıklar üzerinde ister bağlamda ifade edilip edilemeyeceğini bilmiyorum. Muhtemelen mümkündür ancak bunu kontrol etmedim. Başka bir yol izleyeceğiz. Bu kısıtlamayı eklemek için SQL Server yönetim istemcisini kullanacağız.
"SQL Server Management Studio" ile, bu kısıtlamayı oluşturan SQL komutunu çalıştırmak dışında, bu kısıtlamayı eklemek için basit bir yöntem bulamadım:
![]() |
- [1]'te, [rdvmedecins-ef] veritabanı için SQL sorgusu oluşturulur;
- [2]'te, tekillik kısıtlamasını oluşturan SQL sorgusu;
- [3]'te, bu sorgunun yürütülmesi [RVS] tablosunda yeni bir dizin oluşturmuştur.
SQL Server için başka yönetim araçları da mevcuttur. Burada EMS SQL Manager for SQL Server Freeware [http://www.sqlmanager.net/fr/products/mssql/manager/download] aracını kullanacağız. Kurulum tamamlandıktan sonra aracı başlatıyoruz:
![]() |
- [1]'te bir veritabanı kaydedin;
- [2]'te (yerel) sunucuya bağlanıyoruz;
- [3] komutuyla, SQL Sunucusu kimlik doğrulaması kullanılarak;
- [4]'te, "sa" kullanıcı adıyla;
- 5'te, sqlserver2012 şifresiyle;
- [6]'te bir sonraki adıma geçilir;
![]() |
- [7]'te, [rdvmedecins-ef] veritabanını seçin;
- [8] adımında, sihirbazı tamamlayın;
- [9]'te, veritabanı veritabanı ağacında görünür. [10]'e bağlanıyoruz;
- [11]'te, bağlantı kurulur.
"SQL Manager Lite for SQL Server" ile [RVS] tablosunda tekillik kısıtlaması oluşturulabilir.
![]() |
- [1]'te, daha önce oluşturduğumuz tekillik kısıtlamasını görüyoruz;
- [2]'te bu kısıtlamayı siliyoruz;
- [3]'te, bu tekillik kısıtlamasına karşılık gelen indeks kaybolmuştur.
Silinen kısıtlamayı yeniden oluşturuyoruz:
![]() |
- [1]'te, [RVS] tablosu için yeni bir dizin oluşturuyoruz;
- [2] olarak, ona bir ad veriyoruz;
- [3]'te, bu bir tekillik kısıtlamasıdır;
- [4]'te, JOUR ve CRENEAU_ID sütunları üzerinde;
DDL sekmesi bize çalıştırılacak olan SQL kodunu verir:
![]() |
- [6]'te, SQL komutu derlenir;
![]() |
- [7]'e dönüştürüldüğünde, onaylanır;
- [8]'e dönüştürüldüğünde, yeni indeks ortaya çıkmıştır.
"SQL Manager Lite for SQL server" tarafından sunulan arayüz, "SQL Server Management Studio" tarafından sunulan arayüze benzerdir. Benzer arayüzler SGBD Oracle, PostgreSQL, Firebird ve MySQL için de bulunabilir. Bu nedenle, bundan sonra SGBD yönetim araçları ailesiyle devam edeceğiz.
Bir tablonun bilgilerine erişmek için üzerine çift tıklamak yeterlidir:
![]() |
Seçilen tabloya ait bilgiler sekmelerde görüntülenir. Yukarıda, [CLIENTS] tablosunun [Fields] sekmesi görülüyor. [Data] sekmesi ise tablonun içeriğini gösterir:

3.4.8. Nihai veritabanı
Nihai veritabanımıza sahibiz. Gerekirse yeniden oluşturabilmek için SQL komut dosyasını dışa aktarıyoruz.
![]() |
- [1]'e, sihirbazın başlangıcı;
- [2]'e, sunucu;
- [3]'e, dışa aktarılacak veritabanı;
![]() |
- [4]'te, SQL betiğinin kaydedileceği dosyanın adını belirtin;
- 5'te, kodlamasını belirtin;
- [6]'te, neyi çıkarmak istediğinizi (tablolar, kısıtlamalar, veriler) belirtin;
![]() |
- [7]'te, oluşturulacak komut dosyasını daha ayrıntılı hale getirebilirsiniz;
- [8]'te, sihirbazı tamamlayın.
Komut dosyası oluşturuldu ve komut dosyası düzenleyicisine yüklendi. Oluşturulan SQL kodunu inceleyebilirsiniz. Bu komut dosyasını kullanarak veritabanını yeniden oluşturacağız.
![]() |
- [1]'te veritabanı silinir;
- [2] ve [3] komut dosyalarında veritabanını yeniden oluşturuyoruz;
![]() |
- [4]'te kimlik doğrulaması yapılır;
- 5'te, veritabanını oluşturan SQL komut dosyasını çalıştırıyoruz;
![]() |
- [6]'te, "SQL Manager"a kaydedilir;
- [7] komutuyla, az önce oluşturulan veritabanına bağlanın;
![]() |
- [8]'te, veritabanında şimdilik herhangi bir tablo bulunmamaktadır;
- [9a]'te, SQL komut dosyası düzenleyicisi açılır;
![]() |
- [9b]'te, daha önce oluşturulan SQL komut dosyası açılır;
- [10]'te, komut dosyasını çalıştırın;
![]() |
- [11]'te tablolar oluşturuldu;
- [12]'te tablolar doldurulur;
![]() |
- [14]'te, [RVS] tablosu için oluşturduğumuz tekillik kısıtlamasını buluyoruz.
Artık bu mevcut veritabanıyla çalışacağız. Veritabanı silinirse veya bozulursa, onu yeniden oluşturmayı biliyoruz.
3.5. Entity Framework ile veritabanını kullanma
Şunları yapacağız:
- veritabanındaki öğeleri ekleyeceğiz, sileceğiz ve değiştireceğiz;
- LINQ to Entities ile veritabanında sorgu yapacağız;
- veritabanındaki aynı öğeye eşzamanlı erişimleri yöneteceğiz;
- Lazy Loading / Eager Loading kavramlarını anlayacağız;
- veritabanının kalıcılık bağlamı tarafından güncellenmesinin bir işlem içinde gerçekleştiğini öğreneceğiz.
3.5.1. Kalıcılık bağlamından öğeleri silme
Veritabanımız dolu. Şimdi onu boşaltacağız. Mevcut [1] projesinde yeni bir [Erase.cs] sınıfı oluşturuyoruz:
![]() |
[Erase] sınıfı şu şekildedir:
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Erase
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// mevcut veritabanını boşaltma
// müşteriler
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
// doktorlar
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// kalıcılık bağlamını kaydediyoruz
context.SaveChanges();
}
}
}
}
- 9. satır: Kalıcılık bağlamındaki işlemler her zaman bir [using] cümlesi içinde gerçekleştirilir. Bu, [using]'ten çıkıldığında bağlamın kapatılmış olmasını sağlar;
- 13. satır: [context.Clients] istemcileri bağlamı taranır. Veritabanındaki tüm istemciler kalıcılık bağlamına eklenecektir;
- 15. satır: Her biri için, onları bağlamdan silen [Remove] işlemi gerçekleştirilir. Aslında, bunlar hala bağlamda bulunurlar ancak "silinmiş" durumdadırlar;
- 18-21. satırlar: doktorlar için de aynı işlem yapılır;
- 23. satır: Kalıcılık bağlamı veritabanına kaydedilir.
Bağlamı veritabanına kaydederken, bağlamdaki şu varlıklar:
- birincil anahtarı null olanlara SQL INSERT işlemi uygulanır;
- "silinmiş" durumundakiler, SQL DELETE işlemine tabi tutulur;
- "değiştirildi" durumunda olanlar, SQL ve UPDATE işlemlerine tabi tutulur;
Daha sonra göreceğimiz gibi, bu SQL işlemleri bir işlem bloğu içinde gerçekleştirilir. Bunlardan biri başarısız olursa, daha önce yapılan her şey geri alınır.
[Erase] programını [1] projesinin yeni başlangıç nesnesi yapalım ve ardından projeyi çalıştıralım.
![]() |
Veritabanını kontrol edelim. [2]'teki tüm tabloların boş olduğunu göreceğiz. Bu şaşırtıcıdır, çünkü biz sadece doktorların ve müşterilerin silinmesini istemiştik. Diğer tabloların da zincirleme olarak boşaltılması, yabancı anahtarların işleyişi sayesinde gerçekleşmiştir.
[CRENEAUX] tablosundan [MEDECINS] tablosuna giden yabancı anahtar, EF 5 sağlayıcısı tarafından şu şekilde tanımlanmıştır:
![]() |
- [1]'te, [CRENEAUX] tablosu seçilir;
- [2]'te, yabancı anahtarlar sekmesi seçilir;
- [3]'te, tek yabancı anahtarı düzenleyin;
![]() |
- [4]'te, DDL sekmesinde, yabancı anahtar kısıtlamasının SQL tanımını düzenleyin;
- 5 olarak değiştirilir; ON, DELETE ve CASCADE maddeleri, bir doktorun silinmesinin ona ait randevu aralıklarının da silinmesine yol açar.
[RVS] tablosundaki yabancı anahtar kısıtlamaları da benzer şekilde tanımlanmıştır:
- 1-6. satırlar: Bir müşterinin silinmesi, ona ait randevuların da silinmesine neden olur;
- satır 1-6: Bir zaman aralığının silinmesi, o zaman aralığıyla ilişkili tüm randevuları da siler.
3.5.2. Kalıcılık bağlamına öğeler ekleme
Veritabanını boşalttığımıza göre, şimdi yeniden dolduracağız. Projeye [Fill.cs] ve [1] programlarını ekliyoruz.
![]() |
[Fill.cs] programı şöyledir:
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class Fill
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// mevcut veritabanını boşaltma
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// veritabanını sıfırlıyoruz
// müşteriler
Client[] clients ={
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
foreach (Client client in clients)
{
context.Clients.Add(client);
}
// doktorlar
Medecin[] medecins ={
new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
};
foreach (Medecin medecin in medecins)
{
context.Medecins.Add(medecin);
}
// randevu aralıkları
Creneau[] creneaux ={
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
};
foreach (Creneau creneau in creneaux)
{
context.Creneaux.Add(creneau);
}
// Randevular
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
// kalıcılık bağlamını kaydediyoruz
context.SaveChanges();
}
}
}
}
- 10. satır: kalıcılık bağlamı açılır;
- 13-20. satırlar: [CLIENTS] ve [MEDECINS] tablolarındaki satırlar bağlama eklenir ve ardından buradan silinir. Az önce gördüğümüz gibi, bu işlem veritabanını tamamen boşaltır;
- satır 22-88: kalıcılık bağlamına öğeler eklenir. Hepsinin birincil anahtarı null değerindedir. Dolayısıyla veritabanına ekleneceklerdir;
- 90. satır: Bağlamda yapılan değişiklikler veritabanıyla senkronize edilir. Veritabanında önce SQL ve DELETE işlem serisi, ardından SQL ve INSERT işlem serisi gerçekleştirilecektir;
[Fill] programını, [1] projesinin yeni başlatma nesnesi olarak belirleriz ve ardından bu programı çalıştırırız.
![]() |
[2]'te tabloların doldurulduğunu görüyoruz.
3.5.3. Veritabanının içeriğinin görüntülenmesi
Şimdi LINQ to Entity sorgularını kullanarak veritabanının içeriğini görüntüleyeceğiz. LINQ (INtegrated Sorgu Dili), 2007 yılında .NET 3.5 çerçeve yapısı ile ortaya çıkmıştır. Bu dil, .NET ve c.a.d dillerinin bir uzantısı olarak ortaya çıkmıştır; söz konusu dillerin bir parçası olarak entegre edilmiştir ve sözdizimi derleyici tarafından doğrulanmaktadır. Bu dil, veritabanı sorgulama dili olan SQL (Structured Query Language) ile benzerlikler gösteren bir sözdizimi kullanarak çeşitli koleksiyonlarda sorgu yapılmasına olanak tanır. LINQ'in farklı sürümleri bulunmaktadır:
- LINQ to Object, bellekteki koleksiyonlara sorgu göndermek için;
- LINQ to XML, XML'te sorgu yapmak için;
- LINQ'ten Entity'ye, veritabanlarına sorgu göndermek için;
LINQ'in varlığı, .NET dillerine yapılan çok sayıda uzantıya dayanmaktadır. Bu uzantılar, LINQ dışında da kullanılabilir. Bunları burada ayrıntılı olarak ele almayacağız; bunun yerine, okuyucunun LINQ hakkında kapsamlı bir açıklama bulabileceği iki kaynak sunacağız:
- LINQ in Action, Fabrice Marguerie, Steve Eichert, Jim Wooley, Manning Yayınları;
- LINQ pocket reference, Joseph ve Ben Albahari, O'Reilly yayınevi.
İlki okudum ve mükemmel buldum. İkincisini okumadım ancak aynı yazarların LINQ çıktığında yayınlanan "C# 3.0 in a nutshell" kitabını okudum. Bu kitabı, genellikle okuduğum kitapların ortalamasının çok üzerinde buldum. Görünüşe göre bu iki yazarın diğer kitapları da aynı seviyede. Ayrıca, Joseph Albahari tarafından yazılan LINQ öğrenme aracı olan LINQPad'i de kullanacağız.
Veritabanında bulunan varlıkları görüntüleyeceğiz. Bunun için, sınıflarına iki görüntüleme yöntemi ekleyeceğiz. [Medecin] varlığıyla başlayalım:
// bir doktor
public class Medecin
{
// veriler
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// doktorun zaman aralıkları
public ICollection<Creneau> Creneaux { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// imza
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return ToString();
}
// yardımcı program
private string dump(byte[] timestamp){
string str = "";
foreach (byte b in timestamp)
{
str += b;
}
return str;
}
}
- 27-30. satırlar: Sınıfın ToString yöntemi. Bu yöntemin 21. satırdaki koleksiyonu görüntülemediğine dikkat edin;
- 32-37. satırlar: Aynı işlevi gören ShortIdentity yöntemi.
Burada, önceki iki yöntemin etkisini değerlendirebilmek için Lazy ve Eager Loading kavramlarını açıklamamız gerekiyor. Bir varlığın başka bir varlığa bağımlı olabileceğini görmüştük. Bu bağımlılıklar iki türdür:
- yukarıdaki örnekte olduğu gibi, bir doktorun birden fazla randevu zaman aralığına bağlı olduğu "birden çoğa" bağımlılık;
- birden çoktan birine, aşağıdaki [Creneau] varlığında olduğu gibi; burada birden çok zaman aralığı aynı doktorla ilişkilidir;
public class Creneau
{
// veri
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
...
}
Bağımlılıklar, bağlı oldukları varlıklarla aynı anda yüklendiğinde buna Eager Loading denir. Aksi takdirde, buna "Lazy Loading" denir: bağımlılıklar yalnızca ilk kez referans alındıklarında yüklenir. Varsayılan olarak, EF 5, "Lazy Loading" kullanır: bağımlılıklar varlıkla aynı anda yüklenmez.
Yukarıdaki [ToString] yöntemimize bir göz atalım:
// doktorun muayene saatleri
public ICollection<Creneau> Creneaux { get; set; }
// imza
public override string ToString()
{
return String.Format("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return ToString();
}
[ToString] yöntemi, 2. satırdaki [Creneaux] bağımlılığını göstermez. Eğer göstermiş olsaydı, yürütülmeden önce doktorun tüm randevu aralıklarının yüklenmesini zorunlu kılacaktı. Bu maliyetli yüklemeyi önlemek için bağımlılık, varlığın imzasına dahil edilmemiştir. Genel olarak, her varlığa iki imza ekleyeceğiz:
- entiteyi ve varsa bağımlılıklarını tek tek görüntüleyecek bir ToString yöntemi. Az önce açıklandığı gibi, bu işlem bağımlılığın yüklenmesine neden olacaktır;
- hiçbir bağımlılığa atıfta bulunmayacak bir ShortIdentity yöntemi. Dolayısıyla, herhangi bir bağımlılık yüklenmeyecektir;
Diğer varlıkların görüntüleme yöntemleri şunlar olacaktır:
[Client] varlığı:
public class Client
{
// tarih
...
// müşterinin randevu numaraları
public ICollection<Rv> Rvs { get; set; }
// imza
public override string ToString()
{
return String.Format("Client[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return ToString();
}
}
- 9-12. satırlar: [ToString] yöntemi, 6. satırdaki bağımlılığı görüntülemez;
[Creneau] varlığı:
public class Creneau
{
...
[Required]
[Column("MEDECIN_ID")]
public int MedecinId { get; set; }
[Required]
[ForeignKey("MedecinId")]
public virtual Medecin Medecin { get; set; }
// zaman aralığı randevuları
public ICollection<Rv> Rvs { get; set; }
// imza
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Timestamp, MedecinId, dump(Timestamp));
}
}
- 16. satır: [ToString] yöntemi, 9. satırdaki bağımlılığa atıfta bulunur. Bu, onun yüklenmesini zorlayacaktır;
- 11. satır: [Rvs] bağımlılığı referanslanmamıştır. Bu bağımlılık yüklenmeyecektir;
- 21-22. satırlar: [ShortIdentity] yöntemi, 9. satırdaki [Medecin] referansına artık atıfta bulunmamaktadır. Dolayısıyla bu referans yüklenmeyecektir.
[Rv] varlığı:
public class Rv
{
// veri
...
[Column("CLIENT_ID")]
public int ClientId { get; set; }
[ForeignKey("ClientId")]
[Required]
public virtual Client Client { get; set; }
[Column("CRENEAU_ID")]
public int CreneauId { get; set; }
[ForeignKey("CreneauId")]
[Required]
public virtual Creneau Creneau { get; set; }
// imza
public override string ToString()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, Client, Creneau, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return String.Format("Rv[{0},{1},{2},{3},{4}]", Id, Jour, ClientId, CreneauId, dump(Timestamp));
}
}
- 17-20. satırlar: [ToString] yöntemi, 9. ve 14. satırlardaki bağımlılıklara atıfta bulunmaktadır. Bu, söz konusu bağımlılıkların yüklenmesini zorunlu kılacaktır;
- 17-20. satırlar: [ShortIdentity] yöntemi bunu önler ve dolayısıyla bağımlılıklar yüklenmez.
Sonuç olarak, varlıkların [ToString] yöntemlerine dikkat edilmelidir. Buna dikkat edilmezse, bir tablonun çok sayıda bağımlılığı olması durumunda, bu tablonun görüntülenmesi veritabanının yarısını yükleyebilir.
Bunu açıkladıktan sonra, aşağıdaki yeni [Dump.cs] kodunu yazıyoruz:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class Dump
{
static void Main(string[] args)
{
// veritabanı dökümü
using (var context = new RdvMedecinsContext())
{
// müşteriler
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// doktorlar
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// randevu aralıkları
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// randevular
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
[Client] varlıklarını görüntüleyen 17-21. satırları açıklayacağız. Verilen açıklama diğer varlıklar için de geçerli olacaktır.
// müşteriler
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
-
- satır: var anahtar sözcüğü C# 3.0 ile eklenmiştir. Bu anahtar sözcük, bir değişkenin tam türünü belirtme gerekliliğini ortadan kaldırır. Derleyici, değişkene atanan ifadenin türünden değişkenin türünü çıkarır;
-
- satır: clients değişkenine atanan ifade, bir LINQ to Entity sorgusudur. Burada, SQL dilinden LINQ'e aktarılan anahtar kelimeler görülmektedir. Burada kullanılan sözdizimi şöyledir:
from variable in DbSet select variable
LINQ için daha genel bir sözdizimi şöyledir:
from variable in collection select variable
Koleksiyon taranacak ve koleksiyondaki her bir öğe için değişken değerlendirilecektir. Bu işlem, yalnızca 3. satırdaki [clients] değişkeni, 4-7. satırlardaki for / each döngüsüyle taranırken gerçekleştirilir. Bu işlem gerçekleştirilene kadar, [clients] değişkeni yalnızca değerlendirilmemiş bir sorgudur;
- 4. satır: [clients] sorgusu döngüye alınır. Bu, sorgunun değerlendirilmesini zorunlu kılar. [CLIENTS] tablosundaki satırlar sırayla kalıcılık bağlamına aktarılır;
- 6. satır: [Client] varlığının [ToString] yöntemi görüntüleme için kullanılır. Herhangi bir bağımlılık yüklenmez;
Şimdi kodun sonraki satırlarına geçelim:
- 24-28. satırlar: [MEDECINS] tablosundaki satırlar kalıcılık bağlamına alınır ve görüntülenir. Herhangi bir bağımlılık yüklemesi yoktur;
- 31-35. satırlar: [CRENEAUX] tablosundaki satırlar kalıcılık bağlamına alınır ve görüntülenir. Bu varlığın [ToString] yönteminin, [Medecin] bağımlılığını görüntülediğini gördük. Ancak bu bağımlılık zaten yüklenmiş durumda. Dolayısıyla yeniden yükleme yapılmayacaktır;
- 38-42. satırlar: [RVS] tablosundaki satırlar kalıcılık bağlamına alınır ve görüntülenir. Bu varlığın [ToString] yönteminin, [Client] ve [Creneau] bağımlılıklarını görüntülediğini görmüştük. Ancak bunlar zaten yüklenmiş durumda. Dolayısıyla yeni bir yükleme yapılmayacaktır.
Görüntüleme sırasının önemsiz olmadığına dikkat edilmelidir. Eğer önce [Rv] varlıklarını görüntülemek isteseydik, bu varlığın [ToString] yöntemi, bu randevularla ilişkili [Client] ve [Creneau] varlıklarının yüklenmesine neden olacaktı. Diğerleri ise yüklenmezdi. Bunlar daha sonra başka bir görüntülemede yüklenirdi. Bu durum performans üzerinde bir etkiye sahiptir. Yukarıdaki kod, tüm varlıkların görüntülenebilmesi için dört adet SQL komutuna ihtiyaç duyar. Şimdi, önce randevu tablosu [RVS]'i işlediğimizi varsayalım. [RVS] tablosu için ilk bir SQL sorgusu gereklidir. Ardından, [Rv] varlığının [ToString] yöntemi, ilişkili [Client] ve [Creneau] varlıklarının yüklenmesine neden olacaktır. Her biri için bir SQL sorgusu gereklidir. N2 adet müşteri ve N3 adet zaman aralığı olduğunu ve tüm bu varlıkların [RVS] tablosunda referanslandığını varsayarsak, bu tablonun görüntülenmesi için 1 + N2 + N3 SQL sorgusu gerekecektir. Dolayısıyla, incelenen sürüme kıyasla performans daha düşüktür. [RVS] tablosunu bağımlılıklarıyla birlikte görüntülemek için tablolar arasında bir birleştirme (join) yapılması gerekir. Bunu LINQ ile gerçekleştirmek mümkündür. Buna bir örnekle daha sonra tekrar döneceğiz. Şimdilik, LINQ kodumuzun altında yatan SQL sorgularına dikkat etmemiz gerektiğini hatırlayalım.
Projeyi, bu yeni [1] ve [2] kodlarını çalıştıracak şekilde yapılandırıp çalıştırıyoruz:
![]() |
Konsol çıktısı şu şekildedir:
3.5.4. LINQ'in LINQPad ile öğrenilmesi
Yukarıda, veritabanındaki tabloların içeriğini görüntülemek için LINQ to Entity sorgularını kullandık. Joseph Albahari, LINQ'in farklı biçimlerini öğrenmeye yönelik bir program yazmıştır. Şimdi bunu sizlere sunuyoruz.
LINQPad, aşağıdaki URL adresinden indirilebilir: [http://www.linqpad.net/]. Kurulum tamamlandıktan sonra programı şu şekilde çalıştırıyoruz: [1]:
![]() |
Yeni başlayanlar, çok sayıda örnek içeren [Samples] ve [2] sekmelerindeki örneklerle LINQ'e giriş yapabilirler. [3] örneğini seçelim; bu örnek, başka bir pencerede ([4]) görüntülenir. Örneğin tam kodu şöyledir:
// Şimdi basit bir LINQ-to-objects sorgu ifadesi (noktalı virgül olmadığını unutmayın):
from word in "The quick brown fox jumps over the lazy dog".Split()
orderby word.Length
select word
// Bunu dilediğiniz gibi düzenleyebilirsiniz... (kimse izlemiyor!) Herhangi bir
// değişiklikleri ayrı bir dosyaya kaydetmeniz istenecektir.
//
// İpucu : Sorgunun bir bölümünü vurgulayıp F5 tuşuna basarak çalıştırabilirsiniz.
3-5. satırlar, LINQ to Object sorgusunun bir örneğidir. LINQ sorgusu şu sözdizimini izler:
from variable in collection orderby élément1 select élément2
- değişkeni, koleksiyonun geçerli öğesini belirtir. Örneğimizde bu koleksiyon, bölünmüş dizeden elde edilen kelime listesidir;
- koleksiyon, orderby parametresi élément1'e göre sıralanır. Örneğimizde, kelime koleksiyonu uzunluklarına göre sıralanacaktır;
- select anahtar sözcüğü, koleksiyonun geçerli öğesi variable'ten neyin çıkarılacağını belirtir. Örneğimizde bu, kelime olacaktır.
Şu LINQ sorgusunu çalıştıralım:
![]() |
- [1]'te: LINQ ifadesi, [F5] tarafından veya çalıştırma düğmesi aracılığıyla yürütülür;
- [2]'te: görüntüleme. Kelimeler uzunluklarına göre sıralanır. Bu basit örnek, LINQ'in gücünü gösterir;
- [3]'te, özellikle "LINQ in action" kitabındaki örnekler de dahil olmak üzere başka örnekler indirilebilir;
![]() |
- 5'te, kitaptan bir örnek seçiyoruz;
string[] words = { "hello", "wonderful", "linq", "beautiful", "world" };
// Kelimeleri uzunluklarına göre gruplandır
var groups =
from word in words
orderby word ascending
group word by word.Length into lengthGroups
orderby lengthGroups.Key descending
select new { Length = lengthGroups.Key, Words = lengthGroups };
// Her grubu yazdır
foreach (var group in groups)
{
Console.WriteLine("Words of length " + group.Length);
foreach (string word in group.Words)
Console.WriteLine(" " + word);
}
- 4. satır: yeni anahtar kelimeler içeren yeni bir sorgu LINQ;
- 5. satır: istenen koleksiyon, 1. satırdaki kelime tablosudur;
- 6. satır: koleksiyon, kelimelerin alfabetik sırasına göre sıralanır;
- 7. satır: koleksiyon, (anahtar kelime into) yeni bir koleksiyon olan lengthGroups'te gruplandırılır. lengthGroups.Key, gruplandırma faktörünü (anahtar kelime by) temsil eder; burada bu faktör, kelimelerin uzunluğudur. lengthGroups, aynı gruplama faktörüne, yani aynı uzunluğa sahip kelimeleri bir araya getirir;
- satır 8: lengthGroups koleksiyonu, gruplama anahtarına göre azalan sırada, yani burada kelimelerin boyutu azalan sırada sıralanır;
- 9. satır: Bu koleksiyondan, iki alana sahip yeni nesneler (anonim sınıflar) oluşturulur:
- Length: kelimelerin uzunluğu,
- Words: bu uzunlukta olan kelimeler;
Burada, özellikle 4. satırdaki var anahtar sözcüğünün önemini görüyoruz. 9. satırda isimsiz bir sınıf kullandığımız için, groups değişkeninin türünü belirleyemiyoruz. Derleyici ise isimsiz sınıfa dahili bir ad verecek ve groups değişkenini bu adla türlendirecektir. Daha sonra groups değişkeninin doğru kullanılıp kullanılmadığını belirleyebilecektir
- 12. satır: 4. satırdaki sorgu taranır. Sorgu ancak bu anda değerlendirilir. Hatırlayalım ki, sorgunun yürütülmesi 9. satırda belirtilen bir nesne koleksiyonu üretecektir;
- 14. satır: geçerli öğenin Length özelliğini, yani kelime uzunluğunu görüntüler;
- 15-17. satırlar: Words özelliğinin koleksiyonundaki her bir öğe, yani daha önce görüntülenen uzunluğa sahip tüm kelimeler görüntülenir.
Bu sorguyu çalıştırdığımızda, LINQPad'te şu sonucu elde ederiz:
![]() |
[LINQ to Object] sorgularına ilişkin birkaç örneği gördük; şimdi de veritabanlarına sorgu yapmamızı sağlayacak [LINQ to Entity] sorgularına bakalım. Öncelikle, oluşturduğumuz ve verilerle doldurduğumuz SQL Server veritabanına bağlanacağız:
![]() |
- [1]'te bir veritabanı bağlantısı ekliyoruz;
- [2]'te, veri kaynağına erişim yöntemlerini belirleyeceğiz. SQL Server veritabanına erişmek için [LINQPad Driver]'i kullanacağız;
- [3]'te, bir assembly .exe veya .dll dosyasında tanımlanmış bir [DbContext] kalıcılık bağlamını da alabilirsiniz (seçenek 3). Ne yazık ki, bugünkü tarih itibarıyla (8 Ekim 2012), Entity Framework 5 desteklenmemektedir;
- [4]'te, SQL Server dışındaki diğer SGBD sürücülerini indirebilirsiniz;
- 5'te, SGBD, MySQL ve Oracle için sürücü indirilecektir;
![]() |
- [6]'te indirilen sürücü;
- [7]'te, bir SQL Sunucusu veritabanına bağlanacağız;
![]() |
- [8]'te, veritabanı ad sunucusunda (yerel) bulunur;
- [9]'te, sa / sqlserver2012 kimlik bilgileriyle bağlanıyoruz;
- [10]'te, daha önce oluşturduğumuz [rdvmedecins-ef] veritabanına bağlanılır;
- [11]'te, bağlantıyı test edebiliriz;
- [12]'te sihirbazı sonlandırıyoruz;
- [13]'te, bağlantı LINQPad'te görünür.
Varlıklar, [rdvmedecins-ef] tablosundan oluşturulmuştur. Bunlar şunlardır:
![]() |
- [1]'te, [CLIENTS], [Client] varlıklarının tamamını temsil eder. Her varlığın şu özellikleri vardır:
- özellikler (ID, TITRE, NOM, PRENOM, TIMESTAMP),
- bir "1'e çoklu" ilişki olan [CLIENTRVS];
- burada [2] ve [CRENEAUXes], [Creneau] varlıklarının tümünü temsil eder. Her varlığın şu özellikleri vardır:
- özellikleri (ID, HDEBUT, MDEBUT, HFIN, MFIN, MEDECIN_ID, TIMESTAMP),
- 1'e çoklu ilişki [CRENEAURVS],
- birden çoktan 1'e ilişki [MEDECIN];
- [3]'te, [MEDECINS] varlığı, [Medecin] varlıklarının tümünü temsil eder. Her varlığın şunları vardır:
- özellikler (ID, TITRE, NOM, PRENOM, TIMESTAMP),
- bir “1’e çok” ilişkisi olan [MEDECINCRENEAUXes];
- [4]'te, [RVS] varlığı, [Rv] varlıklarının tümünü temsil eder. Her varlığın şu özellikleri vardır:
- özellikler (ID, JOUR, CLIET_ID, CRENEAU_ID, TIMESTAMP),
- birden çok-bir ilişkisi olan [CLIENT],
- birden çok-bir ilişkisi [CRENEAU].
Yukarıdaki özellik adlarının şimdiye kadar kullandıklarımızdan farklı olduğu dikkatinizi çekecektir. Bu çok da önemli değil. Biz sadece veritabanında sorgu yapmanın temel ilkelerini öğrenmek istiyoruz.
Bu varlık tabanında nasıl sorgu yapabileceğimize bir göz atalım. Örneğin, doktorların listesini TITRE ve NOM değerlerine göre sıralanmış olarak istiyoruz:
![]() |
- [1]'te yeni bir sorgu oluşturuyoruz;
- [2]'te, sorgu metni;
![]() |
- [3]'te, sorgunun sonucu;
- [4]'te, lambda ifadeleri içeren aynı sorgu. Lambda ifadeleri içeren bir sorgu, metin tabanlı bir sorguya göre daha az okunaklıdır ve bunlardan vazgeçmek isteyebilirsiniz. Ancak, metin tabanlı sorguların izin vermediği bazı işlemleri mümkün kıldıkları için bazen vazgeçilmezdirler. Bir lambda ifadesi, a giriş parametresi ve b çıkış parametresine sahip bir işlevi, a=>b biçiminde ifade eder. Yukarıdaki OrderBy yöntemi, tek parametre olarak bir lambda fonksiyonu kabul eder. Bu fonksiyon, koleksiyonun hangi parametreye göre sıralanması gerektiğini yönteme sağlar. Dolayısıyla MEDECINS.OrderBy(m=>m.TITRE), unvanlara göre sıralanmış doktor listesidir. Bu komut, bir koleksiyon üzerinde bir boru hattı (pipeline) olarak okunmalıdır. Doktorlar koleksiyonu, OrderBy yöntemine girdi olarak verilir. Bu yöntem, [Medecin] varlıklarını tek tek işleyecektir. m=>m.TITRE lambda ifadesinde, m lambda fonksiyonunun girdisini temsil eder. Buna istenilen herhangi bir isim verilebilir. Burada, lambda fonksiyonunun girdisi bir [Medecin] varlığı olacaktır. m=>m.TITRE fonksiyonu şu şekilde okunur: Girişimi (bir [Medecin] varlığı) m olarak adlandırırsam, çıktım m.TITRE, yani doktorun unvanı olur. MEDECINS.OrderBy(m=>m.TITRE) ise unvanlara göre sıralanmış doktor koleksiyonudur. Bu yeni koleksiyon başka bir yönteme girdi olarak kullanılabilir; örnekte bu, ThenBy yöntemidir. Bu yöntem de aynı prensiple çalışır. Koleksiyonun sıralanması için ek parametreler belirtmeye yarar.
Genellikle yazdığımız metin koduna eşdeğer lambda kodunu okumak, bunu öğrenmenin iyi bir yoludur;
![]() |
- 5'te, veritabanına gönderilen SQL emri. Burada da bu kodu dikkatlice okuyacağız. Bu kod, bir LINQ sorgusunun gerçek maliyetini değerlendirmemizi sağlar.
Aşağıda, LINQ sorgusuna ilişkin birkaç örnek sunuyoruz. Her seferinde, görüntülenen sonuçları ve bunlara karşılık gelen lambda kodları ile SQL kodlarını göstereceğiz. Bu istekleri anlamak için, varlıkları birbirine bağlayan çok-tek ilişkilerini hatırlamak gerekir. Varlıklardan birinden diğerine geçiş, bu ilişkiler aracılığıyla gerçekleşir. Bunlara navigasyon özellikleri denir.
![]() |
// unvanı "Bay" olan müşteriler, isimlerine göre azalan sırada sıralanmış
Sonuçlar:
![]() |
LINQ | |
Lambda | |
SQL | |
// ilgili doktorun tüm randevu saatleri
(Kısmi) sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// ilgili hasta ve doktorun tüm randevuları
Sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// randevusu olmayan doktorlar
Sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
Bu istek için LINQ sorgusu mevcut değildir. Lambda ifadeleri kullanılması gerekir. Bu ifade şu şekilde okunur: doktorlar koleksiyonunu (MEDECINS) alıyorum ve randevular koleksiyonunda (RVS) bu doktorla (m) bir randevu (rv) bulamadığım doktorları (m) tutuyorum (Where).
// Bayan Pélissier’in randevu saatleri
(Kısmi) sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// 08/10/2012 tarihinde Bayan Pélissier'in randevu sayısı
Sonuçlar:
![]() |
LINQ | |
Lambda | |
SQL | |
// 08/10/2012 tarihinde Bayan Pélissier ile randevu alan müşterilerin listesi
Sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
// doktor başına zaman dilimi sayısı
Sonuçlar:
![]() |
LINQ | |
Lambda | ![]() |
SQL | |
3.5.5. Kalıcılık bağlamına bağlı bir varlığın değiştirilmesi
Kalıcılık bağlamında aşağıdaki işlemleri inceledik:
- bağlama bir öğe ekleme ([dbContext].[DbSet].Add);
- bağlamdan bir öğeyi silme ([dbContext].[DbSet].Remove);
- LINQ sorgularıyla bir bağlamı sorgulamak.
Bağlamı veritabanıyla senkronize etmek istediğimizde, [dbContext].SaveChanges() komutunu yazıyoruz.
![]() | ![]() |
[ModifyAttachedEntity] kodu, bağlama eklenmiş bir varlığın değiştirilmesini gösterir:
using System;
using System.Data;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyAttachedEntity
{
static void Main(string[] args)
{
Client client1, client2, client3;
// 1. bağlam
using (var context = new RdvMedecinsContext())
{
// Mevcut veritabanını boşalt
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// bir müşteri ekleniyor
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
// takip
Console.WriteLine("client1--avant");
Console.WriteLine(client1);
// bağlamı kaydet
context.SaveChanges();
// takip
Console.WriteLine("client1--après");
Console.WriteLine(client1);
}
// 2. bağlam
using (var context = new RdvMedecinsContext())
{
// client1'i client2'den alınıyor
client2 = context.Clients.Find(client1.Id);
// takip
Console.WriteLine("client2");
Console.WriteLine(client2);
// müşteri2'yi değiştiriyoruz
client2.Nom = "yy";
// bağlamın kaydedilmesi
context.SaveChanges();
}
// 3. bağlam
using (var context = new RdvMedecinsContext())
{
// client2'yi client3'ten alıyoruz
client3 = context.Clients.Find(client2.Id);
// takip
Console.WriteLine("client3");
Console.WriteLine(client3);
}
}
}
}
- 15. satır: uygulama bağlamı açılır;
- 18-25. satırlar: bağlam boşaltılır. Tam olarak, tüm varlıklar veritabanından bağlama aktarılır ve ardından "silinmiş" durumuna geçer. Bu aşamada veritabanında herhangi bir değişiklik olmadığına dikkat edilmelidir. Bağlam veritabanıyla senkronize edilmediği sürece, veritabanı değişmez. Hatırlanacağı üzere, [Medecin] ve [Client] varlıklarının silinmesi, zincirleme silme mekanizması sayesinde veritabanını boşaltmak için yeterlidir;
- 27-28. satırlar: veritabanına yeni bir müşteri eklenir;
- satır 30-31: veritabanına kaydedilmeden önce müşteri görüntülenir;
- satır 33: bağlam, veritabanıyla senkronize edilir. "Silindi" durumuna işaretlenen varlıklar, SQL DELETE işlemine tabi tutulacak; eklenen varlık ise SQL INSERT işlemine tabi tutulur;
- 35-36. satırlar: veritabanıyla senkronizasyonun ardından müşteri görüntülenir;
Konsolda elde edilen sonuç şöyledir:
Aşağıdaki noktalara dikkat edilmelidir:
- veritabanıyla senkronizasyon öncesinde, müşterinin ne bir birincil anahtarı ne de timestamp,
- senkronizasyondan sonra ise bunlara sahiptir. Burada, birincil anahtarın SQL Sunucusu tarafından oluşturulacak şekilde yapılandırıldığını hatırlatmak isteriz. Aynı şekilde, bu SGBD zaman damgasını otomatik olarak oluşturur;
- 37. satır: kalıcılık bağlamı kapatılır. İçinde bulunan varlıklar "bağlantısız" hale gelir. Bunlar nesne olarak var olurlar, ancak bir kalıcılık bağlamına bağlı varlıklar olarak var olmazlar;
- 39. satır: Yeni ve boş bir bağlam başlatılır;
- satır 42: müşteri, birincil anahtarı aracılığıyla doğrudan veritabanından alınır. Ardından bağlam içine aktarılır. Bulunamazsa, Find yöntemi null işaretçisini döndürür;
- 48-49. satırlar: müşteri görüntülenir;
Bu, aşağıdaki sonucu verir:
- 47. satır: Değiştirilir;
- satır 49: bağlam, veritabanıyla senkronize edilir. EF, bağlama aktarıldıklarından bu yana bağlamdaki bazı öğelerin değiştirildiğini algılayacaktır. Bu öğeler için, veritabanında SQL ve UPDATE komutlarını oluşturacaktır. Dolayısıyla burada senkronizasyon, tek bir UPDATE komutundan ibaret olacaktır;
- 50. satır: İkinci bağlam kapatılır. Bağlama bağlı olan client2 varlığı artık bağlamdan ayrılır;
- 52. satır: Üçüncü bir boş bağlam açılır;
- satır 55: Veritabanındaki tek müşteriyi tekrar bu bağlama getiriyoruz. Önceki bağlamda bu müşteri üzerinde yapılan değişikliğin veritabanına yansıtılıp yansıtılmadığını görmek istiyoruz;
- 57-58. satırlar: müşteri görüntülenir. Sonuç şu şekildedir:
Müşterinin adı veritabanında başarıyla değiştirilmiştir. İlginç bir şekilde, timestamp değerinin güncellendiği görülmektedir.
- satır 59: bağlam kapatılır. Bu arada, önceki iki durumun aksine, bağlamda herhangi bir değişiklik yapılmadığı için (SaveChanges) bağlamı veritabanıyla senkronize etmeye gerek kalmamıştır.
3.5.6. Ayrılmış varlıkların yönetimi
Vaka çalışmasındaki gibi bir uygulamanın katmanlı mimarisine geri dönelim:
![]() |
[DAO] katmanı, verilere erişmek için ORM ve EF5'i kullanır. Bu katmanın temel yapı taşlarına sahibiz. Her yöntem bir kalıcılık bağlamı açacak, üzerinde gerekli işlemleri (ekleme, değiştirme, silme, sorgulama) gerçekleştirecek ve ardından kapatacaktır. [DAO] katmanı tarafından yönetilen varlıklar, ASP.NET web katmanına kadar yükselir. Bu katmanda, varlıklar kalıcılık bağlamı dışında kaldıkları için bağsızdır. Web katmanında bir kullanıcı bu varlıkları değiştirebilir (ekleme, değiştirme, silme). [DAO] katmanına geri döndüklerinde, bu varlıklar hala ayrılmış durumdadır. Ancak [DAO] katmanı, kullanıcı tarafından yapılan değişiklikleri veritabanına yansıtmak zorunda kalacaktır. Dolayısıyla, ayrılmış varlıklarla çalışmak zorunda kalacaktır. Olası üç durumu inceleyelim:
Ayrılmış bir varlık ekleme
Bu, ekleme işlemi için normal durumdur. Ayrılmış varlığı bağlama eklemek (Add) yeterlidir; bunun için varlığın birincil anahtarının null ile aynı olduğundan emin olunmalıdır.
Ayrı bir varlığı düzenleme
Aşağıdaki kodu kullanabilirsiniz:
- [DbContext].Entry(ayrılmış-varlık) yöntemi, varlığı bağlama ekleyecektir;
- bu varlığın durumu, SQL UPDATE emrine tabi tutulması için "değiştirildi" olarak ayarlanır.
Ayrılmış bir varlığı silme
Aşağıdaki kod kullanılabilir:
- 1. satır: Ayrılmış varlıkla aynı birincil anahtara sahip varlığı bağlama eklenir;
- 2. satır: Bu varlığı silin:
Bunun için veritabanında bir SELECT komutunun ardından bir DELETE komutu gerektiği unutulmamalıdır; oysa normalde sadece DELETE komutu yeterlidir. Ayrıca, ayrılmış bir varlığın değiştirilmesi örneğini takip ederek şunu yazabilirsiniz:
Veritabanında gerçekleştirilen SQL işlemlerine ilişkin günlükleri etkinleştiremediğim için, hangisinin diğerine göre daha tavsiye edilebilir bir yöntem olduğunu bilmiyorum.
İşte bir örnek:
![]() | ![]() |
[ModifyDetachedEntities] programının kodu şöyledir:
using System;
using System.Data;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
class ModifyDetachedEntities
{
static void Main(string[] args)
{
Client client1;
// mevcut veritabanı boşaltılıyor
Erase();
// bir müşteri ekleniyor
using (var context = new RdvMedecinsContext())
{
// müşteri oluşturma
client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
// müşteriyi bağlama ekleniyor
context.Clients.Add(client1);
// bağlam kaydediliyor
context.SaveChanges();
}
// veritabanını görüntüleme
Dump("1-----------------------------");
// müşteri1 bağlamda yok - değiştiriliyor
client1.Nom = "y";
// yeni bağlam
using (var context = new RdvMedecinsContext())
{
// burada boş bir bağlam var
// client1'i değiştirilmiş durumda bağlama yerleştiriyoruz
context.Entry(client1).State = EntityState.Modified;
// bağlam kaydediliyor
context.SaveChanges();
}
// temel görüntüleme
Dump("2-----------------------------");
// bağlam dışındaki varlığın silinmesi
using (var context = new RdvMedecinsContext())
{
// burada yeni bir boş bağlam var
// client1'i bağlamda silinmiş durumuna alıyoruz
context.Entry(client1).State = EntityState.Deleted;
// bağlam kaydediliyor
context.SaveChanges();
}
// veritabanını görüntüleme
Dump("3-----------------------------");
}
static void Erase()
{
// veritabanını boşaltıyoruz
using (var context = new RdvMedecinsContext())
{
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// bağlam kaydedilir
context.SaveChanges();
}
}
static void Dump(string str)
{
Console.WriteLine(str);
// veritabanını görüntüler
using (var context = new RdvMedecinsContext())
{
foreach (var rv in context.Rvs)
{
Console.WriteLine(rv);
}
foreach (var creneau in context.Creneaux)
{
Console.WriteLine(creneau);
}
foreach (var client in context.Clients)
{
Console.WriteLine(client);
}
foreach (var medecin in context.Medecins)
{
Console.WriteLine(medecin);
}
}
}
}
}
- 15. satır: veritabanı silinir;
- 17-25. satırlar: Veritabanına bir müşteri eklenir;
- 27. satır: veritabanının içeriğini görüntüler;
- 25. satırdan sonra, kalıcılık bağlamı artık mevcut değildir. Dolayısıyla, bağlı hiçbir varlık kalmamıştır. client1 varlığı "bağlantısı kesilmiş" durumuna geçmiştir;
- 29. satır: ayrılmış varlığın adı değiştirilir;
- 31. satır: yeni bir boş bağlam açılır;
- 35. satır: Ayrılmış client1 varlığı, "değiştirilmiş" durumuyla bağlama eklenir;
- satır 37: bağlam, veritabanıyla senkronize edilir;
- satır 38: bağlam kapatılır;
- satır 40: veritabanı görüntülenir;
Müşterinin adı veritabanında başarıyla değiştirilmiştir. timestamp'in güncellendiğine dikkat edilmelidir;
- satır 42: yeni bir boş bağlam açıldı;
- satır 46: Ayrılmış varlık client1, bağlamda "silinmiş" durumunda yerleştirilir;
- 48. satır: bağlam, veritabanıyla senkronize edilir;
- satır 49: bağlam kapatılır;
- satır 51: veritabanı görüntülenir;
Varlık, veritabanından başarıyla silinmiştir.
Şimdi, bir varlığın bağımlılıklarını yüklemenin iki yöntemini inceleyeceğiz: Lazy ve Eager Loading.
3.5.7. Lazy ve Eager Loading
Dört varlığımızdan birinin çok-bir bağımlılık şemasını tekrar ele alalım:
![]() |
Yukarıda, [Creneau] varlığı, [Creneau.Medecin] varlık üzerinden [Medecin] varlığına giden bir navigasyon özelliğine sahiptir. Buna bağımlılık denir. Birden çok varlıkla olan bağımlılıkların da olduğunu görmüştük. Aşağıda açıklanacak ilke, bunlar için de geçerlidir.
Varsayılan olarak, EF 5, Gecikmeli Yükleme (Lazy Loading) modundadır: veritabanından bir varlığı kalıcılık bağlamına getirdiğinde, bağımlılıklarını da getirmez. Bu bağımlılıklar, ilk kez kullanıldıklarında getirilecektir. Bu, mantıklı bir önlemdir. Aksi takdirde, randevuları bağlama aktarmak, yukarıdaki bağımlılıklara göre şunları da beraberinde getirirdi:
- randevularla ilişkili [Creneau] varlıklarını;
- bu zaman aralıklarıyla ilişkili [Medecin] varlıkları;
- randevularla ilişkili [Clients] varlıklarını.
Ancak bazen bir varlık ve onun bağımlılıklarına ihtiyaç duyulur. Her iki yükleme yöntemini de örneklerle açıklayacağız.
![]() | ![]() |
[LazyEagerLoading] kodunun içeriği şöyledir:
using RdvMedecins.Entites;
using RdvMedecins.Models;
using System;
using System.Linq;
namespace RdvMedecins_01
{
class LazyEagerLoading
{
// varlıklar
static Medecin[] medecins;
static Client[] clients;
static Creneau[] creneaux;
static void Main(string[] args)
{
// veritabanını başlat
InitBase();
Console.WriteLine("Initialisation terminée");
// önceden yükleme
Creneau creneau;
int idCreneau = (int)creneaux[0].Id;
using (var context = new RdvMedecinsContext())
{
// 0 numaralı zaman aralığı
creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
// bağımlılık görüntüleme
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 1 suivante s'est produite : {0}", e);
}
// gecikmeli yükleme - varsayılan mod
using (var context = new RdvMedecinsContext())
{
// 0 numaralı zaman aralığı
creneau = context.Creneaux.Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
// bağımlılık gösterimi
try
{
Console.WriteLine("Médecin={0}", creneau.Medecin);
}
catch (Exception e)
{
Console.WriteLine("L'erreur 2 suivante s'est produite : {0}", e);
}
}
static void InitBase()
{
// veritabanı başlatılıyor
using (var context = new RdvMedecinsContext())
{
// mevcut veritabanı boşaltılıyor
...
// veritabanı başlatılıyor
// müşteriler
clients = new Client[] {
new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
};
...
// Randevular
context.Rvs.Add(new Rv { Jour = new System.DateTime(2012, 10, 8), Client = clients[0], Creneau = creneaux[0] });
// kalıcılık bağlamı kaydediliyor
context.SaveChanges();
}
}
}
}
- 18. satır: Şimdiye kadar kullanılan bilinen bir veritabanından başlıyoruz. Bu işlemden sonra, 11-13. satırlardaki tablolar ayrı varlıklarla doldurulur;
- 21-22. satırlar: ilk zaman aralığı ve ilgili hekim ele alınır;
- 23. satır: yeni bağlam;
- 26. satır: zaman aralığını bağımlılığıyla birlikte bağlama yerleştiriyoruz (eager loading). Bu varsayılan mod olmadığı için, bu bağımlılığı açıkça talep etmek gerekir. Bunu sağlayan yöntem Include'dur. Bu yöntemin parametresi, bağlama getirilen varlıktaki bağımlılığın adıdır. Varlığı bağlama getiren sorgu, lambda ifadeleri kullanır. Single yöntemi, tek bir varlığı getirmek için bir koşul belirtmeye olanak tanır. Burada, veritabanında 0 numaralı zaman diliminin birincil anahtarına sahip [Creneau] varlığını arıyoruz;
- 27. satır: Geri getirilen varlık görüntülenir. Varlıklarda kullanılan iki yazma yöntemini hatırlayalım:
// imza
public override string ToString()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5},{6}]", Id, Hdebut, Mdebut, Hfin, Mfin, Medecin, dump(Timestamp));
}
// kısa imza
public string ShortIdentity()
{
return String.Format("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut, Hfin, Mfin, MedecinId, dump(Timestamp));
}
- 2-5. satırlar: [ToString] yöntemi, [Medecin] bağımlılığını görüntüler. Bu bağımlılık henüz bağlamda yoksa, veritabanında aranarak bağlama eklenir;
- 8-11. satırlar: [ShortIdentity] yöntemi, [Medecin] bağımlılığını görüntülemez. Dolayısıyla, bağlamda bulunmuyorsa veritabanında aranmayacaktır;
Bu aşamada konsol çıktısı şu şekildedir:
- 28. satır: bağlam kapatılır;
- 30-37. satırlar: Varlığın [Medecin] bağımlılığını yazmaya çalışılıyor. Lazy Loading işleyişini hatırlayalım: Bir bağımlılık, mevcut değilse ilk kullanımında yüklenir. Burada normalde mevcut olması gerekir. Görüntü şu şekildedir:
- 39-44. satırlar: Yeni bir bağlam kapsamında, 0 numaralı zaman aralığı veritabanında yeniden aranır ve bağlama eklenir. Burada, [Medecin] bağımlılığı açıkça talep edilmez. Dolayısıyla eklenmeyecektir (Gecikmeli Yükleme);
- 43. satır: zaman aralığının kısa kimliği şu şekilde görüntülenir:
Burada, varlığı görüntülemek için ToString yerine ShortIdentity'in kullanılması önemlidir. ToString kullanılırsa, [Medecin] bağımlılığı görüntülenecek ve bunun için veritabanında aranacaktır. Oysa biz bunu istemiyoruz.
- 44. satır: bağlam kapatılır;
- 46-53. satırlar: Varlığın bağımlılığını görüntülemeye çalışıyoruz. Bunu bağlam dışında yapmak önemlidir, aksi takdirde veritabanında aranır ve bulunur. Burada bağlam dışındayız. [Creneau] varlığı bağlanmamıştır ve bağımlılığı olan [Medecin] yoktur (Gecikmeli Yükleme). Ne olacak? Ekranda şunlar görüntülenir:
EF, eksik olan [Medecin] bağımlılığını buldu. Bunu yüklemek istedi ancak bağlam kapalı olduğu için bu işlem artık mümkün değildi. Bu [System.ObjectDisposedException] istisnasını aklımızda tutalım, çünkü bu istisna, açık bir bağlam dışında bir bağımlılığın yüklenmesi durumunda ortaya çıkar.
Şimdi varlıklarına erişim rekabetini inceleyelim.
3.5.8. Varlıklara erişim rekabeti
[Client] varlığının tanımına geri dönelim:
public class Client
{
// veri
[Key]
[Column("ID")]
public int? Id { get; set; }
[Required]
[MaxLength(5)]
[Column("TITRE")]
public string Titre { get; set; }
[Required]
[MaxLength(30)]
[Column("NOM")]
public string Nom { get; set; }
[Required]
[MaxLength(30)]
[Column("PRENOM")]
public string Prenom { get; set; }
// müşterinin randevuları
public ICollection<Rv> Rvs { get; set; }
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
// imza
...
}
23. satırdaki [Timestamp] alanına odaklanacağız. Bu alanın değerinin SGBD tarafından üretildiğini biliyoruz. Ayrıca, 22. satırdaki [Timestamp] anotasyonunun, EF 5'in anotasyonlu alanı kullanarak varlıklara erişimdeki eşzamanlılıkları yönetmesini sağladığını da belirtmiştik. Erişim eşzamanlılığı yönetiminin ne olduğunu hatırlayalım:
- bir P1 süreci, T1 zamanında [MEDECINS] tablosundaki L satırını okur. Bu satırın timestamp TS1 değerleri vardır;
- bir P2 süreci, [MEDECINS] tablosundaki aynı L satırını T2 zamanında okur. Satırın durumu timestamp TS1 şeklindedir, çünkü P1 işlemi henüz yaptığı değişikliği onaylamamıştır;
- P1 süreci, L satırındaki değişikliğini onaylar. Bunun üzerine L satırındaki timestamp değeri TS2 olarak değişir;
- P2 işlemi, L satırındaki değişikliğini onaylar.ORM, P2 sürecinin L satırındaki timestamp ve TS1 değerlerinin, veritabanında bulunan timestamp ve TS2'ten farklıdır.
Buna “eşzamanlı erişimlerin iyimser yönetimi” denir. EF 5 ile, bu rolü üstlenen bir alanın [Timestamp] veya [ConcurrencyCheck] özniteliklerinden birine sahip olması gerekir. SQL sunucusunun türü [timestamp]'tir. Bu türe sahip bir sütunun değeri, bir satırın eklenmesi veya değiştirilmesi durumunda SQL sunucusu tarafından otomatik olarak oluşturulur. Bu tür bir sütun, erişim eşzamanlılığını yönetmek için kullanılabilir.
Bu erişim rekabetini, veritabanındaki aynı [Client] varlığını aynı anda değiştirecek iki iş parçacığı ile göstereceğiz. Proje şu şekilde gelişir:
![]() | ![]() |
[AccèsConcurrents] programının kodu şöyledir:
using System;
using System.Data;
using System.Linq;
using System.Threading;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// iş parçacıklarıyla paylaşılan nesne
class Data
{
public int Duree { get; set; }
public string Nom { get; set; }
public Client Client { get; set; }
}
// test programı
class AccèsConcurrents
{
static void Main(string[] args)
{
Client client1;
using (var context = new RdvMedecinsContext())
{
// ana iş parçacığı
Thread.CurrentThread.Name = "main";
// mevcut veritabanı boşaltılıyor
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
// bir istemci ekleniyor
client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
context.Clients.Add(client1);
// izleme
Console.WriteLine("{0} client1--avant sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
// yedekleme
context.SaveChanges();
// takip
Console.WriteLine("{0} client1--après sauvegarde du contexte", Thread.CurrentThread.Name);
Console.WriteLine(client1.ShortIdentity());
}
// client1'i iki iş parçacığıyla değiştireceğiz
// iş parçacığı t1
Thread t1 = new Thread(Modifie);
t1.Name = "t1";
t1.Start(new Data { Duree = 5000, Nom = "yy", Client = client1 });
// t2 iş parçacığı
Thread t2 = new Thread(Modifie);
t2.Name = "t2";
t2.Start(new Data { Duree = 5000, Nom = "zz", Client = client1 });
// 2 iş parçacığının bitmesini bekliyoruz
Console.WriteLine("Thread {0} -- début attente fin des deux threads", Thread.CurrentThread.Name);
t1.Join();
t2.Join();
Console.WriteLine("Thread {0} -- fin attente fin des deux threads", Thread.CurrentThread.Name);
// değişikliği görüntülüyoruz – sadece biri başarılı olmuş olmalı
using (var context = new RdvMedecinsContext())
{
// client1'i client2'den alıyoruz
Client client2 = context.Clients.Find(client1.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
}
}
// iş parçacığı
static void Modifie(object infos)
{
...
}
- 26. satır: boş bir bağlam başlatılır;
- 29. satır: daha sonra oluşturulacak iki iş parçacığından ayırt edilebilmesi için mevcut iş parçacığına bir ad verilir;
- satır 31-38: [Medecin] ve [Client] varlıklarına "silinmiş" durumu atanır;
- 40-41. satırlar: bağlama bir istemci eklenir;
- satır 43-44: bağlam senkronizasyonundan önce bu müşteri görüntülenir;
- satır 46: bağlamın veritabanıyla senkronizasyonu: "silinmiş" durumundaki varlıklar veritabanından silinecektir. Bağlama eklenen [Client] varlığı veritabanına eklenecektir. Bu, veritabanındaki tek öğe olacaktır;
- 47-49. satırlar: bağlam senkronizasyonundan sonra müşteri görüntülenir. Bu aşamada ekran görüntüleri şöyledir:
Bağlamın senkronizasyonundan sonra, müşterinin bir birincil anahtarı ve bir timestamp'i olduğu görülecektir;
- satır 50: bağlam kapatılır;
- satır 53: t1 iş parçacığı, 84. satırdaki [Modifie] yöntemine atanır. Bu, iş parçacığı başlatıldığında [Modifie] yöntemini çalıştıracağı anlamına gelir;
- 54. satır: t1 iş parçacığına bir ad verilir;
- satır 55: t1 iş parçacığı başlatılır. İş parçacığına, 12-17. satırlarda tanımlanan [Data] yapısı biçiminde parametreler aktarılır:
- Süre: iş parçacığı, yürütmesini tamamlamadan önce Durée saniye önce duracaktır,
- Müşteri: veritabanında güncellenecek müşteriye ait bir referans,
- Ad: bu müşteriye verilecek ad;
- 57-59. satırlar: İkinci iş parçacığı için de aynı işlem yapılır. Sonuç olarak, iki iş parçacığı da veritabanında aynı müşterinin adını değiştirmeye çalışacaktır;
- 60-63. satırlar: iki iş parçacığı başlatıldıktan sonra, ana iş parçacığı bunların yürütülmesinin bitmesini bekler;
- satır 62: t1 iş parçacığının bitmesini bekleme;
- satır 63: t2 iş parçacığının bitmesini bekleme;
- satır 64: iki iş parçacığının hangi sırayla biteceği bilinmiyor. Kesin olan şey, 64. satırda her ikisinin de bitmiş olmasıdır;
- satır 66-72: yeni bir bağlamda, veritabanından müşteriyi arayarak durumunu kontrol ediyoruz.
Şimdi t1 ve t2 adlı iki iş parçacığının ne yaptıklarına bakalım. Aşağıdaki [Modifie] yöntemini yürütürler:
static void Modifie(object infos)
{
// parametre alınır
Data data = (Data)infos;
try
{
using (var context = new RdvMedecinsContext())
{
Console.WriteLine("Début Thread {0}", Thread.CurrentThread.Name);
// client1'i client2'den alıyoruz
Client client2 = context.Clients.Find(data.Client.Id);
Console.WriteLine("Thread {0} client2", Thread.CurrentThread.Name);
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, client2.ShortIdentity());
// client2'yi değiştiriyoruz
client2.Nom = data.Nom;
// biraz bekleniyor
Thread.Sleep(data.Duree);
// değişiklikleri kaydediyoruz
context.SaveChanges();
}
}
catch (Exception e)
{
// istisna
Console.WriteLine("Thread {0} {1}", Thread.CurrentThread.Name, e);
}
// iş parçacığı sona erdi
Console.WriteLine("Fin Thread {0}", Thread.CurrentThread.Name);
}
- 4. satır: iş parçacığının parametreleri (Süre, Ad, Müşteri) alınır;
- 7. satır: yeni bağlam;
- 11. satır: müşteri bağlama alınır;
- 12-13. satırlar: müşterinin durumunu görmek için izleme yapılır;
- 15. satır: adı değiştirilir;
- 17. satır: iş parçacığı Duree milisaniye boyunca durur. Bunun ilginç bir etkisi vardır. İş parçacığı, kendisini çalıştıran işlemciyi serbest bırakarak başka bir iş parçacığına yer açar. Örneğimizde üç iş parçacığı vardır: main, t1 ve t2. main iş parçacığı, t1 ve t2 iş parçacıklarının bitmesini bekleyerek durmuş durumdadır. t1 iş parçacığının işlemciyi ilk olarak kullandığını varsayarsak, artık işlemciyi t2 iş parçacığına bırakır. Bunun sonucu olarak, t2 iş parçacığı t1 iş parçacığıyla tam olarak aynı şeyi, yani aynı timestamp numarasına sahip aynı istemciyi okuyacaktır;
- 19. satır: Bağlam, veritabanıyla senkronize edilir. Yine, t1 iş parçacığının ilk uyanacağını varsayalım. "yy" adlı istemciyi kaydedecektir. Bunu yapabilecektir çünkü elinde veritabanındakiyle aynı timestamp değerine sahiptir. Bu güncelleme nedeniyle, SGBD, timestamp'i değiştirecektir. t2 iş parçacığı da sırayla uyanınca, şu anda veritabanında bulunan timestamp'ten farklı bir timestamp'e sahip bir müşteriye sahip olacaktır. Onun güncellemesi reddedilecektir.
Ekran görüntüleri şu şekildedir:
- 4. satır: veritabanındaki istemci;
- 9. satır: t2 iş parçacığı tarafından okunan müşteri;
- 11. satır: t1 iş parçacığı tarafından okunan müşteri. Dolayısıyla her iki iş parçacığı da aynı şeyi okumuştur;
- 12. satır: t2 iş parçacığı ilk olarak sona erer. Dolayısıyla güncellemesini yapabilmiştir. Ad, "zz" olarak değişmiş olmalıdır;
- satır 13: t1 iş parçacığı, [System.Data.OptimisticConcurrencyException] türünde bir istisna atar. EF, doğru timestamp'e sahip olmadığını tespit etmiştir;
- 21. satır: t1 iş parçacığı da sona erdi;
- 22. satır: ana iş parçacığı bekleme süresini tamamladı;
- 24. satır: ana iş parçacığı, veritabanındaki müşteriyi görüntüler. Kazanan gerçekten de t2 iş parçacığıdır. Adı "zz"dir. timestamp'in değiştiğine dikkat edelim.
Şimdi başka bir yönü inceleyelim: kalıcılık bağlamının veritabanıyla senkronizasyonunu sağlayan işlem.
3.5.9. İşlem içindeki senkronizasyon
[CRENEAUX] tablosunda, elle eklediğimiz bir tekillik kısıtlaması vardır (bkz. paragraf 2.2.4, sayfa 12):
Şu şekilde ilerleyeceğiz: Aynı doktor, aynı gün ve aynı zaman aralığı için aynı anda iki randevu ekleyeceğiz. Ne olacağını görelim.
Proje şu şekilde gelişiyor:
![]() | ![]() |
[SynchronisationTransaction] programının kodu şöyledir:
using System;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Models;
namespace RdvMedecins_01
{
// test programı
class SynchronisationTransaction
{
static void Main(string[] args)
{
using (var context = new RdvMedecinsContext())
{
// mevcut veritabanı boşaltılıyor
foreach (var client in context.Clients)
{
context.Clients.Remove(client);
}
foreach (var medecin in context.Medecins)
{
context.Medecins.Remove(medecin);
}
context.SaveChanges();
}
// bir müşteri oluşturuluyor
Client client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
// bir doktor oluşturuluyor
Medecin medecin1 = new Medecin { Nom = "xx", Prenom = "xx", Titre = "xx" };
// bu doktor için bir randevu aralığı oluşturuluyor
Creneau creneau1 = new Creneau { Hdebut = 8, Mdebut = 20, Hfin = 8, Mfin = 40, Medecin = medecin1 };
// bu doktor ve bu müşteri için aynı gün, aynı randevu aralığında iki randevu oluşturuluyor
Rv rv1 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
Rv rv2 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10, 18) };
try
{
// tüm bu verileri kalıcı veritabanına kaydediyoruz
using (var context = new RdvMedecinsContext())
{
context.Clients.Add(client1);
context.Creneaux.Add(creneau1);
context.Medecins.Add(medecin1);
context.Rvs.Add(rv1);
context.Rvs.Add(rv2);
// bağlam kaydediliyor - bir istisna oluşması gerekiyor
// çünkü altta yatan BD, aynı gün ve aynı zaman diliminde iki RDV olmasını engelleyen bir tekillik kısıtlamasına sahiptir
// aynı gün, aynı zaman aralığında iki RDV olmasını engelliyor
context.SaveChanges();
}
}
catch (Exception e)
{
Console.WriteLine("Erreur : {0}", e);
}
// kaydetme işlemi bir işlem içinde gerçekleşiyorsa, veritabanına hiçbir şey eklenmemiş olmalıdır
// önceki istisna nedeniyle - kontrol edilir
using (var context = new RdvMedecinsContext())
{
// müşterileri kontrol ediyoruz
Console.WriteLine("Clients--------------------------------------");
var clients = from client in context.Clients select client;
foreach (Client client in clients)
{
Console.WriteLine(client);
}
// doktorlar
Console.WriteLine("Médecins--------------------------------------");
var medecins = from medecin in context.Medecins select medecin;
foreach (Medecin medecin in medecins)
{
Console.WriteLine(medecin);
}
// randevu zaman aralıkları
Console.WriteLine("Créneaux horaires--------------------------------------");
var creneaux = from creneau in context.Creneaux select creneau;
foreach (Creneau creneau in creneaux)
{
Console.WriteLine(creneau);
}
// randevular
Console.WriteLine("Rendez-vous--------------------------------------");
var rvs = from rv in context.Rvs select rv;
foreach (Rv rv in rvs)
{
Console.WriteLine(rv);
}
}
}
}
}
- 15-27. satırlar: veritabanını boşaltmak için bir kalıcılık bağlamı kullanılır;
- 30. satır: [Client] nesnesinin oluşturulması;
- 32. satır: [Medecin] nesnesinin oluşturulması;
- 34. satır: [Creneau] nesnesinin oluşturulması;
- satır 36: [Rv] nesnesinin oluşturulması;
- satır 37: öncekiyle aynı ikinci bir [Rv] nesnesinin oluşturulması;
- satır 41: yeni bir bağlamın açılması;
- satır 43-47: Daha önce oluşturulan nesneler yeni bağlama eklenir. Burada, bağımlılıkları dikkate alarak Add işlemlerinin sayısını en aza indirebileceğimizi unutmayın. Ancak EF, veritabanına gönderilecek SQL ve INSERT komutlarını optimize edecektir;
- 51. satır: bağlam, veritabanıyla senkronize edilir. Yorumda da belirtildiği gibi, [RVS] tablosundaki tekillik kısıtlaması nedeniyle iki randevudan birinin eklenmesi başarısız olmalıdır. Ancak bundan da öte, senkronizasyon bir işlem içinde gerçekleşiyorsa, her şey geri alınmalıdır. Dolayısıyla hiçbir ekleme işlemi gerçekleşmemelidir. Veritabanı boş kalmalıdır;
- 53. satır: bağlam kapatılır;
- 61-90. satırlar: veritabanının içeriğinin görüntülenmesi. Veritabanı boş olmalıdır.
Ekran görüntüsü şu şekildedir:
- 1. satır: [RVS] tablosundaki teklik kısıtlamasının ihlali nedeniyle istisna;
- 9-12. satırlar: veritabanı gerçekten boştur. Dolayısıyla bağlamın veritabanıyla senkronizasyonu bir işlem içinde gerçekleşmiştir.
EF 5'te muhtemelen araştırılması gereken başka konular da vardır. Ancak çok katmanlı mimari incelememize geri dönmek için yeterli bilgiye sahibiz. Okuyucu, bu belgenin başında EF 5 hakkındaki bilgilerini derinleştirebileceği makale ve kitap referanslarını bulacaktır.
3.6. EF 5'e dayalı çok katmanlı mimari incelemesi
2. paragrafta açıklanan vaka çalışmamıza geri dönüyoruz. Bu, aşağıdaki gibi yapılandırılmış bir ASP.NET web uygulamasıdır:
![]() |
Öncelikle, veri erişim katmanını ([DAO]) oluşturmaya başlayacağız. Bu katman, EF5'e dayanacaktır.
3.6.1. Yeni proje
Mevcut [1] çözümünde yeni bir konsol projesi olan VS 2012 [RdvMedecins-SqlServer-02] oluşturuyoruz:
![]() |
Kodlarımızı dağıtacağımız dört klasör ([2]) ekliyoruz. [Entites] klasörü, önceki projenin [Entites] klasörünün bir kopyasıdır. Bu kopyalama işleminden sonra, doğru referanslara sahip olmadığımızdan kaynaklanan hatalar ortaya çıkıyor. Entity Framework 5'e bir referans eklememiz gerekiyor. Bunun için, sayfa 21'deki 3.4 numaralı paragrafta açıklanan yöntemi uygulayacağız. Referans listesi şu şekilde oluyor: [3]:
![]() |
Bu aşamada, projede artık derleme hatası olmamalıdır. Önceki projeden, veritabanı bağlantısını yapılandıran [App.config] dosyasını da kopyalıyoruz:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Entity Framework yapılandırması hakkında daha fazla bilgi için http://go.microsoft.com/fwlink/?LinkID=237468 adresini ziyaret edin -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- veritabanı bağlantı dizesi -->
<connectionStrings>
<add name="monContexte"
connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<!-- fabrika sağlayıcısı -->
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider"
invariant="System.Data.SqlClient"
description=".Net Framework Data Provider for SqlServer"
type="System.Data.SqlClient.SqlClientFactory, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
/>
</DbProviderFactories>
</system.data>
</configuration>
3.6.2. Exception sınıfı
Projeye özgü bir istisna sınıfı kullanacağız. Bu sınıf, [DAO] katmanından çıkacaktır:
![]() |
[DAO] katmanı, kendisine kadar ulaşan tüm istisnaları durduracak ve bunları [RdvMedecinsException] türünde bir istisna içinde toplayacaktır. Bu istisna şu şekilde olacaktır:
using System;
namespace RdvMedecins.Exceptions
{
public class RdvMedecinsException : Exception
{
// özellikler
public int Code { get; set; }
// yapıcılar
public RdvMedecinsException()
: base()
{
}
public RdvMedecinsException(string message)
: base(message)
{
}
public RdvMedecinsException(int code, string message)
: base(message)
{
Code = code;
}
public RdvMedecinsException(int code, string message, Exception ex)
: base(message, ex)
{
Code = code;
}
// kimlik
public override string ToString()
{
if (InnerException == null)
{
return string.Format("RdvMedecinsException[{0},{1}]", Code, base.Message);
}
else
{
return string.Format("RdvMedecinsException[{0},{1},{2}]", Code, base.Message, base.InnerException.Message);
}
}
}
}
- 5. satır: Sınıf, [Exception] sınıfından türetilmiştir;
- 9. satır: temel sınıfına bir hata kodu ekler;
- 12-32. satırlar: Çeşitli yapıcılar, [Code] alanını içerir.
Proje şu şekilde gelişir:
![]() |
3.6.3. [DAO] katmanı
![]() |
[DAO] katmanı, [ASP.NET] katmanına bir arayüz sunar. Bunu tanımlamak için uygulamanın web sayfalarına bakmak gerekir:
![]() |
- Yukarıdaki [1] örneğinde, açılır menü doktorların listesiyle doldurulmuştur. Bu listeyi [DAO] katmanı sağlayacaktır;
- [2] örneğinde, [DAO] katmanı;
- bir doktorun o günkü randevu listesini,
- bir doktorun randevu aralıkları listesi,
- seçilen doktorla ilgili ek bilgiler;
![]() |
- [3]'te, müşteri açılır listesi [DAO] katmanı tarafından sağlanacaktır;
![]() |
- [4]'te, kullanıcı bir randevuyu onaylar. [DAO] katmanı bunu veritabanına ekleyebilmelidir. Ayrıca, seçilen müşteri hakkında ek bilgiler de verebilmelidir;
![]() |
- 5'te kullanıcı bir randevuyu siler. [DAO] katmanı bunu mümkün kılmalıdır.
Bu bilgiler ışığında, [DAO] katmanının [IDao] arayüzü şu şekilde olabilir:
using System;
using System.Collections.Generic;
using RdvMedecins.Entites;
namespace RdvMedecins.Dao
{
public interface IDao
{
// müşteri listesi
List<Client> GetAllClients();
// doktor listesi
List<Medecin> GetAllMedecins();
// bir doktorun randevu saatleri listesi
List<Creneau> GetCreneauxMedecin(int idMedecin);
// belirli bir doktorun belirli bir günkü RV listesi
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// bir RV ekle
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// bir RV'i sil
void SupprimerRv(int idRv);
// bir T varlığını birincil anahtarı aracılığıyla bulma
T Find<T>(int id) where T : class;
}
}
10-20. satırlardaki yöntemler, az önce yapılan incelemeden kaynaklanmaktadır. 22. satırdaki yöntem ise Lazy Loading ile çalışılmasından kaynaklanan sorunu gidermek için eklenmiştir. [ASP.NET] katmanında bir varlığa bağlılık gerekirse, bu yöntem kullanılarak veritabanından bu varlık alınacaktır.
Bu arayüzün [Dao] uygulaması şu şekilde olacaktır:
using System;
using System.Collections.Generic;
using System.Linq;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using RdvMedecins.Models;
namespace RdvMedecins.Dao
{
public class Dao : IDao
{
//müşteri listesi
public List<Client> GetAllClients()
{
// müşteri listesi
List<Client> clients = null;
try
{
// kalıcılık bağlamını aç
using (var context = new RdvMedecinsContext())
{
// müşteri listesi
clients = context.Clients.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(1, "GetAllClients", ex);
}
// sonuç döndürülür
return clients;
}
// doktor listesi
public List<Medecin> GetAllMedecins()
{
// doktor listesi
List<Medecin> medecins = null;
try
{
// kalıcılık bağlamının açılması
using (var context = new RdvMedecinsContext())
{
// doktor listesi
medecins = context.Medecins.ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(2, "GetAllMedecins", ex);
}
// sonuç döndürülür
return medecins;
}
// belirli bir doktorun randevu saatleri listesi
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
...
}
// belirli bir gün için bir doktorun RV listesi
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
...
}
// bir RV ekle
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
...
}
// bir RV'i sil
public void SupprimerRv(int idRv)
{
...
}
// bir müşteri bul
public Client FindClient(int id)
{
...
}
// bir randevu aralığı bul
public Creneau FindCreneau(int id)
{
...
}
// bir doktor bul
public Medecin FindMedecin(int id)
{
....
}
// randevu bul
public Rv FindRv(int id){
...
}
}
}
Tüm müşterilerin listesini döndürmesi gereken [GetAllClients] yöntemini açıklayalım:
- 18-31. satırlar: Müşteri araması bir try / catch bloğu içinde gerçekleştirilir. Aşağıdaki tüm yöntemler için de durum aynı olacaktır;
- 21. satır: yeni bir bağlam açılır;
- 24. satır: [Client] varlıkları bağlama yüklenir ve bir listeye eklenir.
Tüm doktorların listesini döndürmesi gereken [GetAllMedecins] yöntemi de benzerdir (satır 37-57).
[GetCreneauxMedecin] yöntemi şu şekildedir:
// belirli bir doktorun randevu saatleri listesi
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
// randevu saatleri listesi
try
{
// kalıcılık bağlamını aç
using (var context = new RdvMedecinsContext())
{
// doktoru ve randevu saatlerini al
Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
// doktorun randevu aralıklarının listesi
return medecin.Creneaux.ToList<Creneau>();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
}
}
- 9. satır: yeni bir kalıcılık bağlamı açılır;
- satır 11: birincil anahtarı bulunan doktor aranır. Doktorun randevu aralıklarının bir koleksiyonu olan [Creneaux] bağımlılığının da listeye eklenmesi istenir. Doktor mevcut değilse, Single yöntemi bir istisna oluşturur;
- 13. satır: randevu aralıklarının listesi döndürülür.
[GetRvMedecinJour] yöntemi, belirli bir gün için bir doktorun randevu listesini döndürmelidir. Kod şu şekilde olabilir:
// belirli bir gün için bir doktorun RV listesi
public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
{
// Randevu listesi
List<Rv> rvs = null;
try
{
// kalıcılık bağlamının açılması
using (var context = new RdvMedecinsContext())
{
// doktoru alıyoruz
Medecin medecin = context.Medecins.Find(idMedecin);
if (medecin == null)
{
throw new RdvMedecinsException(10, string.Format("Médecin [{0}] inexistant", idMedecin));
}
// randevu listesi
rvs = context.Rvs.Where(r => r.Creneau.Medecin.Id == idMedecin && r.Jour == jour).ToList();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(4, "GetRvMedecinJour", ex);
}
// sonucu geri gönderiliyor
return rvs;
}
- 13. satır: birincil anahtarı bulunan doktor bağlama alınır;
- satır 14-17: Eğer yoksa, bir istisna atılır;
- 19. satır: Bu doktorun randevularını almak için LINQ sorgusu çalıştırılır;
[AjouterRv] yöntemi, veritabanına bir randevu eklemeli ve eklenen öğenin birincil anahtarını döndürmelidir. Kod şu şekilde olabilir:
// bir randevu ekleniyor RV
public int AjouterRv(DateTime jour, int idCreneau, int idClient)
{
// Eklenen randevu numarası
int idRv;
try
{
// kalıcılık bağlamı açılıyor
using (var context = new RdvMedecinsContext())
{
// zaman aralığı alınır
Creneau creneau = context.Creneaux.Find(idCreneau);
if (creneau == null)
{
throw new RdvMedecinsException(5, string.Format("Créneau [{0}] inexistant", idCreneau));
}
// müşteri bilgisi alınır
Client client = context.Clients.Find(idClient);
if (client == null)
{
throw new RdvMedecinsException(6, string.Format("Client [{0}] inexistant", idCreneau));
}
// zaman aralığı oluşturuldu
Rv rv = new Rv { Jour = jour, Client = client, Creneau = creneau };
// bağlamda ekleniyor
context.Rvs.Add(rv);
// bağlamın kaydedilmesi
context.SaveChanges();
// eklenen randevunun birincil anahtarını alıyoruz
idRv = (int)rv.Id;
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(7, "AjouterRv", ex);
}
// sonuç
return idRv;
}
- 12. satır: Veritabanında randevu zaman aralığı aranır;
- 13-16. satırlar: Bulunamazsa bir istisna atılır;
- 18. satır: veritabanında randevunun müşterisi aranır;
- 19-22. satırlar: bulunamazsa, bir istisna atılır;
- 24. satır: gerekli bilgilerle bir [Rv] nesnesi oluşturulur;
- 26. satır: nesneyi kalıcılık bağlamına eklenir;
- 28. satır: Kalıcılık bağlamını veritabanıyla senkronize ediyoruz. Randevu böylece veritabanına kaydedilecek;
- 30. satır: Veritabanıyla senkronizasyondan sonra, eklenen öğelerin birincil anahtarlarının kullanılabilir hale geldiğini biliyoruz. Eklenen randevunun birincil anahtarını alıyoruz;
- satır 31: kalıcılık bağlamını kapatırız.
[SupprimerRv] yöntemi, kendisine birincil anahtarı verilen bir randevuyu silmelidir.
// bir RV'i silme
public void SupprimerRv(int idRv)
{
try
{
// kalıcılık bağlamının açılması
using (var context = new RdvMedecinsContext())
{
// RV'yi alıyoruz
Rv rv = context.Rvs.Find(idRv);
if (rv == null)
{
throw new RdvMedecinsException(5, string.Format("Rv [{0}] inexistant", idRv));
}
// Rv'nin silinmesi
context.Rvs.Remove(rv);
// bağlamın kaydedilmesi
context.SaveChanges();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(8, "SupprimerRv", ex);
}
}
- 7. satır: yeni kalıcılık bağlamı;
- 10. satır: silinecek randevu bağlama aktarılır;
- satır 11-15: Eğer yoksa, bir istisna atılır;
- satır 16: randevu bağlamdan silinir;
- 18. satır: bağlamı veritabanıyla senkronize ediyoruz;
- satır 19: bağlam kapatılır.
[Find<T>] yöntemi, birincil anahtarı aracılığıyla veritabanında T türünde bir varlığı aramaya olanak tanır. Kod şu şekilde olabilir:
public T Find<T>(int id) where T : class
{
try
{
// kalıcılık bağlamının açılması
using (var context = new RdvMedecinsContext())
{
return context.Set<T>().Find(id);
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(20, "Find<T>", ex);
}
}
- 8. satır: Set<T> yöntemi, üzerine olağan yöntemlerin uygulanabileceği bir DbSet<T> nesnesini almayı sağlar.
Proje şu şekilde gelişir:
![]() |
3.6.4. [DAO] katmanının testi
[DAO] katmanını test etmek için bir program oluşturacağız. Testin mimarisi şu şekilde olacaktır:
![]() |
Bir konsol programı, [Spring.net]'ten [DAO] katmanını örneklendirmesini ister. Bu işlem tamamlandıktan sonra, [DAO] katmanının arayüzündeki çeşitli işlevleri test eder. Konsol programı yerine, NUnit türünde bir test programı yazmak daha uygun olurdu. [DAO] katmanının bir test programı şu şekilde olabilir:
using System;
using System.Collections.Generic;
using RdvMedecins.Dao;
using RdvMedecins.Entites;
using RdvMedecins.Exceptions;
using Spring.Context.Support;
namespace RdvMedecins.Tests
{
class Program
{
public static void Main()
{
IDao dao = null;
try
{
// Spring aracılığıyla [DAO] katmanının örneklenmesi
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
// müşterilerin görüntülenmesi
List<Client> clients = dao.GetAllClients();
DisplayClients("Liste des clients :", clients);
// doktorların görüntülenmesi
List<Medecin> medecins = dao.GetAllMedecins();
DisplayMedecins("Liste des médecins :", medecins);
// 0 numaralı doktorun randevu aralıkları listesi
List<Creneau> creneaux = dao.GetCreneauxMedecin((int)medecins[0].Id);
DisplayCreneaux(string.Format("Liste des créneaux horaires du médecin {0}", medecins[0]), creneaux);
// Belirli bir gün için bir doktorun randevu listesi
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// 1 numaralı hekime 0 numaralı zaman diliminde bir RV ekle
Console.WriteLine(string.Format("Ajout d'un RV au médecin {0} avec client {1} le 23/11/2013", medecins[0], clients[0]));
int idRv1 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
// zaten dolu bir zaman dilimine randevu ekle - bir istisna oluşturmalıdır
int idRv2;
Console.WriteLine("Ajout d'un RV dans un créneau déjà occupé");
try
{
idRv2 = dao.AjouterRv(new DateTime(2013, 11, 23), (int)creneaux[0].Id, (int)clients[0].Id);
Console.WriteLine("Rdv ajouté");
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (RdvMedecinsException ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
// bir randevuyu sil
Console.WriteLine(string.Format("Suppression du RV n° {0}", idRv1));
dao.SupprimerRv(idRv1);
DisplayRvs(string.Format("Liste des RV du médecin {0}, le 23/11/2013 :", medecins[0]), dao.GetRvMedecinJour((int)medecins[0].Id, new DateTime(2013, 11, 23)));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("L'erreur suivante s'est produite : {0}", ex));
}
//duraklama
Console.ReadLine();
}
// yardımcı yöntemler - listeleri görüntüler
public static void DisplayClients(string Message, List<Client> clients)
{
Console.WriteLine(Message);
foreach (Client c in clients)
{
Console.WriteLine(c.ShortIdentity());
}
}
public static void DisplayMedecins(string Message, List<Medecin> medecins)
{
...
}
public static void DisplayCreneaux(string Message, List<Creneau> creneaux)
{
...
}
public static void DisplayRvs(string Message, List<Rv> rvs)
{
...
}
}
}
-
- satır: [DAO] katmanına yapılan referans. Testi, bu katmanın gerçek uygulamasından bağımsız hale getirmek için, bu referans [IDao] arayüz türündedir ve [Dao] sınıfı türünde değildir;
-
- satır: [DAO] katmanı Spring tarafından örneklenir. Bunun mümkün olması için gerekli yapılandırmaya daha sonra değineceğiz. Spring tarafından döndürülen nesne referansını, [IDao] arayüzünün türünde bir referansa dönüştürüyoruz;
- 21-22. satırlar: müşterileri görüntüler;
- 25-26. satırlar: doktorları görüntüler;
- 29-30. satırlar: 0 numaralı doktorun randevu aralıkları listesini görüntüler;
-
- satır: 23/11/2013 tarihli doktor no: 0'ın randevularını görüntüler. Hiç randevu olmamalıdır;
-
- satır: 23/11/2013 tarihi için 0 numaralı doktora bir randevu ekler;
- satır 39: 23/11/2013 tarihli doktor no: 0'ın randevularını gösterir. Bir randevu olmalıdır;
- satır 46: Aynı randevu ikinci kez eklenir. Bir istisna oluşmalıdır;
- satır 57: eklenen tek randevu silinir;
- satır 58: 23/11/2013 tarihinde doktor no. 0'ın randevularını gösterir. Hiç randevu olmamalıdır.
3.6.5. Spring.net Yapılandırması
Yukarıdaki test programında, [DAO] katmanını oluşturan komutun üzerinden hızlıca geçtik:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
[ContextRegistry] sınıfı, [Spring.Context.Support] ad alanında bulunan bir Spring sınıfıdır. Spring'i kullanabilmek için, proje referanslarına DLL'i eklememiz gerekiyor. Bunun için şu adımları izliyoruz:
![]() |
- [1]'te, [NuGet] aracıyla paketleri arıyoruz;
![]() |
- [2]'e dönüştürdükten sonra, çevrimiçi paketleri arıyoruz;
- [3]'te, arama alanına spring anahtar kelimesi girilir;
- [4]'te, açıklamasında bu anahtar kelimeyi içeren paketler görüntülenir. Burada, bize uygun olan [Spring.Core]'tir. Bunu kurarız.
Projenin referansları şu şekilde değişir:
![]() |
[Spring.Core] paketi, [Common.Logging] paketine bağımlıydı. Bu paket de yüklendi. Bu aşamada, projede artık hata olmamalıdır.
Ancak bu, projenin çalışacağı anlamına gelmez. Öncelikle [App.config] dosyasında Spring'i yapılandırmamız gerekiyor. Bu, projenin en hassas kısmıdır. Yeni [App.config] dosyası şu şekildedir:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Entity Framework yapılandırması hakkında daha fazla bilgi için http://go.microsoft.com/fwlink/?LinkID=237468 adresini ziyaret edin -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- ortak günlük kaydı-->
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<!-- Entity Framework -->
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
<!-- Bağlantı Dizeleri -->
<connectionStrings>
<add name="monContexte" connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User Id=sa;Password=sqlserver2012;" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.data>
<DbProviderFactories>
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</DbProviderFactories>
</system.data>
<!-- Spring yapılandırması -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!-- yapılandırma common.logging -->
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</configuration>
Öncelikle zaten bilinen her şeyi kaldırarak başlayalım: Entity Framework, bağlantı dizeleri, ProviderFactory. Dosya şu şekilde değişir:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- Entity Framework yapılandırması hakkında daha fazla bilgi için http://go.microsoft.com/fwlink/?LinkID=237468 adresini ziyaret edin -->
<section name="entityFramework" ... />
<!-- spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- ortak günlük kaydı-->
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
...
<!-- Spring yapılandırması -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
<!-- common.logging yapılandırması -->
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="DEBUG" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
</configuration>
- 3-15. satırlar: yapılandırma bölümlerini tanımlar;
-
- satır: XML dosyasının <spring><context> bölümünü yönetecek sınıfı tanımlar (19-21. satırlar);
-
- satır: XML dosyasının <spring><objects> bölümünü yönetecek sınıfı tanımlar (22-24. satırlar);
-
- satır: XML dosyasının <common><logging> bölümünü yönetecek sınıfı tanımlar (27-36. satırlar);
- satır 7-14: sabittir. Başka bir projede değiştirilmesine gerek yoktur;
- satır 18-25: Spring yapılandırması. Spring'in örneklendireceği nesneleri tanımlayan satır 22-24 hariç, sabittir;
-
- satır: bir nesnenin tanımı. id özniteliği serbesttir. Bu, nesnenin kimliğidir. type özniteliği, "sınıfın tam adı, sınıfı içeren Assembly" biçiminde örneklenecek sınıfı belirtir. Buradaki sınıf, [DAO] katmanını uygulayan sınıftır: [RdvMedecins.Dao.Dao]. Assembly'sini öğrenmek için projenin özelliklerine bakmak gerekir:
![]() |
[1], sağlanacak assembly'nin adı;
- 27-36. satırlar: "Common Logging" yapılandırması sabittir. 32. satırdaki bilgi düzeyini değiştirmeniz gerekebilir. Hata ayıklama aşamasından sonra, bu düzeyi INFO olarak ayarlayabilirsiniz.
Sonuç olarak, ilk bakışta karmaşık görünen Spring yapılandırma dosyası aslında basittir. Değiştirilmesi gereken tek şey:
- örneklenecek nesneleri tanımlayan 22-24. satırlar;
-
- satır: günlük kaydı seviyesi.
Test programında, [DAO] katmanını oluşturan komut şöyledir:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
[ContextRegistry], [Web.config] veya [App.config] dosyasında yapılan Spring yapılandırmasını kullanan bir Spring sınıfıdır. Burada, [App.config] dosyasının aşağıdaki bölümünü kullanacaktır:
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
- ContextRegistry.GetContext(), 2-4. satırlardaki bağlamı kullanır. 3. satır, Spring nesnelerinin yapılandırma dosyasının [spring/objects] bölümünde tanımlandığını belirtir. Bu bölüm 5-7. satırlarıdır;
- ContextRegistry.GetContext().GetObject("rdvmedecinsDao") komutu, 5-7. satırları içeren bölümü kullanır. Bu, id="rdvmedecinsDao" özniteliğine sahip nesneye bir referans döndürür. Bu, 6. satırda tanımlanan nesnedir. Spring daha sonra, type özniteliği ile tanımlanan sınıfı, parametresiz yapıcı işlevini kullanarak örneklendirir. Dolayısıyla bu yapıcı işlevin mevcut olması gerekir. Bu işlem tamamlandıktan sonra, oluşturulan nesnenin referansı çağıran koda döndürülür. Kodda nesne ikinci kez istenirse, Spring ilk oluşturulan nesneye ait referansı döndürür. Bu, singleton olarak adlandırılan tasarım kalıbıdır (Design Pattern).
Nesnenin oluşturulması daha karmaşık olabilir. Parametreli bir yapıcı kullanılabilir veya nesne oluşturulduktan sonra belirli alanlarının başlatılması belirtilebilir. Bu konu hakkında daha fazla bilgi için, "Spring IOC Eğitimi for .NET" başlıklı makaleyi, URL [http://tahe.developpez.com/dotnet/springioc/] adresinden okuyabilirsiniz.
Bu işlem tamamlandıktan sonra uygulamayı çalıştırabiliriz. Ekran çıktısı şu şekildedir:
Sonuçlar beklendiği gibidir. Artık [DAO] katmanımızın geçerli olduğunu kabul edeceğiz. Eğitim burada sona erebilir. Şimdiye kadar şunları gösterdik:
- ORM Entity Framework 5'in temellerini;
- bu ORM'i kullanan bir [DAO] katmanını.
Bu belgenin başında anlattığımız vaka çalışmasını hatırlayalım. Aşağıdaki mimariye sahip mevcut bir uygulamadan yola çıkıyoruz:
![]() |
ve bunu şuna dönüştürmek istiyoruz:
![]() |
burada EF5, NHibernate’in yerini almıştır. Az önce [DAO2] katmanını oluşturduk. Aslında bu katman, daha sınırlı bir arayüze sahip olan [DAO1] katmanıyla aynı arayüze sahip değildir:
public interface IDao
{
// müşteri listesi
List<Client> GetAllClients();
// doktor listesi
List<Medecin> GetAllMedecins();
// bir doktorun randevu saatleri listesi
List<Creneau> GetCreneauxMedecin(int idMedecin);
// belirli bir doktorun belirli bir günkü RV listesi
List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
// bir RV ekle
int AjouterRv(DateTime jour, int idCreneau, int idClient);
// bir RV'i sil
void SupprimerRv(int idRv);
}
[DAO2] katmanı, bu arayüze şu yöntemi eklemiştir:
// bir T varlığını birincil anahtarı aracılığıyla bulma
T Find<T>(int id) where T : class;
Bu yöntemin eklenmesi, ORM ve EF 5'in varsayılan olarak Lazy Loading modunda çalışmasından kaynaklanmaktadır. Varlıklar, bağımlılıkları olmadan [ASP.NET] katmanına gelir. Yukarıdaki yöntem, gerektiğinde bu bağımlılıkları almamızı sağlar ve bazı durumlarda buna ihtiyaç duyarız. NHibernate de varsayılan olarak Lazy Loading modunda çalışır, ancak ben onu Eager Loading modunda kullanmıştım. Varlıklar, bağımlılıklarıyla birlikte [ASP.NET] katmanına geliyordu.
ASP.NET / NHibernate uygulamasının ASP.NET / EF 5 uygulamasına taşınmasını tamamlayacağız. Ancak bu, EF5'i artık ilgilendirmediğinden, web kodunu yorumlamayacağız. Sadece web uygulamasını nasıl kuracağınızı ve test edeceğinizi açıklayacağız. Bu uygulama, bu öğreticinin web sitesinde mevcuttur.
3.6.6. [DAO] katmanından DLL'in oluşturulması
Aşağıdaki mimaride:
![]() |
[ASP.NET] katmanı, sağındaki katmanları DLL biçiminde kullanabilecektir. Dolayısıyla, [DAO] katmanından DLL'i oluşturuyoruz.
![]() |
- [1]'te test programı seçilirken, [2]'te bu program, oluşturulacak olan DLL'e dahil edilmez;
- [3]'te, proje özelliklerinde oluşturulacak derlemenin DLL olduğu belirtilir;
- [4] dosyasında, VS menüsünden, [Debug] türündeki bir derlemeden daha az bilgi içeren [Release] türünde bir derleme oluşturulacağı belirtilir;
![]() |
- 5'te, projenin derlemesi yeniden oluşturulur. DLL oluşturulacaktır;
- [6]'e dönüştürüldüğünde, projenin tüm dosyaları görüntülenir;
![]() |
- [7] komutuyla, [DAO] katmanındaki projenin DLL dosyası oluşturulur. ASP.NET web projesi bu dosyayı kullanacaktır;
- [8]'te, projenin görüntüsünü yeniliyoruz;
![]() |
- [9]'te, [Release] klasöründeki DLL dosyaları, [lib] adlı harici bir klasörde toplanır. Web projesi referanslarını buradan alacaktır.
3.6.7. [ASP.NET] katmanı
Burada, [ASP.NET / NHibernate] uygulamasının [ASP.NET / EF 5] uygulamasına taşınmasını açıklayacağız. URL [http://www.microsoft.com/visualstudio/fra/downloads] adresinden ücretsiz olarak indirilebilen Visual Studio Express 2012 for the Web ile çalışacağız.
VS 2010 ile oluşturulan mevcut web projesini temel alacağız.
![]() |
- [1]'te mevcut projeyi açıyoruz:
- [2]'te, yüklenen projenin aşağıdaki referansları vardır: [3]:
- [NHibernate], NHibernate çerçevesinin DLL'sidir,
- [Spring.Core], Spring.net çerçeve yapısının DLL'sidir,
- [log4net], log4net günlük çerçevesinin DLL sürümüdür. Bu çerçeve, Spring.net tarafından kullanılmaktadır,
- [MySql.Data], SGBD ve MySQL'in ADO.NET sürücüsüdür,
- [rdvmedecins], NHibernate ile oluşturulan [DAO] katmanının DLL'sidir;
- [4]'te projenin adını değiştiriyoruz ve 5'te önceki referansları siliyoruz;
![]() |
- [6]'te projeye referanslar ekliyoruz;
- [7]'te, sihirbazda [Parcourir] seçeneğini kullanıyoruz;
![]() |
- [8]'te, daha önce [lib] klasörüne yerleştirilen 2 numaralı projenin tüm DLL dosyalarını seçiyoruz;
- [9]'e, onayladığımız bir özet;
- [10] dosyasında, yeni referansları içeren web projesi.
Bu işlemlerin ardından proje şu şekilde görünmektedir:
![]() |
- [1] dosyasında, web sayfalarını yönetme kodu [Global.asax] ve [Default.aspx] olmak üzere iki dosyaya bölünmüştür. Yardımcı kodlar ise [Entites] klasörüne yerleştirilmiştir. Son olarak, uygulama [Web.config] dosyası ile yapılandırılır;
- [2] dosyasında projenin derlemesini oluşturuyoruz;
- [3] dosyasında ise hatalar ortaya çıkıyor.
Hataları inceleyelim, örneğin şunu:
![]()
ve açıklaması:
![]()
[medecin.Id]'in türü int mi? Oysa [GetCreneauxMedecin] yöntemi int türündedir. Dolayısıyla cast gereklidir. Bu hata kodun her yerinde tekrarlanıyor çünkü ASP.NET / NHibernate projesindeki varlıkların birincil anahtarları int türündeyken, ASP.NET / EF 5 projesindekiler int? türündedir. Bu türdeki tüm hataları düzelttikten sonra projeyi yeniden oluşturuyoruz. Böylece hatalar ortadan kalkıyor.
Projeyi çalıştırmadan önce halletmemiz gereken bir ayrıntı daha var: Spring çerçeve tarafından [DAO] katmanının örneklenmesi. Bu işlem [Global.asax] dosyasında gerçekleştirilir:
protected void Application_Start(object sender, EventArgs e)
{
// veritabanındaki belirli verileri önbelleğe alıyoruz
try
{
// [dao] katmanının örneklenmesi
Dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
...
}
catch (Exception ex)
{...
}
}
[DAO] katmanının test programında, bu katman [DAO] katmanını şu şekilde örneklendiriyordu:
dao = ContextRegistry.GetContext().GetObject("rdvmedecinsDao") as IDao;
Her iki yöntem de aynıdır. Hatırlanacağı üzere, [DAO] katmanının bu örneklenmesi, [App.config]'te yapılan bir yapılandırmaya dayanıyordu. Aynı yapılandırmaya sahip olmak için, web projesinin mevcut içeriği olan [Web.config]'i, [DAO] katman projesindeki [App.config] içeriğiyle değiştiriyoruz.
İlk çalıştırmaya hazırız. [1] ana sayfası görüntülenir:
![]() |
- [2]'te bir randevu tarihi girip onaylıyoruz;
![]() |
- [3] sayfasında bir hata oluşuyor.
Sayfa tarafından görüntülenen hata metnini incelediğimizde, bildirilen istisnanın Lazy Loading ile ilgili olduğunu görüyoruz: nesneyi yöneten kalıcılık bağlamı kapatılmışken, nesnenin bir bağımlılığını yüklemeye çalışılmış. Nesne şu anda "bağlantısı kesilmiş" durumdadır. Bu hata, NHibernate'in Eager Loading modunda kullanılmış olmasına karşın, EF'in varsayılan olarak Lazy Loading ile çalışmasından kaynaklanmaktadır. Yukarıdaki kırmızı satırda:
- rdv, bağımlılıkları olmadan yüklenmiş bir [Rv] nesnesini temsil eder;
- rdv.Creneau.Id'i değerlendirmek için uygulama, rdv.Creneau bağımlılığını yüklemeye çalışır. Ancak artık bağlamda olmadığımız için bu mümkün değildir, dolayısıyla istisna oluşur.
Buradaki çözüm basittir. 108. satırda, bir randevunun zaman aralığının birincil anahtarını anahtar olarak kullanarak bir sözlükte bir giriş oluşturulur. Oysa [Rv] nesnesi, ilişkili zaman aralığının birincil anahtarını kapsular. Dolayısıyla şöyle yazılır:
dicoRvPris[(int)rdv.CreneauId] = rdv;
Yürütmeyi tekrar deniyoruz. Bu sefer şu hata ortaya çıkıyor:
![]() |
Hata benzerdir. 132. satırda, [Rv] nesnesinin [Client] bağımlılığını, ASP.NET katmanına, yani bağlam dışı bir yere yüklemeye çalışıyoruz. Veritabanından [Client] nesnesini almak gerekiyor. Bu sorunu gidermek için [IDao] arayüzüne aşağıdaki yöntem eklenmiştir:
// bir T varlığını birincil anahtarıyla bulma
T Find<T>(int id) where T : class;
Bu yöntem, bağımlılıkları almayı sağlayacaktır. Böylece yukarıdaki hatalı satır şu şekilde yeniden yazılacaktır:
Client client = Global.Dao.Find<Client>(agenda.Creneaux[i].Rdv.ClientId);
Yine, varlıkların yabancı anahtarlarını içermelerinin ne kadar önemli olduğuna dikkat çekmek isteriz. Burada, [Rv] varlığı, ilişkili [Creneau] bağımlılığının yabancı anahtarına erişim sağlar. Bu iki düzeltme yapıldıktan sonra uygulama çalışır hale gelir. Okuyucuların, bu makalenin web sitesindeki örnek indirmeler bölümünde bulunan [RdvMedecins-SqlServer-03] uygulamasını test etmeleri önerilir.
3.7. Conclusion
ASP.NET / NHibernate uygulamasının taşınmasını başarıyla tamamladık:
![]() |
uygulamasını ASP.NET / EF 5 uygulamasına başarıyla taşıdık:
![]() |
Bu mimari, [ASP.NET] katmanını olduğu gibi korumamıza olanak sağlaması gerekirken, iki nedenden dolayı onu değiştirmek zorunda kaldık:
- varlıklar tam olarak aynı değildi. NHibernate varlıklarının birincil anahtar türü int iken, EF 5 varlıklarınınki int? idi. Bu durum, web koduna cast'leri eklememize neden oldu;
- entitelerin yüklenme modu her iki ORM için aynı değildi: NHibernate için Eager Loading, EF 5 için Lazy Loading. Bu durum, [DAO] katmanının arayüzünü, bir varlığı birincil anahtarı aracılığıyla alabilen genel bir yöntemle zenginleştirmemize yol açtı.
Bununla birlikte, taşıma işlemi oldukça basit oldu; bu da, gerekirse, katmanlı mimariyi ve Spring ya da başka bir bağımlılık enjeksiyon çerçevesiyle bağımlılık enjeksiyonunu bir kez daha haklı çıkardı.
Şimdi, SGBD'teki bir değişikliğin önceki mimariye etkisini ölçeceğiz. Önceki tüm projeleri dört farklı SGBD'e taşıyacağız:
- Oracle Database Express Edition 11g Sürüm 2;
- MySQL 5.5.28;
- PostgreSQL 9.2.1;
- Firebird 2.1.
Kodlar artık değişmeyecek. Yalnızca aşağıdaki öğeler değişecek:
- bir varlığa erişim rekabetini kontrol etmek için kullanılan alanın varlık tanımındaki tanımı;
- [App.config] veya [Web.config] yapılandırma dosyaları;
Yalnızca değişen unsurlar hakkında yorum yapacağız.


















































































































































