Aracılığıyla paylaş


Özel Kod İlk Kuralları

Uyarı

YALNıZCA EF6'ya Doğru - Bu sayfada ele alınan özellikler, API'ler vb. Entity Framework 6'da sunulmuştur. Önceki bir sürümü kullanıyorsanız, bilgilerin bazıları veya tümü geçerli değildir.

Code First kullanırken modeliniz sınıflarınızdan bir kural kümesi kullanılarak hesaplanır. Varsayılan Code First Conventions özelliğin bir varlığın birincil anahtarı olduğu, varlığın eşlendiği tablonun adı ve bir ondalık sütunun varsayılan olarak hangi duyarlığa ve ölçeklendirmeye sahip olduğu gibi öğeleri belirler.

Bazen bu varsayılan kurallar modeliniz için ideal değildir ve Veri Ek Açıklamalarını veya Fluent API'sini kullanarak birçok ayrı varlığı yapılandırarak bunlara geçici bir çözüm getirebilirsiniz. Özel Kod İlk Kuralları, modeliniz için yapılandırma varsayılanları sağlayan kendi kurallarınızı tanımlamanıza olanak tanır. Bu kılavuzda, farklı özel kural türlerini ve bunların her birini nasıl oluşturacağımızı keşfedeceğiz.

Model Tabanlı Sözleşmeler

Bu sayfa, özel kurallar için DbModelBuilder API'sini kapsar. Bu API, çoğu özel kuralı yazmak için yeterli olmalıdır. Ancak, gelişmiş senaryoları işlemek için model tabanlı kurallar (oluşturulduktan sonra son modeli işleyen kurallar) yazma özelliği de vardır. Daha fazla bilgi için bkz. Model-Based Kuralları.

 

Modelimiz

İlk olarak kurallarımızla kullanabileceğimiz basit bir model tanımlayalım. Projenize aşağıdaki sınıfları ekleyin.

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;

    public class ProductContext : DbContext
    {
        static ProductContext()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
        }

        public DbSet<Product> Products { get; set; }
    }

    public class Product
    {
        public int Key { get; set; }
        public string Name { get; set; }
        public decimal? Price { get; set; }
        public DateTime? ReleaseDate { get; set; }
        public ProductCategory Category { get; set; }
    }

    public class ProductCategory
    {
        public int Key { get; set; }
        public string Name { get; set; }
        public List<Product> Products { get; set; }
    }

 

Özel Kurallar'a Giriş

Key adlı herhangi bir özelliği varlık türü için birincil anahtar olacak şekilde yapılandıran bir kural yazalım.

Kurallar model oluşturucuda etkinleştirilir ve bu, bağlamda OnModelCreating'i geçersiz kılarak erişilebilir. ProductContext sınıfını aşağıdaki gibi güncelleştirin:

    public class ProductContext : DbContext
    {
        static ProductContext()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductContext>());
        }

        public DbSet<Product> Products { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Properties()
                        .Where(p => p.Name == "Key")
                        .Configure(p => p.IsKey());
        }
    }

Şimdi, modelimizin Key adlı herhangi bir özelliği, parçası olduğu varlığın birincil anahtarı olarak yapılandırılacaktır.

Yapılandıracağımız özellik türüne göre filtreleyerek kurallarımızı daha belirgin hale de getirebiliriz:

    modelBuilder.Properties<int>()
                .Where(p => p.Name == "Key")
                .Configure(p => p.IsKey());

Bu, Key adlı özelliklerin tümünü, sadece bir tamsayı oldukları takdirde, varlıklarının birincil anahtarı olacak şekilde yapılandırır.

IsKey yönteminin ilginç bir özelliği, eklemeli olmasıdır. Bu, birden çok özellik üzerinde IsKey'i çağırırsanız ve bunların tümünün bileşik anahtarın parçası olacağı anlamına gelir. Bunun için bir uyarı, bir anahtar için birden çok özellik belirttiğinizde, bu özellikler için de bir sıra belirtmeniz gerektiğidir. Bunu, aşağıdaki gibi HasColumnOrder yöntemini çağırarak yapabilirsiniz:

    modelBuilder.Properties<int>()
                .Where(x => x.Name == "Key")
                .Configure(x => x.IsKey().HasColumnOrder(1));

    modelBuilder.Properties()
                .Where(x => x.Name == "Name")
                .Configure(x => x.IsKey().HasColumnOrder(2));

Bu kod, modelimizdeki türleri int Key sütunundan ve dize Adı sütunundan oluşan bileşik bir anahtara sahip olacak şekilde yapılandıracaktır. Modeli tasarımcıda görüntülersek şu şekilde görünür:

bileşik Anahtar

Özellik kurallarının bir diğer örneği de modelimdeki tüm DateTime özelliklerini tarih saat yerine SQL Server'daki datetime2 türüne eş olacak şekilde yapılandırmaktır. Bunu aşağıdakilerle gerçekleştirebilirsiniz:

    modelBuilder.Properties<DateTime>()
                .Configure(c => c.HasColumnType("datetime2"));

 

Konvansiyon Sınıfları

Kuralları tanımlamanın bir diğer yolu da kuralınızı kapsüllemek için Bir Convention Sınıfı kullanmaktır. Bir Convention Sınıfı kullanırken System.Data.Entity.ModelConfiguration.Conventions ad alanındaki Convention sınıfından devralan bir tür oluşturursunuz.

Aşağıdakileri yaparak daha önce gösterdiğimiz datetime2 kuralıyla bir Convention Sınıfı oluşturabiliriz:

    public class DateTime2Convention : Convention
    {
        public DateTime2Convention()
        {
            this.Properties<DateTime>()
                .Configure(c => c.HasColumnType("datetime2"));        
        }
    }

EF'ye bu konvansiyonu kullanmasını söylemek için, bunu OnModelCreating'deki Conventions koleksiyonuna eklersiniz. Adım adım ilerlemeyi takip ettiyseniz, bu şöyle görünecektir:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Properties<int>()
                    .Where(p => p.Name.EndsWith("Key"))
                    .Configure(p => p.IsKey());

        modelBuilder.Conventions.Add(new DateTime2Convention());
    }

Gördüğünüz gibi kural koleksiyonuna kuralımızın bir örneğini ekliyoruz. Konvansiyondan devralma, takımlar veya projeler arasında konvansiyonları gruplandırma ve paylaşmanın kullanışlı bir yolunu sağlar. Örneğin, tüm kuruluş projelerinizin kullandığı ortak bir kural kümesine sahip bir sınıf kitaplığınız olabilir.

 

Özel Öznitelikler

Kuralların bir diğer harika kullanımı da modeli yapılandırırken yeni özniteliklerin kullanılmasını sağlamaktır. Bunu göstermek için, Dize özelliklerini Unicode olmayan olarak işaretlemek için kullanabileceğimiz bir öznitelik oluşturalım.

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class NonUnicode : Attribute
    {
    }

Şimdi bu özniteliği modelimize uygulamak için bir kural oluşturalım:

    modelBuilder.Properties()
                .Where(x => x.GetCustomAttributes(false).OfType<NonUnicode>().Any())
                .Configure(c => c.IsUnicode(false));

Bu kuralla, dize özelliklerimizden herhangi birine NonUnicode özniteliğini ekleyebiliriz; bu da veritabanındaki sütunun nvarchar yerine varchar olarak depolanacağı anlamına gelir.

Bu kuralla ilgili dikkat edilecek bir nokta, NonUnicode özniteliğini bir dize özelliği dışında bir şeye koyarsanız bir özel durum oluşturmasıdır. Bunu yapar çünkü IsUnicode'ı dize dışında herhangi bir türde yapılandıramazsınız. Böyle bir durumda kuralınızı daha belirgin hale getirerek dize olmayan her şeyi filtreleyebilirsiniz.

Yukarıdaki kural özel öznitelikleri tanımlamak için işe yarasa da, özellikle öznitelik sınıfından özellikleri kullanmak istediğinizde kullanımı çok daha kolay olabilecek başka bir API vardır.

Bu örnekte özniteliğimizi güncelleştirecek ve bir IsUnicode özniteliğiyle değiştireceğiz, böylece şöyle görünür:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    internal class IsUnicode : Attribute
    {
        public bool Unicode { get; set; }

        public IsUnicode(bool isUnicode)
        {
            Unicode = isUnicode;
        }
    }

Bunu elde ettikten sonra, bir özelliğin Unicode olup olmaması gerektiğini kurala bildirmek için özniteliğimizde bir bool ayarlayabiliriz. Bunu, yapılandırma sınıfının ClrProperty'sine şu şekilde erişerek zaten sahip olduğumuz kuralda yapabiliriz:

    modelBuilder.Properties()
                .Where(x => x.GetCustomAttributes(false).OfType<IsUnicode>().Any())
                .Configure(c => c.IsUnicode(c.ClrPropertyInfo.GetCustomAttribute<IsUnicode>().Unicode));

Bu yeterince kolaydır, ancak kurallar API'sinin Having yöntemini kullanarak bunu başarmanın daha kısa bir yolu vardır. Having yönteminin, PropertyInfo değerini Where yöntemiyle aynı kabul eden ancak bir nesne döndürmesi beklenen Func<PropertyInfo, T> türünde bir parametresi vardır. Döndürülen nesne null ise, özellik yapılandırılmaz; bu da where gibi özellikleri filtreleyebileceğiniz, ancak döndürülen nesneyi yakalayıp Configure yöntemine geçirmesi farklı olduğu anlamına gelir. Bu, aşağıdaki gibi çalışır:

    modelBuilder.Properties()
                .Having(x => x.GetCustomAttributes(false).OfType<IsUnicode>().FirstOrDefault())
                .Configure((config, att) => config.IsUnicode(att.Unicode));

Having yöntemini kullanmanın tek nedeni özel öznitelikler değildir, türlerinizi veya özelliklerinizi yapılandırırken filtrelediğiniz bir şey hakkında düşünmeniz gereken her yerde kullanışlıdır.

 

Türleri Yapılandırma

Şimdiye kadar tüm kurallarımız özelliklere yöneliktir, ancak modelinizde türleri yapılandırmak için kural API'sinin başka bir alanı vardır. Deneyim, şu ana kadar gördüğümüz kavramlarla benzerdir, ancak yapılandırma içindeki seçenekler özellik düzeyi yerine varlık düzeyinde olacaktır.

Tür düzeyi kurallarının gerçekten yararlı olabileceği şeylerden biri, EF varsayılanından farklı olan mevcut bir şemayla eşlemek veya farklı bir adlandırma kuralına sahip yeni bir veritabanı oluşturmak için tablo adlandırma kuralını değiştirmektir. Bunu yapmak için öncelikle modelimizdeki bir türün TypeInfo değerini kabul eden ve bu tür için tablo adının ne olması gerektiğini döndürebilen bir yönteme ihtiyacımız var:

    private string GetTableName(Type type)
    {
        var result = Regex.Replace(type.Name, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);

        return result.ToLower();
    }

Bu yöntem bir tür alır ve CamelCase yerine alt çizgilerle küçük harf kullanan bir dize döndürür. Modelimizde bu, ProductCategory sınıfının ProductCategories yerine product_category adlı bir tabloyla eşlendiği anlamına gelir.

Bu yönteme sahip olduktan sonra bunu aşağıdaki gibi bir kuralda çağırabiliriz:

    modelBuilder.Types()
                .Configure(c => c.ToTable(GetTableName(c.ClrType)));

Bu kural, modelimizdeki her türü GetTableName yöntemimizden döndürülen tablo adıyla eş olacak şekilde yapılandırır. Bu kural, Fluent API'sini kullanarak modeldeki her varlık için ToTable yöntemini çağırmaya eşdeğerdir.

Bu konuda dikkat edilmesi gereken bir nokta, ToTable'i çağırdığınızda EF, sağladığınız dizeyi tam tablo adı olarak alır ve tablo adlarını belirlerken normalde yapacağı çoğullaştırmayı uygulamaz. Bu nedenle kullandığımız kurala göre tablo adı product_categories yerine product_category. Çoğullaştırma hizmetine bir çağrı yaparak bunu sözleşmemizde çözebiliriz.

Aşağıdaki kodda EF6'ya eklenen Bağımlılık Çözümleme özelliğini kullanarak EF'nin kullanacağı çoğullaştırma hizmetini alacaktır ve tablo adımızı çoğullaştıracağız.

    private string GetTableName(Type type)
    {
        var pluralizationService = DbConfiguration.DependencyResolver.GetService<IPluralizationService>();

        var result = pluralizationService.Pluralize(type.Name);

        result = Regex.Replace(result, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);

        return result.ToLower();
    }

Uyarı

GetService'in genel sürümü System.Data.Entity.Infrastructure.DependencyResolution ad alanındaki bir uzantı yöntemidir, bunu kullanmak için bağlamınıza bir using deyimi eklemeniz gerekir.

ToTable ve Kalıtım

ToTable'ın bir diğer önemli yönü, bir türü belirli bir tabloyla açıkça eşlerseniz EF'in kullanacağı eşleme stratejisini değiştirebilmenizdir. Devralma hiyerarşisindeki her tür için tür adını, yukarıda örneklendiği gibi, tablonun adı olarak belirtip ToTable'ı çağırırsanız, varsayılan Tablo Başına Hiyerarşi (TPH) eşleme stratejisini Tablo Başına Tür (TPT) olarak değiştirirsiniz. Bunu tanımlamanın en iyi yolu, somut bir örnektir:

    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Manager : Employee
    {
        public string SectionManaged { get; set; }
    }

Varsayılan olarak hem çalışan hem de yönetici veritabanındaki aynı tabloya (Çalışanlar) eşlenir. Tabloda, her satırda ne tür bir örneğin depolandığını belirten ayrıştırıcı sütunu olan hem çalışanlar hem de yöneticiler yer alır. Hiyerarşi için tek bir tablo olduğundan bu TPH eşlemesidir. Bununla birlikte, her iki sınıf için de ToTable'ı çağırırsanız, bu kez her tür kendi tablosuna eşlenir ve bu durum her türün kendi tablosuna sahip olması sebebiyle TPT olarak da bilinir.

    modelBuilder.Types()
                .Configure(c=>c.ToTable(c.ClrType.Name));

Yukarıdaki kod aşağıdaki gibi görünen bir tablo yapısıyla eşlenir:

tpt Örneği

Bunu önleyebilir ve varsayılan TPH eşlemesini birkaç yolla koruyabilirsiniz:

  1. Hiyerarşideki her tür için aynı tablo adıyla ToTable'ı çağırın.
  2. ToTable'ı yalnızca hiyerarşinin temel sınıfında, örneğimizde bu sınıf 'çalışan' olacaktır, çağırın.

 

Yürütme Sırası

Kurallar, Fluent API'de olduğu gibi "son geçerli olan kazanır" yöntemiyle çalışır. Bunun anlamı, aynı özelliğin aynı seçeneğini yapılandıran iki kural yazarsanız, yürütülecek son kuralın kazanmasıdır. Örneğin, tüm dizelerin uzunluk üst sınırının altındaki kodda 500 olarak ayarlanır, ancak modelde Ad adlı tüm özellikleri en fazla 250 uzunluğunda olacak şekilde yapılandırıyoruz.

    modelBuilder.Properties<string>()
                .Configure(c => c.HasMaxLength(500));

    modelBuilder.Properties<string>()
                .Where(x => x.Name == "Name")
                .Configure(c => c.HasMaxLength(250));

Maksimum uzunluğu 250 olarak ayarlama kuralı, tüm dizeleri 500 olarak ayarlayan kuraldan sonra olduğundan, modelimizdeki Ad adlı tüm özelliklerin MaxLength değeri 250 olurken, açıklamalar gibi diğer tüm dizeler 500 olur. Kuralları bu şekilde kullanmak, modelinizdeki türler veya özellikler için genel bir kural sağlayabileceğiniz ve ardından farklı alt kümeler için bunları aşırı kullanabileceğiniz anlamına gelir.

Fluent API ve Veri Ek Açıklamaları, belirli durumlarda bir kuralı geçersiz kılmak için de kullanılabilir. Yukarıdaki örnekte, bir özelliğin en uzun uzunluğunu ayarlamak için Fluent API'sini kullandıysak, daha belirli Fluent API'sinin daha genel Yapılandırma Kuralı'nı kazanacağı için bunu kuraldan önce veya sonra koyabilirdik.

 

Yerleşik Konvansiyonlar

Özel kurallar varsayılan Code First kurallarından etkilenebileceğinden, başka bir kuraldan önce veya sonra çalıştırılacak kurallar eklemek yararlı olabilir. Bunu yapmak için türetilmiş DbContext'inizde Conventions koleksiyonunun AddBefore ve AddAfter yöntemlerini kullanabilirsiniz. Aşağıdaki kod, yerleşik anahtar bulma kuralından önce çalışması için daha önce oluşturduğumuz kural sınıfını ekler.

    modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new DateTime2Convention());

Bu, yerleşik kurallardan önce veya sonra çalıştırılması gereken kurallar eklerken en çok kullanılacaktır. Yerleşik kuralların listesi burada bulunabilir: System.Data.Entity.ModelConfiguration.Conventions Ad Alanı.

Modelinize uygulanmasını istemediğiniz kuralları da kaldırabilirsiniz. Bir kuralı kaldırmak için Remove yöntemini kullanın. İşte PluralizingTableNameConvention kaldırma örneği.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }