C# 4.0 Dynamic Type - Reflection Hız Testi

C# 4.0 ile gelen Dynamic type’ların ilk bakışta sadece .NET ortamı dışında yazılmış bir tipi, .NET ortamında kullanmak amacıyla getirildiğini düşünebilirsiniz. Ancak Dynamic tipleri, halihazırda Reflection tiplerini kullanarak kod geliştirdiğimiz durumlarda Reflection tipleri yerine kullandığımızda ciddi performans kazançları elde edebiliyoruz. Dolayısıyla C# 4.0 ile geliştirilen uygulamalarda reflection tipleri yerine dynamic tipleri kullanmak oldukça mantıklı hale geliyor.

Aşağıdaki örnekte IMedia arayüzünü(interface) uygulayan Music ve Video adında iki sınıfımız(class) var. Hazırladığım basit bir konsol uygulamasında birer tane Music ve Video nesnesi oluşturarak bu nesnelerin Play işlemlerini gerçekleştirecek generic PlayMedia<T> metodunu çağırıyorum. Senaryo icabı reflection veya dynamic type kullanmayı zorunlu kılmak için, PlayMedia metoduna Play metodu içeren herhangi bir nesnenin gelebileceği şekilde tasarladım. PlayMedia metodunun 20 ve 21 numaralı satırlarında dynamic tip kullanılarak metoda gönderilen ve tipi derleme zamanında belirli olmayan media nesnesinin Play metodu çağrılmış. Eğer Reflection tiplerinin kullanılmasını test etmek isterseniz 20 ve 21 numaralı satırları yorum satırı haline getirip, 19 numaralı satırın yorumlarını kaldırabilirsiniz. İlk olarak sadece 19 numaralı satırı çalıştırarak test sonuçlarını alıyorum. Sonrasında 20 ve 21 numaralı satırları çalıştırarak test sonuçlarını aldığımda aradaki fark gerçekten oldukça cezbedici :)

Reflection bir tipin yapısını çözümleyerek içerisindeki metot, field gibi üyelere erişmeye çalışmaktadır. Ancak dynamic tip içerdiği tipi çözümlemeden, doğrudan istenilen üyeye erişeceği için sonuca daha hızlı ulaşabilecektir. Zaten aşağıdaki gibi bir test çalışması yapacak olursanız sonucun bu doğrultuda çıkacağını göreceksiniz.

Örnekte kullandığımız tiplerin sınıf diyagramı aşağıdaki gibidir.

    1 class Program

    2 {

    3     static void Main(string[] args)

    4     {

    5         Music m = new Music { FileName = "amazing.mp3" };

    6         Video v = new Video { FileName = "avatar_fragman.wmv" };

    7 

    8         PlayMedia<Music>(m);

    9         PlayMedia<Video>(v);

   10         Console.ReadLine();

   11     }

   12 

   13     static void PlayMedia<T>(T media)

   14     {

   15         DateTime d1, d2;

   16         d1 = DateTime.Now;

   17         for (int i = 0; i < 1000000; i++)

   18         {

   19             //media.GetType().GetMethod("Play").Invoke(media, null);

   20             dynamic d = media;

   21             d.Play();

   22         }

   23 

   24         d2 = DateTime.Now;

   25         TimeSpan t = d2 - d1;

   26 

   27         Console.WriteLine(t.TotalSeconds);

   28     }

   29 }

Test sonuçlarından da görüleceği gibi, reflection kullanılarak 1.000.000 kayıt üzerinde yapılan işlem ortalama 1.85 saniye sürerken, dynamic tip kullandığımda ortalama 0.17 saniye gibi bir süre alıyor. Yani dynamic tip kullanıldığında 10 kat hız sağlanmış görünüyor. Tabi ki farklı senaryolarda daha farklı sonuçlar elde edilebilir, ama dynamic tipin reflectiona göre açık şekilde daha hızlı çalıştığını söybu tarz işlemlerde performans adına daha iyi bir seçim olacağı ortada.

12 Mayıs 2010 Çarşamba 21:32

Yorum - RSSYorumlar (3)

Kategori: .NET Framework | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

LINQ to SQL’de Bulk Delete İşlemleri

LINQ to SQL’in temel hedef noktası projelerdeki Select işlemleridir. Tabi ki bunun yanında diğer CRUD işlemleri olan Insert, Update ve Delete sorguları da DataContext nesneleri üzerinden gerçekleştirilebiir. Bu işlemlerle ilgili daha önceden yazdığım blog girdimi bu linkten okuyabilirsiniz. Bu yazımda LINQ to SQL yapısı dahilinde veritabanında çalıştıracağımız Bulk Delete(birden fazla kaydı etkileyen delete işlemleri diyelim) işlemlerini inceleyeceğiz. Eğer daha önceden LINQ to SQL’de yaptığınız delete işlemlerini SQL Profiler’dan gözlemlemediyseniz yazımızın ilerleyen kısımlarında(hatta hemen devamında) ilginç bir detayla karşılaşacaksınız.

Öncelikli olarak LINQ to SQL üzerinde basit bir delete işlemi için gerekli kodları yazalım ve SQL Profiler’da bizi nasıl bir süpriz bekliyormuş görelim. Northwind veritabanındaki Product tablosunu içeren bir LINQ to SQL sınıfı(.dbml dosyası) oluşturuyor ve basit bir konsol uygulaması üzerinden CategoryID değeri 10 olan ürünleri siliyorum(Veritabanında Id değeri 10 olan bir kategori yok, eğer Constraint’lere takılmadan bu örneği yazmak istiyorsanız yeni bir kategori ve bu kategoriye ait birkaç ürünü Product tablosuna eklemenizi tavsiye ederim).

Program.cs

NorthwindDataContext ctx = new NorthwindDataContext();

var p = from urun in ctx.Products

        where urun.CategoryID == 10

        select urun;

 

ctx.Products.DeleteAllOnSubmit(p);

ctx.SubmitChanges();

Şu an üzerinde çalıştığım tabloda 10 numaralı kategoriye ait 4 tane kayıt var. Uygulamayı çalıştırıp SQL Profiler’dan sorguların çalıştığını nasıl izliyoruz.

Sonuç biraz enteresan olsa da temel olarak Select sorguları için hazırlanmış bir ortamın bundan daha fazlasını yapması beklenmez sanırım:) Şekile bakıp da hala nedir burada enteresan olan diye soranlarınız varsa; 4 tane farklı delete sorgusunun çalışmasıdır enteresan olan. Böyle bir işlem sonucunda çoğumuz arka planda Delete From Products Where CategoryID=10 gibi bir sorgunun çalışmasını bekleriz, çünkü veriye erişimi biz yazdığımız zaman delete sorgumuz böyle olacaktır. Ama unutmamak lazım ki artık biz yazmıyoruz! LINQ to SQL bu tarz bir delete ifadesini arka planda her bir satırı etkileyecek şekilde tasarlar ve SQL Server’a gönderir. Dolayısıyla aynı anda yüzlerce-binlerce kayıdı etkileyecek delete işlemi yapıyorsak, resimde gördüğümüz çıktı bizi performans kuşkularına düşürebilir. Hem satırların tek tek silinmesi, hem de network trafiği açısından bu kadar sorgunun taşınması sorun oluşturabilir. O zaman bu durumu iyileştirmek için nasıl bir yol izlemek lazım?

Bu sorunun cevabını bulmak için ben yaklaşık 3 saatimi harcadım. İlk olarak DataContext nesnesi içerisinde işe yarayacak bir üye aradım ama nafile. Ardından inatla kendim çözeceğim diye yola koyulup işe yarar birşeyler çıkaramayınca, 3 saatin sonunda dünyayı yeniden keşfetme inadımdan vazgeçip arama motorlarının yollarını tuttum. Bu linkten orjinaline ulaşabileceğiniz yazıda gayet güzel ve basit bir metot ile bu işin üstesinden gelinmiş(Yazanın ellerine sağlık). Aşağıda bu metodu görebilirsiniz. Metodu extension metot olarak uygulayacağımız için DataContext’ten eriştiğimiz Table nesneleri üzerinden çağırmamız daha kolay olacak. Metotla ilgili açıklamaları ilgili satırlara yorum olarak ekliyorum.

TableExtensions.cs

public static class TableExtensions

{

    //DataContext'te yer alan Table<TEntity> nesneleri uzerinden cagrilabilmesi icin Extension Method olusturuyoruz

    public static int BulkDelete<TEntity>(this Table<TEntity> table, IQueryable idsToDelete) where TEntity : class

    {

        //Uzerinde sorgu calistirilacak tablonun metadata bilgileri cekiliyor

        MetaTable metaTable = table.Context.Mapping.GetTable(typeof(TEntity));

        string tableName = metaTable.TableName;

 

        //Bu arada bulk delete metodumuz sadece bir tane PK kolonu tasiyan tablolarda kullanilabilir!

        if (metaTable.RowType.IdentityMembers.Count != 1)

        {

            var keyFields = from im in metaTable.RowType.IdentityMembers

                            select im.MappedName;

 

            if (keyFields == null || keyFields.Count() < 1)

            {

                keyFields = new List<string>();

            }

            //Eger tabloda birden fazla PK varsa Exception firlatiliyor

            throw new ApplicationException(string.Format("Error in TableExtensions.BulkDelete<TEntity>: Table {0} has a compound key.  BulkDelete can only operate on tables with a single key field.  (key fields found: {1}", tableName, string.Join(",", keyFields.ToArray())));

        }

        string primaryKey = metaTable.RowType.IdentityMembers[0].MappedName; //PK kolonu bulduk

 

        System.Data.Common.DbCommand origCmd = table.Context.GetCommand(idsToDelete);

        string toDelete = origCmd.CommandText;

        //Iste bu kisimda Delete sorgusu olusturuluyor ve

        //subquery ile Select cumlesinden donen kayitlar Delete ifadesine dahil ediliyor

        string sql = string.Format("delete from {0} where {1} in ({2})", tableName, primaryKey, toDelete);

 

        origCmd.CommandText = sql;

 

        bool openedConn = false;

 

        //Baglanti acma-kapama ve sorgunu calismasi islemleri burada yapiliyor

        if (origCmd.Connection.State != ConnectionState.Open)

        {

            openedConn = true;

            origCmd.Connection.Open();

        }

 

        try

        {

            //Sorgu burada dogrudan calistiriliyor. Dolayisiyla bu metotdan sonra

            //DataContext.SubmitChanges() metodunu cagirmaya gerek kalmiyor

            return origCmd.ExecuteNonQuery();

        }

        finally

        {

            if (openedConn)

            {

                origCmd.Connection.Close();

            }

        }

    }

}

Metot DataContext nesnemiz içerisinde yer alan entity tablolarımız için hazırlanmış bir extension metottur. Yani projemizin herhangi bir yerinden DataContext.TabloIsmi.BulkDelete() şeklinde çağrılabilir. Parametre olarakta sorgulanabilir bir koleksiyon alıyor, yani LINQ sorgumuzun sonucu parametre olarak bu metoda gönderilecek. Metot içerisinde ele alınan entity tablosunun yapısı çözümlenerek tablo ismi ve primary key kolonu elde ediliyor. Böylece DELETE FROM TabloAdi WHERE PrimayKeyKolonu IN (Silinecek satırların PK değerleri) şeklindeki bir sorguyu hazırlamamız mümkün olacaktır. Dikkat ettiyseniz buradaki sorgu sayesinde tüm delete işlemlerini tek bir sorgu ile yapabileceğiz.

Metot ile ilgili bir diğer dikkat edilmesi gereken nokta ise yukarıda koyu puntolarla yazdığım DELETE ifadesinin sonunda yer alan subquery’nin döndüreceği değerler. Sorguyu biraz daha açacak olursak; burada incelediğimiz Product tablosu için DELETE FROM Product WHERE ProductID IN (SELECT ProductID FROM Product WHERE …) şeklinde bir sorgumuz olmalı. Yani subquery olarak saklanan SELECT sorgusu geriye sadece primary key kolonun değerlerinden oluşan satırları döndürmelidir. O zaman BulkDelete metoduna bir LINQ sorgusundan gelen tüm bilgileri değil de sadece primary key kolonundan oluşan kümeyi göndermemiz gerekecektir. İzah etmek istediğim bu durum anlaşılmadıysa aşağıdaki kod parçalarını dikkatlice incelemenizi tavsiye edeceğim.

Program.cs

NorthwindDataContext ctx = new NorthwindDataContext();

var p = from urun in ctx.Products

        where urun.CategoryID == 10

        select urun;

 

//BulkDelete metoduna sadece PK kolonundaki bilgileri gondermemiz gerekecegi icin

//sorgu sonucundaki var degiskeninin sadece ProductID alanini seciyoruz

ctx.Products.BulkDelete(p.Select(pr => pr.ProductID));

 

Görüldüğü gibi Lambda ifadesi kullanılarak LINQ sorgusu sonucunda gelmesi beklenen kayıtların sadece ProductID bilgilerini metoda gönderdik. Yine dikkat çekmek istediğim bir nokta da bu sorgulama sonunda ctx nesnesinin SubmitChanges metodunu çağırmamıza gerek olmadığıdır. Çünkü extension metot içerisinde yeni bir sorgu nesnesi oluşturulup bunun çalıştırılması sağlanıyor ve DataContext nesnesi üzerinden ek bir işlem daha yapmamıza gerek kalmıyor. Metodun çalışması sonucunda SQL Server’a gönderilecek bilgileri SQL Profiler adlı aracımızdan izlediğimizde aşağıdaki gibi bir sonuçla karşılaşacağız.

Ekran çıktısında da görüleceği gibi SQL Server’a gönderilen sorgu sayısı artık bire inmiş durumda. Eğer LINQ to SQL kullandığınız projelerinizde aynı anda çok fazla sayıda kayıt silmek gibi bir ihtiyacınız olursa böyle bir metot işinizi görecektir.

5 Temmuz 2009 Pazar 23:13

Yorum - RSSYorumlar (0)

Etiket: ,
Kategori: .NET Framework | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

Bilkent Üniversitesi Yazılım Teknolojileri Seminerleri Ardından

Geçtiğimiz hafta sonu Ankara’da Bilkent Üniversitesi’ndeydim. Daron Yöndem ile beraber Bilkentli teknolojistlerle dolu dolu iki günde .NET teknolojileriyle ilgili bilgi ve tecrübelerimizi paylaşmaya çalıştık. Ben Cumartesi günü C# 3.0 ve LINQ, Pazar günü ise ASP.NET 3.5 ve SP1 ile Gelen Yenilikler sunumlarımla bu güzel etkinliğe katkıda bulunurken, Daron’da Silverlight, WPF ve WCF sunumlarını gerçekleştirdi. Karlı ve soğuk bir Ankara havasında salonu tıka basa dolduran öğrenci arkadaşlarla birlikte oldukça keyifli sunumlar gerçekleştirdik. Uzun bir aradan sonra tekrar Ankara’da olmak, Ankara’daki arkadaşlarımla zaman geçirmek bu hafta sonunun anılarımda daha da güzel bir şekilde yer almasını sağladı :)

bilkent_ilkgun2  bilkent_ilkgun1
Etkinliğin ilk gününden iki kare

Vee katılımcı arkadaşlara söz verdiğim gibi etkinlik dosyalarını paylaşıyorum. Oturumlarda kullandığım sunumları ve kod örneklerini aşağıdaki linklerden indirebilirsiniz. Etkinlikte emeği geçen başta MSP arkadaşlarım Ali Uğur Çakmak ve Alper Özçetin olmak üzere tüm arkadaşlara teşekkürlerimi buradan da iletiyorum.

C# 3.0 ve LINQ Sunumu
C# 3.0 ve LINQ Demolar
ASP.NET 3.5 ve SP1 Sunumu
ASP.NET 3.5 ve SP1 Demolar

24 Şubat 2009 Salı 00:28

Yorum - RSSYorumlar (0)

Kategori: ASP.NET | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

Netron Techweek'deki Sunumlarım ve Örnek Kodlar

Bu hafta içerisinde gerçekleşen ve benimde iki oturumda konuşmacı olarak yer aldığım Netron Techweek her sene olduğu gibi sektördeki önemli gelişmeleri takip edebilmek adına herkes için güzel bir etkinlik oldu. Özellikle ASP.NET MVC Framework oturumumda, hala gelişme aşamasında olan ve henüz tam bir ürün kimliğine bürünmemiş bir yapının bile katılanların ciddi anlamda ilgisini çekmesi ve olumlu tepkiler alması, ilerleyen günlerde MVC uygulamalarındaki artacak eğilimin bir işaretiydi. LINQ'in büyüleyici ortamının özellikle bu ortamı yeni tanıyanlarda bıraktığı izlenimler hakkında yorum dahi yapmama gerek yoktur diye düşünüyorum :)

Aşağıdaki linklerde gerçekleştirdiğim sunumları ve örnek kod dosyalarını bulabilirsiniz.

ASP.NET MVC Framework - Sunum Dosyası
ASP.NET MVC Framework - Örnek Kodlar

LINQ Overview - Sunum Dosyası
LINQ Overview - Örnek Kodlar

25 Eylül 2008 Perşembe 23:32

Yorum - RSSYorumlar (0)

Kategori: ASP.NET | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

Netron Techweek Etkinliğinde Konuşmacıyım

Netron TechweekGeleneksel hale gelen ve bilişim sektörünün en önemli etkinliklerinden olan Netron Techweek (Teknoloji Haftası) bu yıl 22-26 Eylül tarihleri arasında Netron Altunizade Genel Merkezi'nde gerçekleşecek. Infrastructure ve Development kategorilerinde yirminin üzerinde oturumun olacağı bu beş günlük etkinlikte benim de iki farklı konuda sunumlarım gerçekleşecek.

Gerçekleştireceğim sunum konuları ve zaman bilgilerini aşağıda bulabilirsiniz. Yine etkinlik ile ilgili detaylı bilgilere ve kayıt formuna erişmek için aşağıdaki linkleri kullanabilirsiniz. Bu önemli etkinliği kaçırmamanızı tavsiye ediyorum.

Konuşmacı olduğum oturumlar:

Konu  Zaman
ASP.NET MVC Framework  23 Eylül 2008 Salı / 14:30-16:30
LINQ Overview  24 Eylül 2008 Çarşamba / 14:30-16:30

Etkinlik Takvimi
Kayıt Formu

16 Eylül 2008 Salı 16:10

Yorum - RSSYorumlar (0)

Kategori: ASP.NET | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

LINQ to SQL'de Insert, Update ve Delete İşlemleri

LINQ to SQL ile Insert,Update,Delete İşlemleriBildiğiniz gibi LINQ adını verdiğimiz dile entegre sorgularla artık .NET ortamında koleksiyon tabanlı nesneleri sorgulayabiliyoruz. Buradaki sorgulamaların temel amacı normalde foreach döngüleri, if-else kontrol yapılarıyla yapabildiğimiz uzun ve karmaşık işlemleri tıpkı T-SQL'de olduğu gibi sade Select sorguları yazarak daha basit hale getirmektir. LINQ'in asıl çıkış noktası ise uygulama içerisinde büyük ve karmaşık olan veri nesnelerini (entity nesnelerini) kolay ve pratik şekilde sorgulanabilmesini sağlamak ve büyük çaplı projelerde yazılımcıların en büyük yüklerinden birisini hafifletmektir.

LINQ to SQL ile ilgili olarak en çok sorulan sorulardan birisi de Insert, Update ve Delete işlemlerinin nasıl gerçekleştirilebileceği. LINQ ifadelerinde Insert, Update ve Delete gibi anahtar kelimelerle sorgular yazılamadığını söylemeye gerek yok sanırım:) Dolayısıyla LINQ to SQL'de amaç veri nesneleriyle çalışmak ise bir şekilde bu tip işlemleri de gerçekleştirebilmek gerekecektir. LINQ to SQL Classes (.dbml) dosyaları veritabanında yer alan veri nesnelerini doğrudan uygulamamıza class'lar olarak aktarmamızı sağlamaktadır. Örneğin Northwind veritabanındaki Product tablosunu uygulamamızda veri class'ı olarak oluşturduğumuzda DataContext nesnesinin içerisindeki Products özelliği(property) Products tablosundaki ürünleri koleksiyon olarak döndürecektir. İşte bu property'nin bazı metotları bizim Insert, Update ve Delete gibi işlemleri gerçekleştirebilmemizi sağlayacaktır. Bu metotlardan;

- InsertOnSubmit, Insert işlemini
- DeleteOnSubmit, Delete işlemini

gerçekleştirebilmektedir. Peki Update işlemi nerede? Update işlemini ise LINQ sorgusu sonucundan gelen kayıt veya kayıtların değerlerini değiştirerek gerçekleştirebileceğiz. Buradaki her üç işleminde sonuçlarının veritabanına aktarılabilmesi için DataContext nesnesinin SubmitChanges isimli metodunun çalıştırılması yeterli olacaktır. Aşağıdaki kod parçalarında LINQ to SQL'de veri ekleme, silme ve güncelleme işlemlerinin nasıl yapılabileceği görülmektedir. Kodlarda yer alan northWind adındaki nesne DataContext nesnemizdir.

Product eklenecekUrun = new Product() { ProductName = "Acer Aspire 5100", UnitPrice = 1290, UnitsInStock = 15, CategoryID = 9 };

northWind.Products.InsertOnSubmit(eklenecekUrun); // Eklenecekler listesine yeni bir ürün eklendi

 

Product guncellenecekUrun = northWind.Products.First(u => u.ProductID == 69);

guncellenecekUrun.ProductName = "HP Pavilion 3355"; // Seçilen ürünün ProductName özelliği değiştirildi

 

Product silinecekUrun = northWind.Products.First(u => u.ProductID == 79);

northWind.Products.DeleteOnSubmit(silinecekUrun); // Seçilen ürünü silinecekler listesine at

 

northWind.SubmitChanges(); // Yukarıda yapılan güncellemeler veritabanına gönderildi

9 Eylül 2008 Salı 23:07

Yorum - RSSYorumlar (5)

Etiket: ,
Kategori: C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

C# Kodlarınızı VB.NET'e, VB.NET Kodlarınızı C#'a Çevirin!

Visual Basic.NET'te yazmış olduğunuz kodları C#'a, yine C# ile yazmış olduğunuz kodları Visual Basic.NET'e bir internet sayfasındaki küçük bir metin kutusu sayesinde hızlıca çevirebiliyorsunuz. Ben zaten kendim çeviririm diye düşünebilirsiniz ama uzun kodlamalarda işinizi hızlandıracak bir araç bence. Sık Kullanılanlarınıza eklemenizi tavsiye ediyorum.

C# -> VB.NET
VB.NET -> C#

31 Mayıs 2006 Çarşamba 03:34

Yorum - RSSYorumlar (0)

Etiket:
Kategori: .NET Framework | C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

C# ile Yazılmış Açık Kaynak Projeler

Nette gezerken gözüme çarpan sitelerden birisi: http://www.csharp-source.net/

C# ile proje geliştirenler için faydali projeler bulunuyor sitede. Geçmişte çalışan bazı linkler şu anda çalışmıyor ama geçici bir sorun olabilir. İlgilenenlere yararlı olur umarım...
2 Ekim 2005 Pazar 17:13

Yorum - RSSYorumlar (0)

Kategori: C#

facebook'da Paylaş   twitter'da Paylaş   friendfeed'de Paylaş   del.icio.us'da Paylaş   stumpleupon'da Paylaş   Permalink

Bağlantılar



Takip Et

RSS Feed twitter friendfeed

Seminer/Webiner Programım

  • Seminer-WebinerASP.NET 4.0 WebForms Yenilikleri (Microsoft İstanbul Ofisi)
    29 Mayıs 2010

  • Seminer-WebinerVisual Studio 2010 Yenilikleri (Osmangazi Üniversitesi)
    15 Mayıs 2010

  • Seminer-WebinerASP.NET AJAX ile Zengin Internet Uygulamaları Geliştirme (Microsoft İstanbul Ofisi)
    3 Mayıs 2010

>> Etkinlik Takvimi