Aracılığıyla paylaş


Entity Framework Core ile altyapı kalıcılık katmanını uygulama

Tavsiye

Bu içerik, .NET Docs veya çevrimdışı olarak okunabilen ücretsiz indirilebilir bir PDF olarak sağlanan Kapsayıcılı .NET Uygulamaları için .NET Mikro Hizmet Mimarisi adlı e-Kitap'tan bir alıntıdır.

.NET Mikro Hizmetler Mimarisi Kapsayıcılı .NET Uygulamaları için eKitabın kapak küçük resmi .

SQL Server, Oracle veya PostgreSQL gibi ilişkisel veritabanları kullandığınızda, Entity Framework(EF) tabanlı kalıcılık katmanını uygulamak önerilen bir yaklaşımdır. EF, LINQ'yi destekler ve modeliniz için kesin olarak yazılan nesnelerin yanı sıra veritabanınızda basitleştirilmiş kalıcılık sağlar.

Entity Framework, .NET Framework'ün bir parçası olarak uzun bir geçmişe sahiptir. .NET kullanırken, Windows veya Linux üzerinde .NET ile aynı şekilde çalışan Entity Framework Core da kullanmanız gerekir. EF Core, daha küçük bir yer kaplayan ve performansta önemli iyileştirmeler içeren Entity Framework'ün tamamen yeniden yazılmış bir sürümüdür.

Entity Framework Core'a giriş

Entity Framework (EF) Core, popüler Entity Framework veri erişim teknolojisinin basit, genişletilebilir ve platformlar arası bir sürümüdür. 2016'nın ortasında .NET Core ile kullanıma sunulmuştur.

EF Core'a giriş microsoft belgelerinde zaten mevcut olduğundan, burada bu bilgilere bağlantılar sağlıyoruz.

Ek kaynaklar

DDD perspektifinden Entity Framework Core'da altyapı

DDD açısından bakıldığında EF'nin önemli bir özelliği, EF terminolojisinde POCO kod öncelikli varlıklar olarak da bilinen POCO etki alanı varlıklarını kullanabilmektir. POCO etki alanı varlıklarını kullanıyorsanız, etki alanı modeli sınıflarınız kalıcılığı göz ardı eden yapılar olup, Kalıcılığı Göz Ardı Etme ve Altyapıyı Göz Ardı Etme ilkelerine uyar.

DDD desenlerine göre, herhangi bir koleksiyona erişirken sabitleri, doğrulamaları ve kuralları denetleyebilmesi için varlık sınıfının kendi alan davranışlarını ve kurallarını kapsüllemeniz gerekir. Bu nedenle, DDD'de alt varlıkların veya değer nesnelerinin koleksiyonlarına genel erişime izin vermek iyi bir uygulama değildir. Bunun yerine, alanlarınızın ve özellik koleksiyonlarınızın nasıl ve ne zaman güncelleştirilebileceğini ve bu durumda hangi davranış ve eylemlerin gerçekleşebileceğini denetleyebilen yöntemleri kullanıma sunmanız gerekir.

EF Core 1.1'den beri, DDD gereksinimlerini karşılamak için nesnelerinizde genel özellikler yerine basit alanlar kullanabilirsiniz. Bir varlık alanının dışarıdan erişilebilir olmasını istemiyorsanız, özellik yerine yalnızca özniteliği veya alanı oluşturabilirsiniz. Gizli özellik ayarlarını da kullanabilirsiniz.

Benzer şekilde, varlığınızda, kalıcılık için EF'ye dayanan ve koleksiyon için özel bir alan üyesiyle desteklenen IReadOnlyCollection<T> türünde bir ortak özellik kullanarak koleksiyonlara artık salt okunur erişiminiz olabilir (örneğin List<T> gibi). Entity Framework'ün önceki sürümlerinde, koleksiyon özelliklerinin ICollection<T> desteklemesi gerekiyordu, bu da üst varlık sınıfını kullanan herhangi bir geliştiricinin özellik koleksiyonları aracılığıyla öğe ekleyebileceği veya kaldırabileceği anlamına geliyordu. Bu olasılık DDD'de önerilen desenlere karşı olabilir.

Aşağıdaki kod örneğinde gösterildiği gibi, salt okunur IReadOnlyCollection<T> bir nesneyi gösterirken özel bir koleksiyon kullanabilirsiniz:

public class Order : Entity
{
    // Using private fields, allowed since EF Core 1.1
    private DateTime _orderDate;
    // Other fields ...

    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

    protected Order() { }

    public Order(int buyerId, int paymentMethodId, Address address)
    {
        // Initializations ...
    }

    public void AddOrderItem(int productId, string productName,
                             decimal unitPrice, decimal discount,
                             string pictureUrl, int units = 1)
    {
        // Validation logic...

        var orderItem = new OrderItem(productId, productName,
                                      unitPrice, discount,
                                      pictureUrl, units);
        _orderItems.Add(orderItem);
    }
}

özelliğine yalnızca OrderItems kullanılarak salt okunur olarak IReadOnlyCollection<OrderItem> erişilebilir. Bu tür salt okunur olduğundan normal dış güncelleştirmelere karşı korunur.

EF Core, etki alanı modelini "kirletmeden" fiziksel veritabanına eşlemek için bir yol sağlar. Eşleme eylemi kalıcılık katmanında uygulandığından saf .NET POCO kodudur. Bu eşleme eyleminde alanlar arası eşlemeyi yapılandırmanız gerekir. Aşağıdaki OnModelCreating yönteminin OrderingContext ve OrderEntityTypeConfiguration sınıfı örneğinde, SetPropertyAccessMode çağrısı, EF Core'a OrderItems özelliğine alanı üzerinden erişmesini söyler.

// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   // ...
   modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
   // Other entities' configuration ...
}

// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> orderConfiguration)
    {
        orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
        // Other configuration

        var navigation =
              orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));

        //EF access the OrderItem collection property through its backing field
        navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

        // Other configuration
    }
}

Özellikler yerine alanlar kullandığınızda, OrderItem varlık bir List<OrderItem> özelliği varmış gibi kalıcı olur. Ancak, siparişe yeni öğeler eklemek için AddOrderItem yöntemini açığa çıkaran tek bir erişimci sunar. Sonuç olarak, davranış ve veriler birbirine bağlanır ve etki alanı modelini kullanan tüm uygulama kodları boyunca tutarlı olur.

Entity Framework Core ile özel depolar uygulama

Uygulama düzeyinde depo, aşağıdaki sınıfta gösterildiği gibi güncelleştirmeleri gerçekleştirirken bir iş birimi (EF Core'da DBContext) tarafından koordine edilen veri kalıcılığı koduna sahip bir sınıftır:

// using directives...
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class BuyerRepository : IBuyerRepository
    {
        private readonly OrderingContext _context;
        public IUnitOfWork UnitOfWork
        {
            get
            {
                return _context;
            }
        }

        public BuyerRepository(OrderingContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }

        public Buyer Add(Buyer buyer)
        {
            return _context.Buyers.Add(buyer).Entity;
        }

        public async Task<Buyer> FindAsync(string buyerIdentityGuid)
        {
            var buyer = await _context.Buyers
                .Include(b => b.Payments)
                .Where(b => b.FullName == buyerIdentityGuid)
                .SingleOrDefaultAsync();

            return buyer;
        }
    }
}

Arayüz, IBuyerRepository etki alanı modeli katmanından sözleşme olarak gelir. Ancak, depo uygulaması kalıcılık ve altyapı katmanında gerçekleştirilir.

EF DbContext, Bağımlılık Ekleme aracılığıyla oluşturucudan gelir. IoC kapsayıcısındaki varsayılan ömrü (ServiceLifetime.Scoped) (bu, aynı zamanda services.AddDbContext<> ile açıkça ayarlanabilir) sayesinde, aynı HTTP isteği kapsamındaki birden fazla depo arasında paylaşılır.

Bir depoda uygulama yöntemleri (güncelleştirmeler veya işlemler ve sorgular)

Her depo sınıfına, ilgili toplamanın içerdiği varlıkların durumunu güncelleştiren kalıcılık yöntemlerini yerleştirmeniz gerekir. Bir bütün ile ilişkili depolar arasında bire bir ilişki olduğunu unutmayın. Bir toplama kök varlık nesnesinin EF grafiği içinde gömülü alt varlıklara sahip olabileceğini unutmayın. Örneğin, bir alıcı, ilgili alt varlıklar olarak birden çok ödeme yöntemine sahip olabilir.

eShopOnContainers'da mikro hizmet sipariş etme yaklaşımı da CQS/CQRS'yi temel aldığından, sorguların çoğu özel depolarda uygulanmaz. Geliştiriciler, toplamalar, toplama başına özel depolar ve genel olarak DDD tarafından uygulanan kısıtlamalar olmadan sunu katmanı için ihtiyaç duydukları sorguları ve birleşimleri oluşturma özgürlüğüne sahiptir. Bu kılavuz tarafından önerilen özel depoların çoğunda birkaç güncelleştirme veya işlem yöntemi vardır, ancak yalnızca güncelleştirilecek verileri almak için gereken sorgu yöntemleri vardır. Örneğin, uygulama siparişle ilgili yeni bir alıcı oluşturmadan önce belirli bir alıcının var olup olmadığını bilmesi gerektiğinden, BuyerRepository deposu bir FindAsync yöntemini uygular.

Ancak, sunu katmanına veya istemci uygulamalarına gönderilecek verileri almak için gerçek sorgu yöntemleri, Dapper kullanan esnek sorgulara dayalı CQRS sorgularında belirtildiği gibi uygulanır.

Özel depo kullanma ile EF DbContext'i doğrudan kullanma karşılaştırması

Entity Framework DbContext sınıfı, İş Birimi ve Depo desenlerini temel alır ve ASP.NET Core MVC denetleyicisi gibi doğrudan kodunuzdan kullanılabilir. İş Birimi ve Depo desenleri, eShopOnContainers'daki CRUD kataloğu mikro hizmetinde olduğu gibi en basit kodla sonuçlanır. Mümkün olan en basit kodu istediğiniz durumlarda, birçok geliştiricinin yaptığı gibi doğrudan DbContext sınıfını kullanmak isteyebilirsiniz.

Ancak özel depoların uygulanması, daha karmaşık mikro hizmetler veya uygulamalar uygularken çeşitli avantajlar sağlar. Çalışma Birimi ve Depo desenleri, uygulama ve etki alanı modeli katmanlarından ayrıştırılmak üzere altyapı kalıcılık katmanını kapsüllemek üzere tasarlanmıştır. Bu desenlerin uygulanması, veritabanına erişimi taklit eden sahte depoların kullanımını kolaylaştırabilir.

Şekil 7-18'de, depoları kullanmama (doğrudan EF DbContext kullanarak) ile depoları kullanma arasındaki farkları görebilirsiniz ve bu da bu depolarla dalga geçmeyi kolaylaştırır.

İki depodaki bileşenleri ve veri akışını gösteren diyagram.

Şekil 7-18. Özel depoları düz DbContext yerine kullanma

Şekil 7-18'de özel depo kullanmanın, depoyla dalga geçerek testi kolaylaştırmak için kullanılabilecek bir soyutlama katmanı eklediği gösterilmiştir. Sahte yaparken birden çok alternatif vardır. Sadece depolar ile dalga geçebilirsiniz ya da bütün bir iş birimiyle dalga geçebilirsiniz. Genellikle yalnızca depoların sahtesini yapmak yeterlidir ve bir çalışma birimini tamamen soyutlamanın ve sahtesini yapmanın karmaşıklığı genellikle gerekli değildir.

Daha sonra uygulama katmanına odaklandığımızda Bağımlılık Ekleme'nin ASP.NET Core'da nasıl çalıştığını ve depoları kullanırken nasıl uygulandığını göreceksiniz.

Kısacası özel depolar, veri katmanı durumundan etkilenmeyen birim testleriyle kodu daha kolay test etmenizi sağlar. Entity Framework aracılığıyla gerçek veritabanına da erişen testler çalıştırırsanız, bunlar birim testleri değil, çok daha yavaş olan tümleştirme testleridir.

DbContext'i doğrudan kullanıyorsanız, bunu taklit etmeniz veya birim testleri için tahmin edilebilir verilerle bellek içi SQL Server kullanarak birim testleri çalıştırmanız gerekirdi. Ancak DbContext'i taklit etmek veya sahte verileri kontrol etmek, depo düzeyinde taklit etmekten daha fazla çalışma gerektirir. Elbette, MVC denetleyicilerini her zaman test edebilirsiniz.

IoC kapsayıcınızda EF DbContext ve IUnitOfWork örneklerinin yaşam süresi

Nesne DbContext (nesne IUnitOfWork olarak sunulan) aynı HTTP isteği kapsamındaki birden çok depolar arasında paylaşılmalıdır. Örneğin, yürütülen işlemin birden çok toplamayla ilgilenmesi gerektiğinde veya yalnızca birden çok depo örneği kullandığınız için bu durum geçerlidir. Ayrıca, IUnitOfWork arabiriminin EF Core türünde değil, etki alanı katmanınızın bir parçası olduğundan bahsetmek de önemlidir.

Bunu yapmak için nesne örneğinin DbContext hizmet ömrü ServiceLifetime.Scoped olarak ayarlanmış olması gerekir. Bu varsayılan yaşam süresi, ASP.NET Core Web API projenizdeki DbContext dosyasından builder.Services.AddDbContext ile IoC kapsayıcınıza kaydederken olacak. Aşağıdaki kodda bu gösterilmektedir.

// Add framework services.
builder.Services.AddMvc(options =>
{
    options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices();

builder.Services.AddEntityFrameworkSqlServer()
    .AddDbContext<OrderingContext>(options =>
    {
        options.UseSqlServer(Configuration["ConnectionString"],
                            sqlOptions => sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().
                                                                                Assembly.GetName().Name));
    },
    ServiceLifetime.Scoped // Note that Scoped is the default choice
                            // in AddDbContext. It is shown here only for
                            // pedagogic purposes.
    );

DbContext örnekleme modu ServiceLifetime.Transient veya ServiceLifetime.Singleton olarak yapılandırılmamalıdır.

IoC kapsayıcınızdaki depo nesnesi ömrü

Benzer şekilde, deponun (repository) ömrü genellikle kapsamla sınırlı olarak ayarlanmalıdır (Autofac'ta InstancePerLifetimeScope). Ayrıca geçici de olabilir (Autofac'ta InstancePerDependency), ancak kapsamı belirlenmiş yaşam süresi kullanılırken hizmetiniz bellek açısından daha verimli olacaktır.

// Registering a Repository in Autofac IoC container
builder.RegisterType<OrderRepository>()
    .As<IOrderRepository>()
    .InstancePerLifetimeScope();

DbContext'iniz kapsamlı (InstancePerLifetimeScope) yaşam süresi (DbContext için varsayılan yaşam süresi) olarak ayarlandığında depo için tek nesne yaşam süresinin kullanılması ciddi eşzamanlılık sorunlarına neden olabilir. Hem depolarınızın hem de DbContext'inizin hizmet ömrü kapsamlı olduğu sürece bu sorunlardan kaçınırsınız.

Ek kaynaklar

Tablo eşlemesi

Tablo eşlemesi, sorgulanacak ve veritabanına kaydedilecek tablo verilerini tanımlar. Daha önce etki alanı varlıklarının (örneğin, bir ürün veya sipariş etki alanı) ilgili bir veritabanı şeması oluşturmak için nasıl kullanılabileceğini gördünüz. EF, kurallar kavramı çerçevesinde güçlü bir şekilde tasarlanmıştır. Kurallar, "Tablonun adı ne olacak?" veya "Birincil anahtar hangi özellik?" gibi soruları ele alır. Kurallar genellikle geleneksel adlara dayanır. Örneğin, birincil anahtarın ile Idbiten bir özellik olması normaldir.

Geleneklere göre, her varlık, türetilmiş bağlamda varlığı ortaya çıkaran özelliğiyle aynı ada sahip bir tabloya eşlenecek şekilde yapılandırılır. Verilen varlık için değer DbSet<TEntity> sağlanmazsa, sınıf adı kullanılır.

Veri Ek Açıklamaları ve Fluent API karşılaştırması

Birçok ek EF Core kuralı vardır ve bunların çoğu, OnModelCreating yönteminde uygulanan veri ek açıklamaları veya Fluent API'si kullanılarak değiştirilebilir.

Veri ek açıklamaları, DDD açısından daha müdahaleci bir yöntem olan varlık modeli sınıflarında kullanılmalıdır. Bunun nedeni, modelinizi altyapı veritabanıyla ilgili veri ek açıklamalarıyla kirletiyor olmanızdır. Öte yandan Fluent API, veri kalıcılığı altyapı katmanınızdaki çoğu kuralı ve eşlemeyi değiştirmenin kullanışlı bir yoludur; böylece varlık modeli temiz olur ve kalıcılık altyapısından ayrılır.

Fluent API ve OnModelCreating yöntemi

Belirtildiği gibi, kuralları ve eşlemeleri değiştirmek için DbContext sınıfında OnModelCreating yöntemini kullanabilirsiniz.

eShopOnContainers'daki sipariş mikro hizmeti, aşağıdaki kodda gösterildiği gibi gerektiğinde açık eşleme ve yapılandırma uygular.

// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   // ...
   modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
   // Other entities' configuration ...
}

// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> orderConfiguration)
    {
        orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);

        orderConfiguration.HasKey(o => o.Id);

        orderConfiguration.Ignore(b => b.DomainEvents);

        orderConfiguration.Property(o => o.Id)
            .UseHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);

        //Address value object persisted as owned entity type supported since EF Core 2.0
        orderConfiguration
            .OwnsOne(o => o.Address, a =>
            {
                a.WithOwner();
            });

        orderConfiguration
            .Property<int?>("_buyerId")
            .UsePropertyAccessMode(PropertyAccessMode.Field)
            .HasColumnName("BuyerId")
            .IsRequired(false);

        orderConfiguration
            .Property<DateTime>("_orderDate")
            .UsePropertyAccessMode(PropertyAccessMode.Field)
            .HasColumnName("OrderDate")
            .IsRequired();

        orderConfiguration
            .Property<int>("_orderStatusId")
            .UsePropertyAccessMode(PropertyAccessMode.Field)
            .HasColumnName("OrderStatusId")
            .IsRequired();

        orderConfiguration
            .Property<int?>("_paymentMethodId")
            .UsePropertyAccessMode(PropertyAccessMode.Field)
            .HasColumnName("PaymentMethodId")
            .IsRequired(false);

        orderConfiguration.Property<string>("Description").IsRequired(false);

        var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));

        // DDD Patterns comment:
        //Set as field (New since EF 1.1) to access the OrderItem collection property through its field
        navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

        orderConfiguration.HasOne<PaymentMethod>()
            .WithMany()
            .HasForeignKey("_paymentMethodId")
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        orderConfiguration.HasOne<Buyer>()
            .WithMany()
            .IsRequired(false)
            .HasForeignKey("_buyerId");

        orderConfiguration.HasOne(o => o.OrderStatus)
            .WithMany()
            .HasForeignKey("_orderStatusId");
    }
}

Tüm Fluent API eşlemelerini aynı OnModelCreating yöntem içinde ayarlayabilirsiniz, ancak örnekte gösterildiği gibi bu kodu bölümlemeniz ve varlık başına bir tane olmak üzere birden çok yapılandırma sınıfına sahip olmanız önerilir. Özellikle büyük modeller için farklı varlık türlerini yapılandırmak için ayrı yapılandırma sınıflarının olması önerilir.

Örnekteki kod birkaç açık bildirim ve eşleme gösterir. Ancak EF Core kuralları bu eşlemelerin çoğunu otomatik olarak yapar, bu nedenle sizin durumunuzda ihtiyacınız olan gerçek kod daha küçük olabilir.

EF Core'da Hi/Lo algoritması

Yukarıdaki örnekte kodun ilginç bir yönü, anahtar oluşturma stratejisi olarak Hi/Lo algoritmasını kullanmasıdır.

Hi/Lo algoritması, değişiklikleri işlemeden önce benzersiz anahtarlara ihtiyacınız olduğunda kullanışlıdır. Özet olarak, Hi-Lo algoritması satırın veritabanında hemen depolanmasına bağlı olmasa da tablo satırlarına benzersiz tanımlayıcılar atar. Bu, normal sıralı veritabanı kimliklerinde olduğu gibi tanımlayıcıları hemen kullanmaya başlamanızı sağlar.

Hi/Lo algoritması, ilgili veritabanı dizisinden bir dizi benzersiz kimlik almaya yönelik bir mekanizmayı açıklar. Veritabanı benzersizliği garanti ettiğinden, kullanıcılar arasında çakışma olmayacağından bu kimliklerin kullanımı güvenlidir. Bu algoritma şu nedenlerle ilgi çekicidir:

  • İş Birimi düzenini bozmaz.

  • Veritabanına gidiş dönüşleri en aza indirmek için sıralı kimlikleri toplu olarak alır.

  • GUID kullanan tekniklerden farklı olarak okunabilir bir tanımlayıcı oluşturur.

EF Core, önceki örnekte gösterildiği gibi yöntemiyleUseHiLo HiLo'yı destekler.

Özellikler yerine alanları eşleme

EF Core 1.1'den bu yana kullanılabilen bu özellik sayesinde sütunları doğrudan alanlarla eşleyebilirsiniz. Varlık sınıfında özellikleri kullanmamak ve yalnızca tablodaki sütunları alanlarla eşlemek mümkündür. Bunun için yaygın bir kullanım, varlığın dışından erişilmesi gerekmeyen herhangi bir iç durum için özel alanlar olabilir.

Bunu tek alanlarla veya alan gibi List<> koleksiyonlarla da yapabilirsiniz. Bu noktadan daha önce etki alanı modeli sınıflarını modelleme konusunu ele aldığımızda bahsedildi, ancak burada bu eşlemenin PropertyAccessMode.Field önceki kodda vurgulanan yapılandırmayla nasıl gerçekleştirildiğini görebilirsiniz.

EF Core'da altyapı düzeyinde gizlenen gölge özellikleri kullanma

EF Core'daki gölge özellikler, varlık sınıfı modelinizde bulunmayan özelliklerdir. Bu özelliklerin değerleri ve durumları altyapı düzeyinde yalnızca ChangeTracker sınıfında tutulur.

Sorgu Belirtimi desenini uygulama

Tasarım bölümünde daha önce tanıtıldığı gibi, Sorgu Belirtimi deseni, isteğe bağlı sıralama ve sayfalama mantığıyla sorgu tanımını yerleştirebileceğiniz yer olarak tasarlanmış bir Domain-Driven Tasarım desenidir.

Sorgu Belirtimi düzeni bir nesnedeki sorguyu tanımlar. Örneğin, bazı ürünleri arayan bir sayfalanmış sorguyu kapsüllemek için, gerekli giriş parametrelerini (pageNumber, pageSize, filter vb.) alan bir PagedProduct belirtimi oluşturabilirsiniz. Ardından, herhangi bir Repository yönteminde (genellikle List() aşırı yüklemesi) bir IQuerySpecification kabul eder ve bu belirtim temelinde beklenen sorguyu çalıştırır.

Genel Belirtim arabirimi örneği, eShopOnWeb başvuru uygulamasında kullanılan koda benzer olan aşağıdaki koddur.

// GENERIC SPECIFICATION INTERFACE
// https://github.com/dotnet-architecture/eShopOnWeb

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    List<string> IncludeStrings { get; }
}

Ardından genel belirtim temel sınıfının uygulanması aşağıdaki gibidir.

// GENERIC SPECIFICATION IMPLEMENTATION (BASE CLASS)
// https://github.com/dotnet-architecture/eShopOnWeb

public abstract class BaseSpecification<T> : ISpecification<T>
{
    public BaseSpecification(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }
    public Expression<Func<T, bool>> Criteria { get; }

    public List<Expression<Func<T, object>>> Includes { get; } =
                                           new List<Expression<Func<T, object>>>();

    public List<string> IncludeStrings { get; } = new List<string>();

    protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
    {
        Includes.Add(includeExpression);
    }

    // string-based includes allow for including children of children
    // for example, Basket.Items.Product
    protected virtual void AddInclude(string includeString)
    {
        IncludeStrings.Add(includeString);
    }
}

Aşağıdaki özellik, sepetin kimliği veya sepetin ait olduğu alıcının kimliğine bağlı olarak tek bir sepet öğesi yükler. Sepetin koleksiyonunu Items.

// SAMPLE QUERY SPECIFICATION IMPLEMENTATION

public class BasketWithItemsSpecification : BaseSpecification<Basket>
{
    public BasketWithItemsSpecification(int basketId)
        : base(b => b.Id == basketId)
    {
        AddInclude(b => b.Items);
    }

    public BasketWithItemsSpecification(string buyerId)
        : base(b => b.BuyerId == buyerId)
    {
        AddInclude(b => b.Items);
    }
}

Son olarak, aşağıda genel bir EF Deposunun belirli bir varlık türü T ile ilgili verileri filtrelemek ve yüklemek için böyle bir belirtimi nasıl kullanabileceğini görebilirsiniz.

// GENERIC EF REPOSITORY WITH SPECIFICATION
// https://github.com/dotnet-architecture/eShopOnWeb

public IEnumerable<T> List(ISpecification<T> spec)
{
    // fetch a Queryable that includes all expression-based includes
    var queryableResultWithIncludes = spec.Includes
        .Aggregate(_dbContext.Set<T>().AsQueryable(),
            (current, include) => current.Include(include));

    // modify the IQueryable to include any string-based include statements
    var secondaryResult = spec.IncludeStrings
        .Aggregate(queryableResultWithIncludes,
            (current, include) => current.Include(include));

    // return the result of the query using the specification's criteria expression
    return secondaryResult
                    .Where(spec.Criteria)
                    .AsEnumerable();
}

Belirtim, filtreleme mantığını kapsüllemenin yanı sıra, hangi özelliklerin doldurulacağı da dahil olmak üzere döndürülecek verilerin şeklini belirtebilir.

Bir depodan IQueryable döndürmeyi önermesek de, bunları bir sonuç seti oluşturmak için depo içinde kullanmak gayet uygundur. Bu yaklaşımı yukarıdaki List yönteminde kullanabilirsiniz. Bu yaklaşım, son satırda belirtim ölçütleriyle sorguyu yürütmeden önce sorgunun ekleme listesini oluşturmak için ara IQueryable ifadeler kullanır.

Belirtim deseninin eShopOnWeb örneğinde nasıl uygulandığını öğrenin.

Ek kaynaklar