Özel Kod İlk Kuralları

Dekont

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 bir kısmı 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ı Kurallar

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 Tabanlı 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şturucusunda etkinleştirilir ve bu kurala bağlamda OnModelCreating geçersiz kılınarak 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ı tüm özellikleri varlıklarının birincil anahtarı olacak şekilde yapılandıracak, ancak yalnızca bir tamsayıysa.

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:

composite Key

Ö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"));

 

Kural 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 kuralı kullanmasını söylemek için, bunu OnModelCreating'deki Conventions koleksiyonuna eklersiniz; bu, izlenecek yol ile birlikte izlenecekse şöyle görünür:

    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. Kural'dan devralma, takımlar veya projeler arasında kuralları 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 kurallarla benzerdir, ancak yapılandırma içindeki seçenekler özellik düzeyi yerine varlıkta 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 EF'yi çağırdığınızda, tablo adlarını belirlerken normalde yapacağı çoğullaştırmadan tam tablo adı olarak sağladığınız dizeyi almasıdır. Bu nedenle kuralımızdaki tablo adı product_categories yerine product_category. Çoğullaştırma hizmetine bir çağrı yaparak bunu kuralımızda çö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();
    }

Dekont

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 Devralma

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 ToTable'ı çağırırsanız, tür adını yukarıda yaptığımız gibi tablonun adı olarak geçirirseniz, varsayılan Hiyerarşi Başına Tablo (TPH) eşleme stratejisini Tür Başına Tablo (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, her tür kendi tablosuna sahip olduğundan her tür TPT olarak da bilinen kendi tablosuna eşlenir.

    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 Example

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 çalışan olacak şekilde çağırın.

 

Yürütme Sırası

Kurallar, Fluent API'sinde olduğu gibi son bir kazançla ç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 Kurallar

Ö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. PluralizingTableNameConvention kaldırma örneği aşağıda verilmiştir.

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