https://www.buraksenyurt.com/Burak Selim Şenyurt - C#2023-03-14T12:31:58+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/switch-case-kullanmadan-kod-yazabilmekSwitch Case Kullanmadan Kod Yazılabilir mi?2019-12-25T10:00:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2019/12/strategy_cover.jpg" alt="" />İnsanoğlu yağmurlu bir pazar günü evden çıkıp ne yapacağını bilemezken ne hakla ölümsüzlükten bahseder. Bir yazara ait olan bu cümleyi sevgili Serdar Kuzuloğlu'nun yakın zamanda izlediğim söyleşisinden not almışım. İnsanlığın ömrünü uzatmaya çalışması ile ilgili bir konuya atıfta bulunurken ifade etmişti. Oysa karşımızda duran ekolojik denge ve iklim problemleri, yakın gelecekte<em>(2025 deniyor)</em> dünya nüfusunun 1 milyar 250 milyon kadarının içilebilir su kaynaklarına erişemeyeceğini işaret etmekte. Lakin bundan etkilenmeyecek olan ve asıl ömrünü uzatmak isteyen dünya nüfusunun en zengin %1i, söz konusu kıtlığın yaratacağı sorunlardan ve başka felaketlerden korunmak için kendisine dev sığınaklar inşa ediyor, adalar satın alıyormuş. Gerçekten anlaşılması çok zor ve bir o kadar da karmaşık bir durum değil mi? Bu distopik senaryo bir kenara dursun biz geleceğin iyi şeyler getireceğini ümit ederek gelişmeye devam edelim.</p>
<p>Bazen üzerinde çalıştığımız Legacy sistemlerin biriktirdiği teknik borçlar da aynen bu distopik senaryoda olduğu gibi bizi kara kara düşündürür. Bunun önemli sebeplerinden birisi teknik borçlanma konusundaki bilinçsizliğimizdir. Ancak benim farklı bir teorim var. İtiraf etmeliyim ki programlama dillerini bazen çok yanlış bir biçimde öğreniyoruz. Özellikle nesne yönelimli dillerde bu daha fazla öne çıkıyor.</p>
<p>İlk öğrendiğiniz nesne yönelimli programlama dilini düşünün. Çoğunlukla değişken tanımlamaları ile başlayan, karar yapıları ve döngülerle devam eden bir öğretiyi takip ederiz. Bende genellikle bir programlama dilini tanırken bu yolu tercih ediyorum. Lakin tecrübemiz artıp nesne yönelimli dil bilgimizin yanına tasarım kalıpları ile SOLID<em>(Single Responsibility, Open Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) </em>gibi ilkeleri de ekleyince başka bir dili öğrenirken karar yapılarının üzerinde durmalı mıyız tartışabiliriz.</p>
<p>Öyle ki temiz kodlama ilkeleri bir çok halde switch-case/if-else gibi karar yapılarını kullanmadan kod yazmamız gerektiğini öğütlüyor. Şimdi kısa bir süre için ilgilendiğiniz koda veya projeye bakın. Bir switch-case/if-else bloğu arayın. Şöyle kallavi olanlardan bulursanız harika. Mesela case bloklarının içerisinde bol miktarda if-else ifadeleri de olsun. Tam bir kararsızlıklar abidesinin ekranın üst kısmından aşağıya doğru ilerlediğini ama tüm bu hengameyi içinde barındıran metodun gerçekten işe yarar bir şeyler yaptığını farz edin. Şimdi derin bir nefes alın ve o kod parçasını switch-case olmadan nasıl yazabileceğinizi bulmaya çalışın.</p>
<p>Bu noktaya nasıl mı geldim? Pek tabii bir süredir hayatımızda önemli bir yere sahip olan statik kod analiz aracı Sonarqube sayesinde. Son günlerde Cognitive Complexity <em>(Kavramsal Karmaşıklık desek yeridir)</em> değeri ne yazık ki tavan yapmış sınıflar ve üyeleri arasında gezinmekteyiz. Kullandığımız Sonarqube metriklerine göre bir metodun Cognitive Complexity değerinin 15 puanı aşmaması bekleniyor ancak 200lü değerleri geçen fonksiyonlar var. Complexity değerinin yüksek olması kodun okunurluğu, anlaşılması, yönetimi, bakımı ve test edilebilirliği noktasında çok zayıf olduğu olduğu anlamına gelir. Bu değeri arttıran bir çok bulgu var. Meşhur olanlarından bir tanesi de fazla sayıda switch-case ifadesinin kullanılması. Aslında Sonarqube'a ait aşağıdaki ekran görüntüsü konu hakkında biraz daha fazla fikir verebilir.</p>
<blockquote>
<p>Kavramsal karmaşıklık değeri, temel programlama yapılarının kod içerisindeki kullanımları için belirlenmiş ağırlık değerleri baz alınarak hesaplanmaktadır. Söz gelimi if-then-else için 2, for döngüsü için 3, eş zamanlı çalışan paralel kod parçaları için 4 ağırlık puanı ele alınır. Bu değerlere metodlara aktarılan ve çıkan parametre sayıları gibi kriterler de eklenerek fonksiyonun karmaşıklığı hakkında sayısal bir bilgi elde edilir. Cognitive Complexity puanlamasına etki eden kriterler için Sonarqube'un <a href="https://www.buraksenyurt.com/admin/app/editor/Complexity değerinin yüksek olması kodun yönetimi, bakımı ve test edilebilirliğinin zor olduğu anlamına gelmektedir." target="_blank">şu adresinden</a> yararlanabilirsiniz ancak olay sanıldığı kadar basit değil. İşin içerisinde Matematik formüller de var ;) <a href="https://ieeexplore.ieee.org/document/8253447" target="_blank">IEEE'nin 2018 basımı şu dokümanında</a> çok daha fazlasını bulabilirsiniz.</p>
</blockquote>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/12/strategy_pattern_1.png" alt="" /></p>
<p>Buradaki en önemli sorun metodun bulunduğu sınıfın SOLID ilkelerindeki Open-Closed prensibini ihlal etmesi. Bu ilkeye göre bir nesnenin genişletilmeye açık değiştirilmeye kapalı olması istenir. "Bunu bir örnekle anlamaya çalışsak nasıl olur?" diyorum içimden :) Öyleyse gelin aşağıdaki kod parçasını mercek altına alalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
namespace Sonarqube.Tests
{
public enum ProviderDirection
{
Kafka,
Rabbit,
Redis
}
class Program
{
static void Main()
{
#region Klasik kullanım
RoleProvider.Ping(ProviderDirection.Kafka,"Birr bilmecem var çocuklar...");
#endregion
}
}
#region Birinci Durum (Complexity değerini yükselten switch-case kullanımı -Temsili)
public static class RoleProvider
{
public static void Ping(ProviderDirection target,string message)
{
switch (target)
{
case ProviderDirection.Kafka:
Console.WriteLine("Kafka->{0}",message);
break;
case ProviderDirection.Rabbit:
Console.WriteLine("Rabbit MQ->{0}",message);
break;
case ProviderDirection.Redis:
Console.WriteLine("Redis->{0}",message);
break;
default:
break;
}
}
}
#endregion
}</pre>
<p>Olayı basit bir şekilde analiz etmek için az sayıda case ifadesi kurguladık. Aslında tertemiz bir kod parçamız var. RoleProvider sınıfı içerisindeki Ping metodu bir enum sabitini kullanarak farklı sistemler üzerinden mesaj gönderme işlemini temsil ediyor. Main metodu içerisinde yaptığımız çağrıda nasıl davranması gerektiğini belirliyoruz. Sorun RoleProvider sınıfı içerisine yeni bir provider durumu eklemek istediğimizde ortaya çıkıyor. Bunun için RoleProvider sınıfının kodunu değiştirmek zorundayız. Yani yeni durumu da eklememiz gerekmekte. Eğer RoleProvider sınıfını devasa bir uygulamanın ortak kütüphanelerince kullanılan bir tipi olarak düşünürsek işimiz daha da zorlaşıyor. İşte hem bu ilkenin ihlalini engellemek hem de Sonarqube aracını memnun etmek için başvurabileceğimiz güzel bir yol var. O da strateji tasarım kalıbının bir uyarlaması. Şimdi yukarıdaki kod parçasını aşağıdaki hale getirelim.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/12/strategy_pattern_2.png" alt="" /></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
namespace Sonarqube.Tests
{
public enum ProviderDirection
{
Kafka,
Rabbit,
Redis
}
class Program
{
static void Main()
{
#region Strategy Pattern uygulanan çözüm
ProviderContext strategyContext = new ProviderContext();
string message = "Acaba nedir nedir? Bisküvi denince akla...";
strategyContext.Ping(ProviderDirection.Kafka,message);
#endregion
}
}
#region Strateji kalıbının uygulandığı çözüm
interface IProviderStrategy
{
void Send(string message);
}
class KafkaProvider
: IProviderStrategy
{
public void Send(string message)
{
Console.WriteLine("Mesaj Kafka'ya gönderilir. {0}", message);
}
}
class RabbitMqProvider
: IProviderStrategy
{
public void Send(string message)
{
Console.WriteLine("Mesaj RabbitMQ'ya gönderilir. {0}", message);
}
}
class RedisProvider
: IProviderStrategy
{
public void Send(string message)
{
Console.WriteLine("Mesaj Redis'e gönderilir. {0}",message);
}
}
class ProviderContext
{
private static Dictionary<ProviderDirection, IProviderStrategy> _providers = new Dictionary<ProviderDirection, IProviderStrategy>();
//// Belki basit bir Injection kurgusu ile provider'lar içeri alınabilir
//public void AddProvider(ProviderDirection direction, IProviderStrategy provider)
//{
// _providers.Add(direction, provider);
//}
static ProviderContext()
{
_providers.Add(ProviderDirection.Kafka, new KafkaProvider());
_providers.Add(ProviderDirection.Rabbit, new RabbitMqProvider());
_providers.Add(ProviderDirection.Redis, new RedisProvider());
}
public void Ping(ProviderDirection direction,string message)
{
_providers[direction].Send(message);
}
}
#endregion
}</pre>
<p>Evet evet biliyorum. Üç tanecik case bloğu için tonlarca kod yazdık ama duruma böyle bakmamak gerekiyor değil mi? ;) Strateji kalıbının bu kullanımı sayesinde kodun daha okunabilir hale geldiğini söyleyebiliriz. Özellikle aşağıya doğru uzayıp giden case bazlı kod bloklarını çevirdiğinizde farkı çok daha net anlayacaksınız. Şimdi kod tarafında neler yaptığımızı kısaca inceleyelim. İstemci<em>(Client)</em> olarak niteleyebileceğimiz program sınıfı belli bir Provider tipine göre mesaj göndermek istiyor. Bunu yaparken enum sabitinden yararlanıyor. Enum sabitinin her elemanı farklı bir davranış biçiminin sergilenmesi anlamına da gelmekte. Eğer Kafka seçiliyse mesaj gönderme ona göre yapılmalı. RabbitMQ seçildiyse de ona göre. İşte bu hal değişikliğinin uyarlanması bizi ister istemez davranışsal tasarım kalıplarına<em>(Behavioral Design Patterns)</em> götürüyor. Bu tip switch-case kullanımlarının önüne geçilmesinde yukarıdaki kod parçasında yer verilen strateji tasarım kalıbının uyarlanması yeterli. </p>
<p>Uyarlamada dikkat edileceği üzere case durumuna giren asıl tipler<em>(KafkaProvider, RabbitMqProvider, RedisProvider)</em> ile uyguladıkları bir interface<em>(IProviderStrategy)</em> söz konusu. Context tipi olarak ProviderContext sınıfı kullanılıyor. Bu sınıf dikkat edileceği üzere enum sabiti ile karşılığı olan provider tiplerinin eşleştirildiği bir koleksiyon barındırmakta. Dolayısıyla sisteme yeni bir case bloğu eklemek demek aslında yeni bir IProviderStrategy türevini tasarlayıp buraya koymak demek. Tabii burada kafa karıştıracak bir durum var. switch-case yapısından kurtulduk ancak Context tipi halen open-closed ilkesini ihlal ediyor gibi. Bir başka deyişle generic Dictionary koleksiyonuna eklenecek bağımlılıkları içeriye enjekte etmeyi de düşünebiliriz. Yorum satırı haline getirilen AddProvider metodunda bunu bir nebze olsun ifade etmeye çalıştım ancak çözümü çok daha şık hale getirebiliriz. Bunu bir düşünün ve nasıl yapacağınıza karar verin.</p>
<p>Ah sonlandırmadan çalışma zamanına ait bir ekran görüntüsü de paylaşırsam çok iyi olacak.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2019/12/strategy_pattern_3.png" alt="" /></p>
<p>Tahminlerime göre aklınıza gelen bir başka soru daha var. "Peki ya if-else kullandığımız senaryolar varsa...Hah haaa" Elbette Cognitive Complexity değerini yükselten durumlardan birisi de if bloklarının çokluğu. Hatta ternary operatörü kullansak bile Sonarqube bunu yutmayabilir:) Strateji kalıbını if-else yapıları için de kurgulayabiliriz elbette ama farklı yaklaşımlar da söz konusu. Bunlardan birisi Chain of Responsbility kalıbının uygulanmasıdır. Buna benzer bir kalıp olup normalde GOF<em>(Gangs of Four)</em> listesinde yer almayan ancak <a href="https://app.pluralsight.com/library/courses/patterns-library/table-of-contents" target="_blank">Steve Smith'in bir Pluralsight eğitimi</a>nde anlattığı ve tesafüden de olsa çok geç bulduğum Rules tasarım kalıbı da var. Bir başka yazıda farklı ilkeler ile bu kez if-else karar yapısını daha doğru kurgulayarak nasıl ilerleyebileceğimizi incelemeye çalışacağım. Şimdilik bana müsade. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><em>Bakınız : switch-case/if-else gibi yapıları kullanmadan geliştirme yaparken tek seçeneğimiz Strategy veya Rules deseni midir? Örneğin Command Pattern kullanılabilir mi? ;)</em></p>2019-12-25T10:00:00+00:00sonarqubestrategy design patterndesign patterntasarım kalıplarıc#solidopen closed principlesoftware design principlesbehavioral design patternsbsenyurtBu yazıda switch case kullanmadan nasıl kod yazabileceğimizi anlamaya çalışacağız. İşin içerisine SOLID(Single Responsibility, Open Closed, Liskov Substituion, Interface Segregation, Dependency Inversion) ilkelerinden open-closed principle ve tasarım kalıplarından Strateji girecek. Eğlenceli olduğu kadar Sonarqube bulgularını memnun edecek bir kod parçasını nasıl geliştirebileceğimizi göreceğiz.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=443155f6-bea1-4d2d-8ab0-14c9ec1f1d053https://www.buraksenyurt.com/trackback.axd?id=443155f6-bea1-4d2d-8ab0-14c9ec1f1d05https://www.buraksenyurt.com/post/switch-case-kullanmadan-kod-yazabilmek#commenthttps://www.buraksenyurt.com/syndication.axd?post=443155f6-bea1-4d2d-8ab0-14c9ec1f1d05https://www.buraksenyurt.com/post/dependency-injection-in-tdd-deki-yeriDependency Injection'ın TDD'deki Yeri2018-12-21T10:30:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2018/09/tdd_di_5.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Ne zamandır oturup da Lego yapmadığımı fark ettim. Her ne kadar fiyatları epeyce artmış olsa da geçenlerde dayanamayıp bir tane aldım. Bitirir bitirmez beni tatile götüreceğini düşündüğüm güzel bir karavan. Bloklardaki canlı renklerin tatlılığı, masmavi surf tahtası, uydu alıcısı, konforlu koltukları, panaromik tavanı, spor lastikleri ile bir saate kalmadan hazırdı bile.</p>
<p>Onunla biraz oynayıp, wrom wrom yaptım. Lego City'nin caddelerinden yavaş ve sakince akarak Malaga kıyılarındaki plajlara indim. Güneş batana kadar yüzdüm, surf yaptım. Bazen sahilden iyice uzaklaştım. İnsanların nokta gibi gözüktüğü mesafelere geldim. Şehre akşamın sakinliği çöküp karanlık bastırdıktan sonra plaja tekrar gelen gençlerin gitar melodilerinden çıkan tınıları dinlemeye başladım. Yaktıkları ateş etrafında toplanmış enerji dolu gençler. Karavanın hemen yanıbaşına kurduğum şezlongumda uzanmış onları izliyordum. Tebessümle. Buzlu kahvemden bir yudum aldım ve açık duran bilgisayarımda yanıp sönen cursor'u izlemeye başladım. Yazılmayı bekleyen kuyrukta kalmış bir şeyler vardı...</p>
<p>TDD süreçlerindeki birim ve entegrasyon testlerinde<em>(Integration Tests)</em> yaşadığımız önemli sorunlardan birisi, test edilmek istenen fonksiyonelliklerde kullanılan nesnelerin diğer nesnelerle olan bağımlılıkları sebebiyle yaşanmaktadır. Söz gelimi test edilmek istenen birimin bağımlılıkları arasında servis çağrıları, veritabanı operasyonları veya uzak sunucu dosya hareketleri gibi işlemler söz konusuysa, otomatik olarak çalıştırılan birim testlerinin CI<em>(Continuous Integration)</em> sürecini sekteye uğratmaması için bir şeyler yapılması gerekebilir. Biliyorum çok karışık bir paragraf ile işe başladım. O yüzden problemi ortaya koymak adına aşağıdaki kod parçalarını göz önüne alarak ilerlemeye çalışalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
namespace CodeKata.Services{
public class CalculationService
{
private UserSerice _service;
public CalculationService()
{
_service=new UserSerice();
}
public string GetInvoices()
{
if(_service.CheckRequest(GetCurrentContext()))
{
return "Invoice list";
}
else{
return String.Empty;
}
}
private string GetCurrentContext()
{
return "{\"operation\":\"Sum\"}";
}
}
}</pre>
<p>CalculationService isimli sınıfın GetInvoices metoduna odaklanalım. UserService sınıfına ait nesne örneğinin CheckRequest fonksiyonu kullanılarak bir işlem gerçekleştirilmekte. Açıkça CalculationService sınıfı için bir bağımlılık bulunduğunu görebiliyoruz. Buna ilaveten UserService'in bir web servisi olduğunu düşünelim.</p>
<p><strong>Sorun şu; "GetInvoices için yazılan testler çalışırken ya UserService çalışır durumda değilse?"</strong></p>
<p>Bu Continuous Integration sırasında test aşamasının geçilmesine de engel teşkil edebilecek bir durum olabilir. İşte burada söz konusu servisin CheckRequest fonksiyonunun aslında istediğimiz tipte veriyi döndürecek şekilde kullanılması sorunu çözümleyebilir. Yani bağımlılığı test tarafında istediğimiz gibi enjekte edersek asıl testin çalışıp çalışmadığına odaklanabiliriz. Bunu yapmak için Dependency Injection' a uygun bir tasarıma geçmemiz gerekiyor. </p>
<p>Bildiğiniz üzere Dependency Injection mekanizması ile bu tip nesne bağımlılıklarının soyutlaştırılması mümkün. Temelde bunu üç farklı şekilde yapabiliriz. Yapıcı metod<em>(Constructor)</em> üzerinden, özelliğe<em>(Property)</em> kullanarak ve arayüz<em>(Interface)</em> tipinden yararlanarak. Yukarıdaki örneği düşünerek bu üç tekniğini nasıl kullanabileceğimizi incelemeye çalışalım. </p>
<h1>Yapıcı Metod Kullanımı</h1>
<p>Burada bağımlılığın nesne içerisine yapıcı metod üzerinden aktarımı söz konusudur. Öncelikle aşağıdaki gibi bir arayüze ihtiyacımız var.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public interface IUserService
{
bool CheckRequest(string request);
}</pre>
<p>Buna göre CalculationService sınıfını da aşağıdaki gibi değiştirilmesi gerekiyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
namespace CodeKata.Services{
public class CalculationService
{
private IUserService _service;
public CalculationService(IUserService service)
{
_service=service;
}
public string GetInvoices()
{
if(_service.CheckRequest(GetCurrentContext()))
{
return "Invoice list";
}
else{
return String.Empty;
}
}
private string GetCurrentContext()
{
return "{\"operation\":\"Sum\"}";
}
}
}</pre>
<p>Dikkat edileceği üzere yapıcı metoda parametre olarak IUserService arayüzünü veriyoruz. Bu arayüz, UserService sınıfının uygulaması gereken CheckRequest metodunu tanımlamakta. Dolayısıyla IUserService arayüzünü uygulayan herhangi bir sınıfı kullanabilir ve asıl servise gitmeye gerek kalmadan istediğimiz cevabı döndürerek testin ilerlemesini sağlayabiliriz. Bunun için test tarafında aşağıdaki gibi bir sınıfa ihtiyacımız olacak.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class FakeUserService
: IUserService
{
public bool CheckRequest(string request)
{
return true;
}
}</pre>
<p>CheckRequest operasyonunu her türlü true döndürecek hale getirdik. Dolayısıyla şöyle bir test metodu yazmamız artık mümkün.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using CodeKata.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CodeKata.Tests
{
[TestClass]
public class CalculationServiceTests
{
[TestMethod]
public void Should_Return_Current_Invoice_List_Is_Ok()
{
var calcService = new CalculationService(new FakeUserService());
Assert.AreEqual("Invoice list", calcService.GetInvoices());
}
}
}</pre>
<p>Should_Return_Current_Invoice_List_Is_Ok test metodunun içerisinde CalculationService nesnesini örneklerken parametre olarak FakeUserService sınıfını verdiğimize dikkat edelim. FakeUserService, test projesi içerisinde yer alıyor ve asıl servisin bu test için gerekli olan davranışını taklit ediyor. İşte çalışma zamanı sonucu <em>(Örnekleri Visual Studio Code üzerinde ve MSTest türevli bir test projesi ile geliştirmekteyim)</em></p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2018/09/tdd_di_1.gif" alt="" /></p>
<h1>Property Setter Kullanımı</h1>
<p>Şimdi enjekte mekanizmasını property üzerinden kuralım. Tek yapmamız gereken CalculationService sınıfını aşağıdaki gibi değiştirmek olacak.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
namespace CodeKata.Services{
public class CalculationService
{
public IUserService UserService
{
get;
set;
}
public string GetInvoices()
{
if(UserService.CheckRequest(GetCurrentContext()))
{
return "Invoice list";
}
else{
return String.Empty;
}
}
private string GetCurrentContext()
{
return "{\"operation\":\"Sum\"}";
}
}
}</pre>
<p>Bu sefer yapıcı metod yerine getter ve setter bloklarına sahip IUserService tipinden bir özellik kullanarak bağımlılığın içeriye alınmasını sağlıyoruz. Doğal olarak ilgili test metodunun da aşağıdaki gibi değişmesi gerekiyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">[TestMethod]
public void Should_Return_Current_Invoice_List_Is_Ok()
{
var fakeService=new FakeUserService();
var calcService=new CalculationService()
{
UserService=fakeService
};
Assert.AreEqual("Invoice list", calcService.GetInvoices());
}</pre>
<p>Test bu durumda da beklediğimiz gibi çalışacak ve asıl servisi hiç kullanmadan ilerleyecektir.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2018/09/tdd_di_2.gif" alt="" /></p>
<h1>Interface Kullanımı</h1>
<p>Son olarak daha çok tercih edilen interface üzerinden nasıl bağımlılık enjekte edebileceğimize bir bakalım. Bu yöntem aslında property tabanlı bağımlılık tanımlamanın genişletilmiş bir versiyonu olarak düşünülebilir ve daha çok birden fazla bağımlılığın olduğu durumlarda ele alınır. Bizim örneğimizde bu tekniği aşağıdaki gibi icra etmemiz mümkün.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public interface IUserServiceInjector
{
IUserService UserService{get;set;}
}</pre>
<p>İlk olarak IUserService arayüzünden referansları döndürecek bir başka sözleşme tanımlıyoruz. Ardından IUserServiceInjector isimli arayüz CalculationService sınıfına uyarlanıyor. Böylece CalculationService sınıfının ilgili servis davranışını IUserServiceInjector üzerinden belirtilen sözleşme çerçevesinde almasını sağlıyoruz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class CalculationService
: IUserServiceInjector
{
public IUserService UserService
{
get;
set;
}
public string GetInvoices()
{
if (UserService.CheckRequest(GetCurrentContext()))
{
return "Invoice list";
}
else
{
return String.Empty;
}
}
private string GetCurrentContext()
{
return "{\"operation\":\"Sum\"}";
}
}</pre>
<p>Çalıştığımız nesnelerde birden fazla bağımlılığın söz konusu olması halinde ayrı ayrı Injector arayüzleri tasarlayıp uygulamamız mümkün. Son değişikliklere rağmen test metodunda özellik tabanlı örneğimizden farklı bir işleyiş söz konusu olmayacak.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2018/09/tdd_di_3.gif" alt="" /></p>
<p>Test güdümlü geliştirme kapsamında Dependency Injection tekniğini bağımlılıkların olduğu her yerde ele almak mümkün. Her ne kadar kod okunmasını biraz zorlaştırsa da daha kolay test yazılmasını sağlamakta olduğu aşikar.</p>
<p>Örneklerde kullandığımız FakeUserService sınıfı terminolojide "Test Double" tipi olarak geçmekte<em>(Hatta Stub türünden bir nesne olduğunu ifade edebiliriz)</em> Test Double, basitçe bir üretim nesnesinin test amaçlı olarak değiştirilerek kullanılması olarak ifade ediliyor. Hatta kullanım şekline göre Dummy, Fake, Stubs, Spies ve Mocks gibi farklı türleri de bulunuyor. En çok kullanılan versiyonlar Mocks ve Stubs nesneleri. "Test Double" türlerini uygulamalarımızda daha kolay kullanabilmek de mümkün. Bu işe özel kütüphaneler bulunmakta.</p>
<h1>Mock Nesne Kullanımı</h1>
<p>Şimdi <a href="https://github.com/moq/moq4" target="_blank">Moq Nuget paketini kullanarak</a> Stub yerine mock nesne kullanımını nasıl yapabileceğimize bir bakalım. Bu sayede mock kullanımını daha sade bir şekilde öğrenebiliriz. İlk olarak test projesine Moq4 paketini eklememiz gerekiyor. Bunun için Visual Studio Code terminalinden aşağıdaki komutu vermemiz yeterli.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet add package Moq</pre>
<p>Sonrasında test metodunda Moq kütüphanesini kullanmaya başlayabiliriz. Örneğimizde yapıcı metod odaklı enjekte mekanizmasını kullanabiliriz. Hatırlayacağınız gibi bağımlılığın yapıcı metod üzerinden aktarımı söz konusuydu.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using CodeKata.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace CodeKata.Tests
{
[TestClass]
public class CalculationServiceTests
{
[TestMethod]
public void Should_Return_Current_Invoice_List_Is_Ok()
{
var mockUserService=new Mock<IUserService>();
mockUserService.Setup(o=>o.CheckRequest("{\"operation\":\"Sum\"}")).Returns(true);
var calcService=new CalculationService(mockUserService.Object);
var result=calcService.GetInvoices();
Assert.AreEqual("Invoice list",result);
mockUserService.Verify(o => o.CheckRequest("{\"operation\":\"Sum\"}"), Times.Once());
}
}
}</pre>
<p>Mock nesne örneğini oluştururken generic bir parametre kullanıyoruz. Burada mock'lamak istediğimiz tip IUserService arayüzü. Sonrasında Setup fonksiyonuna bir çağrı yapılıyor. Setup metodunda CheckRequest metodunu çağırıp ve sonucunda da geriye true döndürülmesini istediğimiz belirtiyoruz. Burada ilgili mock tipinin herhangi bir metodunun ele alabiliriz. Yapılan şey o fonksiyon çağırılmış da dönüşünde Returns içerisinde yazan değer döndürülmüş hissiyatını vermek. CalculationService sınıfına ait nesneyi örneklerken de yapıcı metoda parametre olarak mock nesnesini bir başka deyişle çalışma zamanında IUserService için örneklenen referansını atıyoruz. Sonrasında tek yaptığımız GetInvoices fonksiyonunu çağırmak. Kabul kriterinin kontrolünden sonra birde doğrulama işlemimiz var. Burada ilgili operasyonun sadece bir kere çağırılıp çağırılmadığını kontrol etmekteyiz. Test beklendiği gibi çalışacaktır.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2018/09/tdd_di_4.gif" alt="" /></p>
<p>Pek tabii TDD tarafında Dependency Injection kullanımı bu örnek kodlarda olduğu kadar kolay olmayabilir. Özellikle legacy olarak anılan eski projleri sonradan Continuous Integration hattına soktuğumuzda test yazmak gerçekten başa bela olabilir. Çok fazla sayıda özellik barındıran Entity sınıfları ile yürüyen ve iş akışı karmaşık fonksiyonar için entegrasyon testleri yazmak istediğinizi düşünün. Mock nesne kullanımı biraz can acıtıcı olabilir ama uzun vadede rahat edileceği kesindir.</p>
<p>Böylece geldik bir makalemizin daha sonuna. Bu yazımızda Dependency Injection kavramını entegrasyon testlerinde değerlendirerek daha iyi anlamaya çalıştık. Size tavsiyem var olan entegrasyon testlerinizde belli başlı bağımlılıkları mock nesneler kullanarak ortadan kaldırmaya çalışmanız olacaktır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2018-12-21T10:30:00+00:00unit testtddtest driven developmentmockstubdependency injectionc#.net corevisual studio codemoqmockingmocksstubsnugetdotnet testmstestdotnetdotnet coreinterfaceconstructorbsenyurtTest odaklı yazılım geliştirirken özellikle entegrasyon testlerinde(Integration Tests) yaşadığımız önemli sorunlardan birisi de test edilen nesnelerin diğer nesnelerle olan bağımlılıklarıdır. Söz gelimi test edilmek istenen birimin bağımlılıkları arasında servis çağrıları, veritabanı operasyonları veya uzak sunucu dosya hareketleri gibi işlemler söz konusuysa otomatik olarak çalıştırılan birim testlerinin CI sürecini sekteye uğratmaması için bir şeyler yapılması gerekebilir. Biliyorum çok karışık bir paragraf ile işe başladım. O yüzden problemi ortaya koymak adına aşağıdaki kod parçalarını göz önüne alalım.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=e003075c-9d13-438a-a540-7ff95d0738a57https://www.buraksenyurt.com/trackback.axd?id=e003075c-9d13-438a-a540-7ff95d0738a5https://www.buraksenyurt.com/post/dependency-injection-in-tdd-deki-yeri#commenthttps://www.buraksenyurt.com/syndication.axd?post=e003075c-9d13-438a-a540-7ff95d0738a5https://www.buraksenyurt.com/post/json-to-bsonJSON to BSON2017-04-30T21:36:00+00:00bsenyurt<p><img style="float: right;" src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson.gif" alt="" />Merhaba Arkadaşlar,</p>
<p>Sanıyorum her .Net programcısının takım çantasında yer alan paketlerden birisi de Newtonsoft'un JSON serileştirme kütüphanesidir. JSON<em>(JavaScriptObjectNotation)</em> formatı, XML<em>(eXtensibleMarkupLanguage)</em> şemasından sonra hafif ve az yer kaplama özellikleri nedeniyle çokça tercih edilen standartlardan birisi haline gelmiştir. Diğer yandan JSON içeriklerin Binary formatta serileştirilmiş versiyonu olarak adlandırılan BSON formatı da sıklıkla kullanılmaktadır. </p>
<p><a style="line-height: 1.8;" href="http://bsonspec.org/#/specification" target="_blank">BSON</a><span style="line-height: 1.8;">, NoSQL camiasının liderlerinden MongoDB ile popülerlik kazanmış bir veri formatı. JSON içeriğinin binary formatta sunulması oldukça hızlı gerçekleşebilecek bir işlem. Bu nedenle NoSQL tabanlı sistemlerde yatay ölçeklenen veri kümeleri arasındaki iletişimde tercih edilebiliyor. Ağ üzerinde dolaşan bu küçültülmüş paketler içerisinde JSON tipinde veri bulunuyor. Zaten tasarım amaçlarından birisi de bu.</span></p>
<p>Peki .Net tarafında kullanılan nesne örneklerini BSON formatına nasıl dönüştürebiliriz? Bunun bilinen pek çok yolu var tabii ki ancak uygulamanızda Newtonsoft ve JSON içerikleri ile çalışıyorsanız işi kolaylaştıracak tiplerde bu paketle birlikte geliyor. Newtonsoft.Json paketi, bir JSON içeriğinin binary olarak yazılması ve okunması için iki temel tip sunmakta. Bu tipleri kullanarak BSON dönüşüm işlemleri kolayca gerçekleştirilebilir. Basit bir örnekle konuyu özetleyelim. </p>
<blockquote>
<p>Başlamadan önce Newtonsoft'un ilgili paketinin uygulamaya yüklenmiş olması gerektiğini hatırlatalım. Bunu NuGet Package Manager veya Console üzerinden gerçekleştirebiliriz.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson_1.gif" alt="" /></p>
</blockquote>
<p>Aşağıdaki sınıf diagramında ve kod parçasında görülen tipleri tasarladığımızı düşünelim.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson_2.gif" alt="" /></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class Person
{
public int PersonId { get; set; }
public string Title { get; set; }
public decimal Salary { get; set; }
public Job[] Jobs { get; set; } // SOAPFormantter desteği için Array yapıldı
public override string ToString()
{
return string.Format("{0}-{1}-{2}"
, PersonId, Title, Salary.ToString("C2"));
}
}
public class Job
{
public int JobId { get; set; }
public string Description { get; set; }
public DateTime AddingTime { get; set; }
public override string ToString()
{
return string.Format("\t{0}-{1}-{2}"
, JobId, Description, AddingTime.ToShortDateString());
}
}</pre>
<p>Kişi bilgisini tuttuğumuz Person ve bu kişilerin önceden çalıştıkları işlerin bilgisini tutan Job isimli iki sınıfımız var. BSON dönüşümünde kullanılacak örneğin zengin olması için Person sınıfında Job tipinden bir dizi bulunuyor. Böylece bire-çok ilişki barındıran bir nesne ağacını dönüştürme işleminde kullanabiliriz. Tabii bize test verisi ve ters serileştirme sonrası dönüşen liste içeriğini ekrana basacak fonksiyonellikler de gerekiyor. Bunları şimdilik Utility isimli bir sınıfta aşağıdaki kod parçasında görüldüğü gibi toplayabiliriz. </p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public class Utility
{
public List<Person> FillEmployees()
{
List<Person> employees = new List<Person>();
employees.Add(new Person
{
PersonId = 1,
Title = "Coni Minomic",
Salary = 1900,
Jobs = (new List<Job>
{
new Job{
JobId=1
, Description="Google's Master Computer Scientist"
, AddingTime= DateTime.Now
},
new Job{
JobId=2
, Description="Amazon Big Cloud System Manager"
, AddingTime= DateTime.Now
},
}).ToArray()
});
employees.Add(new Person
{
PersonId = 1,
Title = "Jon Carter",
Salary = 1780,
Jobs = (new List<Job>
{
new Job{
JobId=1
, Description="Microsoft Principle Developer"
, AddingTime= DateTime.Now
}
}).ToArray()
});
return employees;
}
public void WriteLine(List<Person> personOfInterest)
{
foreach (var person in personOfInterest)
{
Console.WriteLine(person.ToString());
foreach (var job in person.Jobs)
{
Console.WriteLine(job.ToString());
}
}
}
}</pre>
<p>FillEmployees metodu ile test amaçlı bir çalışan listesi oluşturuyoruz. WriteLine metodu ise gelen çalışan içeriğini ekrana bastırmakla yükümlü. Amacımız, çalışan listesini BSON formatında bir dosyaya yazdırmak ve sonrasında bu içeriği ters-serileştirme ile geri okuyup ekrana yazdırmak. Bunun için gerekli örnek kod parçasını aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using System;
using System.Collections.Generic;
using System.IO;
namespace BsonExample
{
class Program
{
static void Main(string[] args)
{
var mario = new Utility();
var employees = mario.FillEmployees();
var root = Environment.CurrentDirectory;
var file = Path.Combine(root, "employees.bson");
JsonSerializer serializer = new JsonSerializer();
using (MemoryStream mStream = new MemoryStream())
{
using (BsonWriter bsonWriter = new BsonWriter(mStream))
{
serializer.Serialize(bsonWriter, employees);
}
File.WriteAllText(file, Convert.ToBase64String(mStream.ToArray()));
}
var fileContent=File.ReadAllText(file);
var binaryContent=Convert.FromBase64String(fileContent);
using(MemoryStream mStream=new MemoryStream(binaryContent)){
using (BsonReader bsonReader = new BsonReader(mStream))
{
bsonReader.ReadRootValueAsArray = true;
var result=serializer.Deserialize<List<Person>>(bsonReader);
mario.WriteLine(result);
}
}
}
}
.
.
.</pre>
<p>Koddaki başrol oyuncuları JsonSerializer, BsonWriter ve BsonReader sınıflarıdır. İlk using bloğunda MemoryStream ile belleğe açılan employees liste içeriğinin Base64 kodlamasından yararlanılarak dosyaya yazdırılması söz konusudur. İkinci using bloğunda ise bir kaç satır önce dosyaya yazılmış olan binary içeriğin Base64'ten byte[] haline getirilmesi ve ardından BsonReader kullanılarak generic Person listesine dönüştürülmesi işlemi yapılmaktadır. Kritik noktalardan birisi dePerson tipinden bir listenin<em>(List<Person>)</em> kullanılmasıdır. Eğer BsonReader nesne örneğinin ReadRootValueAsArray özelliğine true verilmezse bu listenin ters-serileştirilme aşamasında aşağıdaki ekran görüntüsünde yer alan JsonSerializationException oluşacaktır.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson_5.gif" alt="" /></p>
<p>Kodun çalışması sırasında üretilen BSON içeriği ise aşağıdakine benzer olacaktır.</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson_3.gif" alt="" /></p>
<p>Yapılan ters serileştirme işlemi sonrası elde edilen ekran çıktısı da şöyledir. </p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2017/02/bson_4.gif" alt="" /></p>
<p>Görüldüğü gibi BSON içeriği başarılı bir şekilde ters-serileştirilerek nesne haline getirilebilmiştir.</p>
<p>Örnekteki kod parçalarını biraz daha düzenlemeye çalışmanızı önerebilirim. Nitekim BSON dönüşüm operasyonları birer genişletme metodu<em>(extension method)</em> haline de getirilerek kullanımları daha kolay hele getirilebilir. Ayrıca Newtonsoft'un kütüphanelerine başvurmadan da JSON içeriği oluşturabilir ve Base64 kodlamasından yararlanarak BSON içerikleri üretebilirsiniz. Sonuçta JSON standartları belli. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2017-04-30T21:36:00+00:00jsonbsonbinary jsonjavascriptobjectnotation.netnewtonsoftc#serializationdeserializationJsonSerializationException bsenyurtAmacımız çalışan listesinin içeriğini BSON formatında bir dosyaya yazdırmak ve sonrasında bu içeriği tekrar okuyup ters-serileştirme işlemini gerçekleştirerek ekrana yazdırmak.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=32a8e9fa-1660-4834-8c4c-f309961665f02https://www.buraksenyurt.com/trackback.axd?id=32a8e9fa-1660-4834-8c4c-f309961665f0https://www.buraksenyurt.com/post/json-to-bson#commenthttps://www.buraksenyurt.com/syndication.axd?post=32a8e9fa-1660-4834-8c4c-f309961665f0https://www.buraksenyurt.com/post/tek-fotoluk-ipucu-133-yuklu-oledb-provider-listesinin-bulunmasiTek Fotoluk İpucu 133 - Yüklü OLEDB Provider Listesinin Bulunması2016-09-15T21:01:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p>Malumunuz büyük çaplı sistemler kolay kolay yenilenmiyorlar. Ancak teknolojik gereklilikler ve değişen ihtiyaçlar ister istemez bu yaşayan organizmaların yeni sunucular üzerinde hayata devam etmelerini gerektirebiliyor. Yeniden yazma maliyetlerinin yüksek olduğu durumlarda var olan sistemin kullandığı pek çok bileşenin de bu sunucular ile uyumlu olması gerekiyor<em>(bekleniyor)</em>. Uyumlu olmayanların yerine geçici çözümler uygulanıyor. Tabii mümkün mertebede. Bazen yeni sunuculara taşınan sistem üzerinde yıllardır yaşamını sürdüren C,C++ gibi derlendikten sonra pek de geri çevrilip içeriği görülemeyeccek kodlar da söz konusu oluyor. Böyle bir durumla karşı karşıya kalırsanız vay halinize. Ben ve değerli ekip arkadaşım bu durumdam çok çektik. </p>
<p>Gelelim ipucumuzun konusuna. Yukarıdaki gibi bir senaryo ile karşılaştığımızı düşünelim. Bu kez mesele yeni <strong>Windows Server 2012</strong> sunucusunda <strong>MSDAORA</strong> isimli <strong>OLEDB</strong> provider' ını kullanan <strong>ASP</strong> kod parçalarının çalışmaması<em>(Aslında çalışmama sebebi büyük ihtimalle ilgili nesnenin Windows Server 2012 de zaten desteklenmemesi)</em> Öncelikle bu sunucuda hangi<strong> OLEDB provider</strong>' larının yüklü olduğunu öğrenmeye çalışarak işe başlamaya karar verdik. Siz olsanız bunun için ne yapardınız. Aşağıdaki gibi bir kod parçası olabilir mi?</p>
<p><img src="https://www.buraksenyurt.com/image.axd?picture=/2016/08/tfi133n.gif" alt="" /></p>
<p>Aslında tek yapılan şey <strong>OleDbEnumerator</strong> sınıfının <strong>static GetRootEnumerator</strong> metodu ile dönen listede hareket etmek ve o anki öğenin tüm bilgilerini ekrana yazdırmak. Böylece geldik bir ipucunun daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2016-09-15T21:01:00+00:00c#ado.netadoprovideroledbenumeratorbsenyurtMalumunuz büyük çaplı sistemler kolay kolay yenilenmiyorlar. Ancak teknolojik gereklilikler ve değişen ihtiyaçlar ister istemez bu yaşayan organizmaların yeni sunucular üzerinde hayata devam etmelerini gerektirebiliyor. Yeniden yazma maliyetlerinin yüksek olduğu durumlarda var olan sistemin kullandığı pek çok bileşenin de bu sunucular ile uyumlu olması gerekiyor(bekleniyor). Uyumlu olmayanların yerine geçici çözümler uygulanıyor. Tabii mümkün mertebede. Bazen yeni sunuculara taşınan sistem üzerinde yıllardır yaşamını sürdüren C,C++ gibi derlendikten sonra pek de geri çevrilip içeriği görülemeyeccek kodlar da söz konusu oluyor. Böyle bir durumla karşı karşıya kalırsanız vay halinize. Ben ve değerli ekip arkadaşım bu durumdam çok çektik.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=1f9fd29b-ec1c-42bf-ae4c-0bba53d455df0https://www.buraksenyurt.com/trackback.axd?id=1f9fd29b-ec1c-42bf-ae4c-0bba53d455dfhttps://www.buraksenyurt.com/post/tek-fotoluk-ipucu-133-yuklu-oledb-provider-listesinin-bulunmasi#commenthttps://www.buraksenyurt.com/syndication.axd?post=1f9fd29b-ec1c-42bf-ae4c-0bba53d455dfhttps://www.buraksenyurt.com/post/oracle-view-lari-icin-otomatik-datatable-lar-uretmekOracle View' ları için Otomatik DataTable' lar Üretmek2016-03-19T18:21:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p>Üzerinde çalıştığımız ve uzun süredir canlı ortamda yaşamakta olan eski bir ürünümüz geçtiğimiz günlerde kod kalite taramalarından birisine girdi. Vaktinde her zaman olduğu gibi alel acele yazılmak zorunda olan kodlar bir kaç ana kategori altında çeşitli tipte ihlallere yakalandı.<em> </em>Bunlardan birisi de <strong>Strongly Typed DataSet</strong> kullanımına ilişkindi. </p>
<p>Ölçümleme yapan aracın metriklerinden birisi<strong> Untyped DataSet</strong> kullanımı kabul etmemekte. Bu yüzden uygulama içerisinde <strong>View</strong>' lar için kullandığımız ne kadar <strong>DataTable</strong> varsa ihlale girdi. <strong>.Net</strong> tarafında kodlamaya yeni başlayanların sıklıkla düşebileceği bir hata olduğunu ifade edebilir aslında. <strong>Typed DataSet/DataTable</strong>' ler kod yazımı sırasında <strong>tip güvenliğini<em>(Type Safety)</em></strong> de beraberinde getirdiğinden tercih edilmesi gereken sınıflar.</p>
<p><strong>Typed DataSet/DataTable</strong> üretimi <strong>Visual Studio</strong> ortamında çok da zor değil. <em>(Hatta komut satırından da kolayca yapılabilir. <a title="Generating Strongly Typed DataSet" href="https://msdn.microsoft.com/en-us/library/wha85tzb(v=vs.110).aspx" target="_blank">Şu adresten bilgi</a> alabilirsiniz)</em> Ancak yapılan üretim sonrası bizim için çok kalabalık kod parçaları oluştuğunu ifade etmek isterim. Oysaki tek ihtiyacımız olan <strong>View</strong>' ların karşılığı olacak ve <strong>Lookup Table</strong> gibi kullanılıp sadece veriyi gösterme amaçlı kullanılacak <strong>DataTable</strong> tipleri idi. Araştırmalarımız sonucu aşağıdaki gibi bir yapının <strong>Typed DataTable</strong> kullanımı için yeterli olduğunu gördük.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"> public class ProductTable
:DataTable
{
public DataColumn ID { get; set; }
public DataColumn URUN_ADI { get; set; }
public DataColumn URUN_FIYATI { get; set; }
}</pre>
<p>Dikkat edilmesi gereken ilk nokta <strong>ProductTable</strong> tipinin <strong>DataTable</strong> sınıfından türemiş olmasıdır. Diğer önemli bir nokta ise özelliklerin <strong>DataColumn</strong> tipinden tanımlanmasıdır. Özellik adları aslında <strong>Oracle</strong> tarafındaki nesnenin<em>(Table veya View olabilir)</em> kolon adları ile birebir aynıdır.</p>
<p>Tahmn edileceği üzere bir sonraki adım <strong>Oracle View</strong>' larının her biri için bu tip <strong>DataTable</strong> türevli tipler üretmekti. Ancak<strong> View</strong>' ların sayısı oldukça fazlaydı. Bu sınıfları otomatik üretecek bir kod parçası geliştirmek çok daha mantıklıydı. Dolayısıyla aşağıdaki gibi bir kod parçasını kullanamaya ve ilgili sınıfları otomatik olarak ürettirmeye karar verdik. Unutmadan burada <strong>Oracle DataAccess Client</strong>' ın - <strong>ODP.Net</strong>' in <strong>4.0</strong> sürümünü kullandığımızı ifade edelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using Oracle.DataAccess.Client;
using System.Configuration;
using System.IO;
using System.Text;
namespace GenerateDataTables
{
class Program
{
static void Main(string[] args)
{
string conStr = ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString;
string rootPath = ConfigurationManager.AppSettings["RootPath"];
string getUserViewsQuery = "select view_name from user_views";
string getColumnNamesQuery = "SELECT column_name FROM user_tab_columns WHERE table_name = :table_name ORDER BY column_id";
StringBuilder builder = new StringBuilder();
OracleConnection conn = new OracleConnection(conStr);
OracleCommand cmdGetViews = new OracleCommand(getUserViewsQuery, conn);
OracleCommand cmdGetColumnNames = new OracleCommand(getColumnNamesQuery, conn);
cmdGetColumnNames.Parameters.Add(":table_name", OracleDbType.Varchar2);
conn.Open();
OracleDataReader viewReader = cmdGetViews.ExecuteReader();
while (viewReader.Read())
{
builder.AppendLine("using System.Data;\n");
builder.AppendLine("namespace DBTables{\n");
builder.AppendFormat("\tpublic partial class {0}DataTable \n\t: DataTable {{ \n", viewReader["view_name"].ToString());
cmdGetColumnNames.Parameters[":table_name"].Value = viewReader["view_name"].ToString();
OracleDataReader columnsReader = cmdGetColumnNames.ExecuteReader();
while (columnsReader.Read())
{
builder.AppendFormat("\t\tpublic DataColumn {0} {{get;set;}}\n", columnsReader["column_name"].ToString());
}
columnsReader.Close();
builder.AppendFormat("\t}}\n");
builder.AppendFormat("}}");
File.WriteAllText(string.Format("{0}{1}.cs",rootPath,viewReader["view_name"].ToString()), builder.ToString());
builder.Clear();
}
viewReader.Close();
conn.Close();
}
}
}</pre>
<p>Kısaca kodda neler yaptığımıza bir bakalım dilerseniz. En önemli nokta kullandığımız iki <strong>select</strong> sorgusu. İlk sorgumuz ile <strong>Connection String</strong>' de<em>(app.config dosyasından alıyoruz)</em> yer alan <strong>Oracle</strong> şemasının erişim yetkisi dahilinde olan View' ları elde etmekteyiz. Bunun için <strong>user_views</strong> nesnesi kullanılmakta. İkinci <strong>Select</strong> sorgusu ise her bir <strong>View</strong>' un kolon adlarını döndürmekte. Bunun için de <strong>user_tab_columns</strong> db nesnesine gidiyor ve parametre olarak view adını veriyoruz. Böylece üretilecek <strong>DataTable</strong> türevli sınıfların özelliklerini elde etmiş oluyoruz. <strong style="line-height: 1.8;">StringBuiler</strong><span style="line-height: 1.8;"> sınıfından yararlanarak da </span><strong style="line-height: 1.8;">DataTable</strong><span style="line-height: 1.8;"> türevli sınıfların içeriğini yazdırmaktayız. Aynı, bir kod editöründe C# sınıfı yazar gibi düşünerek hareket etmemiz önemli. Örneğin kod dosyasının başında </span><strong style="line-height: 1.8;">using System.Data</strong><span style="line-height: 1.8;"> bildiriminin olması, özelliklerde </span><strong style="line-height: 1.8;">System.Data.DataColumn</strong><span style="line-height: 1.8;"> yerine </span><strong style="line-height: 1.8;">DataColumn</strong><span style="line-height: 1.8;"> kullanabilmemize olanak tanıyor. </span>Sonuç olarak <strong>Oracle</strong> tarafındaki <strong>View</strong>' lara karşılık gelecek basit <strong>DataTable</strong> türevli tipleri oluşturmuş bulunuyoruz. Bu işlemlerin arından tek yaptığımız ilgili sınıfları ayrı bir sınıf kütüphanesi altında toplamak oldu.</p>
<p>Yine hatırlatmak gerekirse bu tipler veriyi Oracle tarafından çektikten sonra tutmak ve arayüzlerde göstermek amacıyla geliştirilmiş durumda. Normal şartlarda araçlar ile üretilen <strong>Typed DataSet/DataTable</strong>' ler çok daha fazla fonksiyonellik<em>(önreğin Insert, Update, Delete işlemleri için)</em> içeriyor. Eğer bu fonksiyonellikere de ihtiyaç varsa ilgili kod üretim parçasını buna göre düzenlemekte yarar var. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutu günler dilerim.</p>2016-03-19T18:21:00+00:00oracledatatablec#.nettype safetybsenyurtÜzerinde çalıştığımız ve uzun süredir canlı ortamda yaşamakta olan eski bir ürünümüz geçtiğimiz günlerde kod kalite taramalarından birisine girdi. Vaktinde her zaman olduğu gibi alel acele yazılmak zorunda olan kodlar bir kaç ana kategori altında çeşitli tipte ihlallere yakalandı. Bunlardan birisi de Strongly Typed DataSet kullanımına ilişkindi.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=a5b07bc3-935c-4eb1-a332-b5677fae8c0a0https://www.buraksenyurt.com/trackback.axd?id=a5b07bc3-935c-4eb1-a332-b5677fae8c0ahttps://www.buraksenyurt.com/post/oracle-view-lari-icin-otomatik-datatable-lar-uretmek#commenthttps://www.buraksenyurt.com/syndication.axd?post=a5b07bc3-935c-4eb1-a332-b5677fae8c0a