Code First Migrations
Code First Migrations, Code First iş akışını kullananlar için uygulamanın veritabanı şemasını geliştirme açısından önerilen yöntemdir. Code First Migrations, şunları sağlayan bir araç kümesi sunar:
- EF modelinizle çalışan başlangıç veritabanını oluşturma
- EF modelinizde yaptığınız değişiklikleri izlemek için geçişler oluşturma
- Veritabanınızı bu değişikliklerle güncel tutma
Aşağıdaki kılavuz, Entity Framework’teki Code First Migrations aracına genel bakış sağlar. Kılavuzun tamamını inceleyebilir veya doğrudan ilgilendiğiniz konuya geçebilirsiniz. Aşağıdaki konular ele alınmıştır:
İlk Model ve Veritabanı Oluşturma
Code First Migrations aracını kullanmaya başlamadan önce üzerinde çalışacağımız bir projeye ve Code First modeline ihtiyacımız olacak. Bu kılavuzda kurallı Blog ve Post modelini kullanacağız.
- MigrationsDemo adlı yeni bir konsol uygulaması oluşturun
- EntityFramework NuGet paketinin en son sürümünü projeye ekleme
- Araçlar -> Kitaplık Paket Yöneticisi -> Paket Yöneticisi Konsolu
- Install-Package EntityFramework komutunu çalıştırın
- Aşağıda gösterilen kodu içeren bir Model.cs dosyası ekleyin. Bu kod, alan modelimizi oluşturan tek bir Blog sınıfını ve EF Code First bağlamımız olan blogContext sınıfını tanımlar
using System.Data.Entity;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.Infrastructure;
namespace MigrationsDemo
{
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
}
}
- Artık bir modele sahip olduğumuza göre bunu kullanarak veri erişimini gerçekleştirebiliriz. Program.cs dosyasını aşağıda gösterilen kodla güncelleştirin.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MigrationsDemo
{
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
db.Blogs.Add(new Blog { Name = "Another Blog " });
db.SaveChanges();
foreach (var blog in db.Blogs)
{
Console.WriteLine(blog.Name);
}
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Uygulamanızı çalıştırdığınızda sizin için migrationsCodeDemo.BlogContext adlı bir veritabanının oluşturulduğunu göreceksiniz.
Code First Migrations aracını etkinleştirme
Modelimizde daha fazla değişiklik yapma zamanı geldi.
- Şimdi Blog sınıfına bir Url özelliği ekleyelim.
public string Url { get; set; }
Uygulamayı yeniden çalıştırırsanız, veritabanı oluşturulduktan sonra 'BlogContext' bağlamını arkalayan modelin değiştiğini belirten bir InvalidOperationException alırsınız. Veritabanını ( http://go.microsoft.com/fwlink/?LinkId=238269) güncelleştirmek için Code First Migrations kullanmayı göz önünde bulundurun.
Özel durumdan da anlaşılacağı gibi Code First Migrations aracını kullanmaya başlamanın zamanı geldi. İlk adım, bağlamımız için geçişleri etkinleştirmektir.
Paket Yöneticisi Konsolunda Enable-Migrations komutunu çalıştırın
Bu komut projeye bir Migrations klasörü ekler. Bu yeni klasörde iki dosya vardır:
Configuration sınıfı. Bu sınıf, Code First Migrations aracının bağlamınızda nasıl davranacağını yapılandırmanıza olanak tanır. Bu kılavuzda varsayılan yapılandırmayı kullanacağız. Projenizde yalnızca tek bir Code First bağlamı olduğundan Enable-Migrations bu yapılandırmanın geçerli olduğu bağlam türünü otomatik olarak doldurdu.
InitialCreate geçişi. Bu geçiş, geçişleri etkinleştirmeden önce Code First bizim için bir veritabanı oluşturduğundan oluşturuldu. Bu iskeleli geçişteki kod, veritabanında önceden oluşturulmuş nesneleri temsil eder. Buradaki örnekte bu, BlogId ve Name sütunlarına sahip Blog tablosudur. Dosya adı, sıralamaya yardımcı olacak bir zaman damgası içerir. Veritabanı henüz oluşturulmamış olsaydı bu InitialCreate geçişi projeye eklenmeyecekti. Bunun yerine ilk Add-Migration çağrısında bu tabloları oluşturmak için gerekli kod için yeni geçişte iskele oluşturulacaktı.
Aynı Veritabanını Hedefleyen Birden Çok Model
EF6 öncesi sürümleri kullanırken veritabanının şemasını oluşturmak/yönetmek için yalnızca bir Code First modeli kullanılabilir. Bu, veritabanı başına tek bir __MigrationsHistory tablosunun sonucuydu ve hangi girişlerin hangi modele ait olduğunu tanımlamaya yönelik bir yöntem yoktur.
EF6 sürümünden itibaren Configuration sınıfı bir ContextKey özelliği içerir. Bu, her Code First modeli için benzersiz bir tanımlayıcı işlevi görür. __MigrationsHistory tablosundaki karşılık gelen sütun, birden çok modelden gelen girişlerin tabloyu paylaşmasına olanak tanır. Bu özellik varsayılan olarak bağlamınızın tam adı olarak ayarlanır.
Oluşturma ve Çalıştırma Geçişleri
Code First Migrations aracında sürekli kullanacağınız iki temel komut vardır.
- Add-Migration komutu, son geçiş oluşturulduktan sonra modelinizde yaptığınız değişikliklere göre sonraki geçişin iskelesini oluşturur
- Update-Database komutu, bekleyen geçişleri veritabanına uygular
Eklediğimiz yeni Url özelliğiyle ilgilenmek için geçişin iskelesini oluşturmamız gerekiyor. Add-Migration komutu bu geçişlere bir ad vermemizi sağlar. Örneğimize AddBlogUrl adını verelim.
- Paket Yöneticisi Konsolu’nda Add-Migration AddBlogUrl komutunu çalıştırın
- Migrations klasöründe artık AddBlogUrl adlı yeni bir geçişimiz var. Geçişin dosya adının önüne, sıralamaya yardımcı olmak için bir zaman damgası eklendi
namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddBlogUrl : DbMigration
{
public override void Up()
{
AddColumn("dbo.Blogs", "Url", c => c.String());
}
public override void Down()
{
DropColumn("dbo.Blogs", "Url");
}
}
}
İstersek bu geçişte düzenleme veya ekleme yapabiliriz ekleyebiliriz ancak her şey oldukça iyi görünüyor. Bu geçişi veritabanına uygulamak için Update-Database komutunu kullanalım.
- Paket Yöneticisi Konsolu’nda Update-Database komutunu çalıştırın
- Code First Migrations, Migrations klasöründeki geçişleri veritabanına uygulanmış olanlarla karşılaştırır. AddBlogUrl geçişinin uygulanması gerektiğini görür ve bunu çalıştırır.
MigrationsDemo.BlogContext veritabanı artık Blogs tablosunda Url sütununu içerecek şekilde güncelleştirildi.
Geçişleri Özelleştirme
Önceki bölümlerde herhangi bir değişiklik yapmadan bir geçiş oluşturup çalıştırdık. Şimdi varsayılan olarak oluşturulan kodu düzenleyelim.
- Modelimizde daha fazla değişiklik yapma zamanı geldi. Blog sınıfına yeni bir Rating özelliği ekleyelim
public int Rating { get; set; }
- Yeni bir Post sınıfı da ekleyelim
public class Post
{
public int PostId { get; set; }
[MaxLength(200)]
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
- Ayrıca Blog ile Post arasında ilişkinin diğer tarafını oluşturmak için Blog sınıfına bir Posts koleksiyonu ekleyeceğiz
public virtual List<Post> Posts { get; set; }
Add-Migration komutunu kullanarak Code First Migrations aracının bizim adımıza geçişle ilgili en iyi tahmini yapmasına izin vereceğiz. Bu geçişe AddPostClass adını vereceğiz.
- Paket Yöneticisi Konsolu’nda Add-Migration AddPostClass komutunu çalıştırın.
Code First Migrations bu değişikliklerin iskelesini oluşturma konusunda oldukça iyi bir iş çıkardı ancak birkaç değişiklik yapmak isteyebiliriz:
- İlk olarak, Posts.Title sütununa benzersiz bir dizin ekleyelim (aşağıdaki kodda 22 . ve 29. satırda ekleniyor).
- Ayrıca null atanamayan bir Blogs.Rating sütunu da ekliyoruz. Tabloda veri mevcut olduğunda yeni sütun için veri türünün CLR varsayılanı atanacak (Rating alanı tamsayı olduğundan 0 olur). Ancak Blogs tablosundaki mevcut satırların düzgün bir derecelendirmeyle başlaması için varsayılan 3 değerini belirtmek istiyoruz. (Aşağıdaki kodun 24. satırında belirtilen varsayılan değeri görebilirsiniz)
namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddPostClass : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Posts",
c => new
{
PostId = c.Int(nullable: false, identity: true),
Title = c.String(maxLength: 200),
Content = c.String(),
BlogId = c.Int(nullable: false),
})
.PrimaryKey(t => t.PostId)
.ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true)
.Index(t => t.BlogId)
.Index(p => p.Title, unique: true);
AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
}
public override void Down()
{
DropIndex("dbo.Posts", new[] { "Title" });
DropIndex("dbo.Posts", new[] { "BlogId" });
DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs");
DropColumn("dbo.Blogs", "Rating");
DropTable("dbo.Posts");
}
}
}
Düzenlediğimiz geçiş kullanıma hazır. Veritabanını güncelleştirmek için Update-Database komutunu kullanalım. Bu kez Code First Migrations aracı tarafından çalıştırılan SQL kodunu görmek için –Verbose bayrağını belirtelim.
- Paket Yöneticisi Konsolu’nda Update-Database –Verbose komutunu çalıştırın.
Veri Hareketi/Özel SQL
Şu ana kadar veri değiştirmeyen veya taşımayan geçiş işlemlerini inceledik. Şimdi de bazı verileri taşıması gereken bir örneğe bakalım. Veri hareketi için henüz yerel destek sunulmaz. Ancak betiğimizin herhangi bir noktasında rastgele SQL komutları çalıştırabiliriz.
- Modelimize bir Post.Abstract özelliği ekleyelim. Daha sonra Content sütununun başından bir metin kullanarak mevcut gönderiler için Abstract alanını önceden dolduracağız.
public string Abstract { get; set; }
Add-Migration komutunu kullanarak Code First Migrations aracının bizim adımıza geçişle ilgili en iyi tahmini yapmasına izin vereceğiz.
- Paket Yöneticisi Konsolu’nda Add-Migration AddPostAbstract komutunu çalıştırın.
- Oluşturulan geçiş, şema değişikliklerini gerçekleştirir ancak her gönderi için ilk 100 karakterlik içeriği kullanarak Abstract sütununu önceden doldurmak istiyoruz. Bunu yapmak için SQL’e gidebilir ve sütun eklendikten sonra bir UPDATE deyimi çalıştırabiliriz. (Aşağıdaki koda 12. satırı ekliyoruz)
namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddPostAbstract : DbMigration
{
public override void Up()
{
AddColumn("dbo.Posts", "Abstract", c => c.String());
Sql("UPDATE dbo.Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL");
}
public override void Down()
{
DropColumn("dbo.Posts", "Abstract");
}
}
}
Düzenlediğimiz geçiş iyi görünüyor. Veritabanını güncelleştirmek için Update-Database komutunu kullanalım. Veritabanında çalıştırılan SQL kodunu görebilmek için –Verbose bayrağını belirteceğiz.
- Paket Yöneticisi Konsolu’nda Update-Database –Verbose komutunu çalıştırın.
Belirli Bir Sürüme Geçiş (Eski Sürüme Düşürme Dahil)
Şimdiye kadar her zaman en son geçişe yükselttik. Ancak belirli bir geçişe yükseltme/eski sürüme düşürme isteğinde bulunabileceğimiz zamanlar olabilir.
AddBlogUrl geçişini çalıştırdıktan sonra veritabanımızı olduğu duruma geçirmek istediğimizi varsayalım. Bu geçiş sürümüne düşürmek için –TargetMigration anahtarını kullanabiliriz.
- Paket Yöneticisi Konsolu’nda Update-Database –TargetMigration: AddBlogUrl komutunu çalıştırın.
Bu komut AddBlogAbstract ve AddPostClass geçişlerimiz için Down betiğini çalıştırır.
Boş bir veritabanına geri dönmek istiyorsanız Update-Database –TargetMigration: $InitialDatabase komutunu kullanabilirsiniz.
SQL Betiği Alma
Başka bir geliştirici bu değişiklikleri kendi makinesine uygulamak istiyorsa yaptığımız değişiklikleri kaynak denetiminde eklediğimizde bunu yapabilir. Yeni geçişlerimizi aldıktan sonra değişikliklerin yerel olarak uygulanmasını sağlamak için Update-Database komutunu çalıştırabilir. Ancak bu değişiklikleri bir test sunucusuna göndermek ve sonunda üretim ortamına almak istiyorsak muhtemelen DBA ortamına dağıtabileceğimiz bir SQL betiği elde etmek isteriz.
- Update-Database komutunu çalıştırın ancak bu kez değişikliklerin uygulanmak yerine bir betiğe yazılması için –Script bayrağını belirtin. Betiği oluşturmak için bir kaynak ve hedef geçiş de belirteceğiz. Betiğin boş bir veritabanından ($InitialDatabase) en son sürüme ( AddPostAbstract geçişi) kadar uzanmasını istiyoruz. Hedef geçiş belirtmezseniz, Migrations aracı hedef olarak en son geçişi kullanır. Kaynak geçiş belirtmezseniz, Migrations aracı veritabanının mevcut durumunu kullanır.
- Paket Yöneticisi Konsolu’nda Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract komutunu çalıştırın
Code First Migrations geçiş işlem hattını çalıştırır ancak değişiklikleri uygulamak yerine sizin için bir .sql dosyasına yazar. Betik oluşturulduktan sonra Visual Studio’da açılır. Açılan betiği görüntüleyebilir veya kaydedebilirsiniz.
Bir Kez Etkili Betikler Oluşturma
EF6 sürümünden itibaren –SourceMigration $InitialDatabase bayrağını belirttiğinizde oluşturulan betik “bir kez etkili” olur. Bir kez etkili betikler şu anda herhangi bir sürümdeki veritabanını en son sürüme (veya –TargetMigration kullanırsanız belirtilen sürüme) yükseltebilir. Oluşturulan betik, __MigrationsHistory tablosunu denetleme ve yalnızca daha önce uygulanmamış değişiklikleri uygulama mantığını içerir.
Uygulama Başlangıcında Otomatik Yükseltme (MigrateDatabaseToLatestVersion Başlatıcı)
Uygulamanızı dağıtıyorsanız, uygulama başlatıldığında veritabanını otomatik olarak yükseltmesini (ve bekleyen geçişleri uygulamasını) isteyebilirsiniz. Bunu yapmak için MigrateDatabaseToLatestVersion veritabanı başlatıcısını kaydedebilirsiniz. Veritabanı başlatıcı, veritabanının doğru ayarlandığından emin olmak için kullanılan mantığı içerir. Bu mantık, bağlam uygulama işlemi (AppDomain) içinde ilk kez kullanıldığında çalıştırılır.
Bağlamı kullanmadan önce BlogContext için MigrateDatabaseToLatestVersion başlatıcısını ayarlamak üzere program.cs dosyasını aşağıda gösterildiği gibi güncelleştirebiliriz (14. satır). System.Data.Entity ad alanı (5. satır) için bir “using” deyimi de eklemeniz gerektiğini unutmayın.
Bu başlatıcının bir örneğini oluştururken bağlam türünü (BlogContext) ve geçiş yapılandırmasını (Configuration) belirtmemiz gerekir. Geçiş yapılandırması, Migrations aracını etkinleştirdiğimizde Migrations klasörünüze eklenen sınıftır.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using MigrationsDemo.Migrations;
namespace MigrationsDemo
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());
using (var db = new BlogContext())
{
db.Blogs.Add(new Blog { Name = "Another Blog " });
db.SaveChanges();
foreach (var blog in db.Blogs)
{
Console.WriteLine(blog.Name);
}
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Artık uygulamamız her çalıştırıldığında ilk olarak hedeflediği veritabanının güncel olup olmadığını denetleyecek ve güncel değişse bekleyen geçişleri uygulayacak.