Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Asp.NET Core 5.0 – SqlTableDependency Kütüphanesi İle Veritabanındaki Değişikleri Anlık Yakalama

Merhaba,

Yaklaşık 1 ay önce youtube kanalımda ‘Veritabanındaki Değişiklikleri Anlık Yakalama Uygulaması’ başlıklı aşağıdaki dersi yüklemiştim.

Bu içeriğimizde ise ilgili videonun içeriğindeki SqlTableDependency kütüphanesi ile veritabanındaki değişiklerin yakalanmasını ele alacak ve konuya dair metinsel bir kaynak oluşturacağız.

SqlTableDependency Nedir?

SQL Server’da ki tablolar üzerinde yapılan değişikliklere dair denetimde ve izlenimde bulunmak için geliştirilmiş yüksek seviyeli bir C# kütüphanesidir. İlgili kütüphane ile SQL Server’da dinlenilen herhangi bir tablo üzerinde bir değişiklik(ekleme/silme/güncelleme) meydana geldiği taktirde bu duruma dair SQL Server’da ki Service Broker tarafından gönderilen bildirimi yakalayabilmekte ve yapılan değişikliğe dair runtime’da tüm detaylarıyla haberdar olabilmekteyiz.

Service Broker Nedir?

SQL Server’da bulunan herhangi bir veritabanında ki herhangi bir tabloya veri eklendiğinde, silindiğinde yahut güncellendiğinde bizlere bilgilendirmede bulunacak olan bir servistir. Bu servis, kullanılacağı veritabanında aktifleştirilmiş olmalıdır. Bu durumu kontrol edebilmek için;

SELECT NAME, is_broker_enabled FROM SYS.DATABASES

sorgusunun sunucuda çalıştırılması yeterlidir.

Asp.NET Core 5.0 – SqlTableDependency Kütüphanesi İle Veritabanındaki Değişikleri Anlık Yakalama

SELECT NAME, is_broker_enabled FROM SYS.DATABASES


Görüldüğü üzere sorgu neticesinde sunucudaki veritabanları listelenmiş ve service broker özelliği aktif olanlarda ‘1’ değeri gelmiştir. Anlık değişikliklerin bilgisini istediğiniz veritabanında ilgili servis ‘0’ yani pasif olarak geliyorsa eğer;

ALTER DATABASE [TABLE NAME] SET ENABLE_BROKER

komutunu kullanabilirsiniz.

Uygulama Oluşturma ve Temel Gereksinimler

SqlTableDependency’i örneklendirebilmek için öncelikle bir Asp.NET Core Web API yahut MVC uygulaması oluşturalım. Ardından seçilen yaklaşıma dair temel konfigürasyonları yaptıktan sonra uygulamaya SqlTableDependency kütüphanesini Nuget havuzundan yükleyelim.

Install-Package SqlTableDependency -Version 8.5.8

Kütüphaneyi yükledikten sonra veritabanında değişiklerin takip edileceği tabloların modellerini oluşturalım. Bu modeller ORM yapılanması olan Entity Framework Core’da ki entity class’lar da olabilir. Burada amaç, ilgili tabloda değişiklik olduğu vakit, service broker ile gönderilen iletiyle uygulama arasında kontrat görevi görecek bir model tasarlamaktır.

Bizler misal olarak ‘Northwind’ veritabanındaki ‘Bolge’ ve ‘Personeller’ tablosunu dinleyeceğiz;

    public class Bolge
    {
        public int BolgeId { get; set; }
        public string BolgeTanimi { get; set; }
    }
    public class Personeller
    {
        public string Adi { get; set; }
        public string SoyAdi { get; set; }
    }

Esasında bu tanımlanan modeller, yukarıda da bahsedildiği üzere bir kontrat görevi göreceği için farklı isim ve içerikte tanımlanabilir. Böyle bir durumda ilgili modelle veritabanındaki tablo arasında mapping işleminin nasıl yapılacağını yazımızın devamında inceliyor olacağız. Ayrıca tablo içerisindeki tüm kolonları da belirtmek zorunda değiliz. Sadece ilgilenilen kolonları belirtmek yeterli olacaktır.

Service Oluşturma ve Uygulamaya Ekleme

Ardından tablo takip sorumluluğunu üstlenmesi için ‘DatabaseSubscription’ ismindeki generic sınıfı oluşturuyoruz.

    public interface IDatabaseSubscription
    {
        void Configure(string tableName);
    }
    public class DatabaseSubscription<T> : IDatabaseSubscription where T : class, new()
    {
        SqlTableDependency<T> _tableDependency;
        readonly string connectionString;
        public DatabaseSubscription() =>
            connectionString = "Server=.;Database=Northwind;Trusted_Connection=True;";

        public void Configure(string tableName)
        {
            _tableDependency = new SqlTableDependency<T>(connectionString, tableName);

            //Veritabanında bir değişiklik olduğu vakit OnChanged event'i tetiklenecektir.
            _tableDependency.OnChanged += (sender, e) =>
            {
                var props = typeof(T).GetProperties();
                foreach (var prop in props)
                    Console.WriteLine($"{prop.Name} : {prop.GetValue(e.Entity)}");
            };

            //Olası bir hata varsa OnError event'i tetiklenecektir.
            _tableDependency.OnError += (sender, e) => Console.WriteLine("Hata :)");

            //Takibi başlatıyoruz.
            _tableDependency.Start();
        }

        ~DatabaseSubscription() => _tableDependency.Stop();
    }

Görüldüğü üzere ‘DatabaseSubscription’ sınıfı ‘Configure’ metodunda, connection string’de belirtilen veritabanındaki verilen ‘tableName’e karşılık tabloyu dinleyecek ve gerekli event’leri bizim için hazırlayacaktır.

Şimdi yapılması gereken bu servisi uygulamaya dahil etmektir.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<DatabaseSubscription<Bolge>>();
            services.AddSingleton<DatabaseSubscription<Personeller>>();
            .
            .
            .
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        .
        .
        .
    }

‘Startup.cs’ dosyasında ‘ConfigureServices’ metodu içerisinde ilgili servis istenilen tabloları dinleyecek vaziyette uygulamaya eklenmiştir. Lakin daha eklenen bu servis nesneleri çalıştırılmamıştır. Bizim bu nesneleri ayağa kaldırıp, ilgili tabloları takip edilebilir hale getirmemiz gerekmektedir. Bu işlem için en uygun yer ‘Configure’ fonksiyonu olabilir. Nihayetinde bu fonksiyon, uygulama ilk ayağa kaldırılma sürecinde tetikleneceğinden dolayı hemen hemen her şeyden önce ilgili tabloların takibini başlatmak için oldukça uygundur.

‘Configure’ metodunda ilgili takip nesnelerini ayağa kaldırabilmek için middleware mahiyetinde extension metotlardan istifade etmek gayet yerinde olacaktır.

    static public class DatabaseSubscriptionMiddleware
    {
        public static void UseDatabaseSubscription<T>(this IApplicationBuilder builder, string tableName) where T : class, IDatabaseSubscription, new()
        {
            var subscription = (T)builder.ApplicationServices.GetService(typeof(T));
            subscription.Configure(tableName);
        }
    }

Yukarıdaki kod bloğunu incelerseniz eğer ‘UseDatabaseSubscription’ isimli generic metot sayesinde uygulamada istenilen noktada ilgili takip nesnelerini kolayca devreye sokabiliriz. Tabi ki de bu metodu yukarıda da bahsedildiği gibi ‘Configure’ içerisinde kullanacağız.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<DatabaseSubscription<Bolge>>();
            services.AddSingleton<DatabaseSubscription<Personeller>>();
            .
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            .
            app.UseDatabaseSubscription<DatabaseSubscription<Bolge>>("Bolge");
            app.UseDatabaseSubscription<DatabaseSubscription<Personeller>>("Personeller");
            .
            .
        }
    }

Evet… Böylece uygulama ayağa kalkar kalkmaz ilgili tablolar takip edilir vaziyette olacaktır. Şimdi gelin bir test yapalım…

Test

Asp.NET Core 5.0 - SqlTableDependency Kütüphanesi İle Veritabanındaki Değişikleri Anlık Yakalama

Model İle Tablo Uyuşmasaydı?

Son olarak makale seyrinde de bahsettiğimiz, oluşturulan model ile takip edilen tablonun kolon bilgileri uyuşmadığı taktirde bu probleme karşı aşağıdaki gibi ‘ModelToTableMapper’ nesnesinin oluşturulması ve ardından ‘SqlTableDependency’ nesnesine ‘mapper’ parametresinden verilmesi yeterli çözüm olacaktır.

            //Eğer ki, model'ın tüm propertyleri tablodaki kolon isimleriyle eşleşiyorsa 
            //buradaki mapper'ı kullanmayabilirsiniz.
            var mapper = new ModelToTableMapper<Bolge>();
            mapper.AddMapping(b => b.BolgeId, "BolgeId");
            mapper.AddMapping(b => b.BolgeTanimi, "BolgeTanimi");

            _tableDependency = new SqlTableDependency<T>(connectionString, tableName, mapper: mapper);

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek projeyi indirmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir