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

GAC’da Yer Alan Assembly Dosyalarını Normal Görünümde Listelemek

Özellikle projelerimizi production ortamında güncellediğimiz zamanlarda bu ortamda yer alan dosyaları yedeklemek ihtiyacı hissederiz. IIS altında yer alan dosyalara ulaşmakta sorun yok, ancak GAC(Global Assembly Cache) içerisinde yer alan dll dosyaları Windows tarafından farklı görünümde listelendiği için doğrudan bu dosyaları kopyalama şansımız olmaz. GAC içerisinde yer alan dll dosyalarına doğrudan erişmek isterseniz MS-DOS ekranından gireceğiniz şu komut satırı işinize yarayacaktır.

regsvr32 –u C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\shfusion.dll

Bu komut ile GAC’da yer alan assembly dosyaları normal şekilde listelenir. Yedeklemenizi yaptıktan sonra tekrar eski görünüme geçmek isterseniz aynı komutu –u parametresini eklemeden kullanmanız gerekir. Aşağıdaki gibi;

regsvr32 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\shfusion.dll

Not: Bazen komutu çalıştırmanıza rağmen dosyaların düzgün listelenmediğini görebilirsiniz. Bu durumda pencereyi yenilemeyi veya pencereyi kapatıp yeniden açmayı deneyebilirsiniz.

12 Kasım 2009 Perşembe 00:38

Yorum - RSSYorumlar (0)

Etiket: ,
Kategori: .NET Framework

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

.NET Framework 4.0 Beta1 ve Visual Studio 2010 Beta 1 Resmen Duyuruldu

Eğer MSDN üyesi değilseniz başlığa bakıp hemen heyecanlanmayın, çünkü herkese açık yüklemeler tahminen 1 hafta içinde başlayacak. Eğer MSDN üyesi iseniz .NET Framework 4.0 ve Visual Studio 2010’un Beta1 sürümlerini bilgisayarınıza yükleyebilirsiniz. MIX’09 sunumlarını izlemeyenler için en büyük süprizlerden birini söyleyeyim; Visual Studio 2010 arayüzü WPF ile tasarlandı. Visual Studio ile ilgili ilk izlenimim ise RAM kullanımı biraz yüksek olsa da çok hızlı! WPF ile hazırlanan arayüzlerin Vista üzerinde sizde bırakacağı seyir zevki gerçekten oldukça hoş. 1-2 sorun ile karşılaştım ama Beta sürüm sonuçta gayet normal.

Bu post 21 Mayıs 2009 09:04'te güncellendi. Ek bilgiler aşağıdadır:

Herkese açık download linkleri:

Visual Studio 2010 Professional Beta 1 – Web Installer
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=75cbcbcd-b0e8-40ea-adae-85714e8984e3

Visual Studio Team System 2010 Team Suite Beta 1 – Web Installer

http://www.microsoft.com/downloads/details.aspx?FamilyID=85520793-68fc-4361-a8b6-dc2cff49c8d2&displaylang=en

20 Mayıs 2009 Çarşamba 08:48

Yorum - RSSYorumlar (0)


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

WPF Uygulamalarında DocumentViewer ile XPS Dokümanlarının Görüntülenmesi

XPS, Windows Vista ile birlikte kullanımı gittikçe yaygınlaşan bir dosya formatı. Word, Excel, Powerpoint dosyalarını XPS formatına çevirerek Office kurulu olmayan bir bilgisayarda dahi Internet Explorer 7 gibi bir tarayıcı üzerinde görüntüleyebiliyoruz. Bu yazıda kullanımı yaygınlaşan XPS formatınındaki dosyaları WPF(Windows Presentation Foundation) uygulamalarında DocumentViewer kontrolüyle nasıl görüntüleyebileceğimizi anlatmaya çalışacağım.

WPF ile gelen kontrollerimizden olan DocumentView üzerinde bir XPS dosyası görüntüleyebilmek için XpsDocument tipinden bir nesneye ihtiyacımız olacaktır. mscorlib.dll'de bulunmayan bu sınıf için ReachFramework.dll'i projemize eklememiz gerekiyor.

ReachFramework.dll'in Add Reference seçeneğinden seçilmesi
ReachFramework.dll'in proje referanslarına eklenmesi

İlgili dll dosyasını projemizin referanslarına ekledikten sonra artık uygulamamıza geçebiliriz. Yapacağımız işlem oldukça basit aslında; penceremize bir tane DocumentViewer kontrolü ekliyor ve pencerenin Loaded event'ine aşağıda gördüğünüz iki satırlık ifadeyi ekliyoruz.

Window1.xaml

<Window x:Class="WpfDocumentViewerXps.Window1"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Title="DocumentViewer ile XPS Dosyalarının Görüntülemesi" Height="320" Width="600">

    <Grid>

        <DocumentViewer Name="documentViewer1" />

    </Grid>

</Window>

Window1.xaml.cs

...

using System.Windows.Xps.Packaging; //Gerekli isim alanı

namespace WpfDocumentViewerXps

{

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            //XpsDocument nesnesi belirtilen yoldaki dosya içeriğini

            //GetFixedDocumentSequence metodu ile kontrolümüze sunacaktır

            XpsDocument xps = new XpsDocument(@"D:\test.xps", System.IO.FileAccess.Read);

            documentViewer1.Document = xps.GetFixedDocumentSequence();

        }

    }

}

 

DocumentViewer ile XPS dokümanını görüntüledik
DocumentView'da görüntülenen XPS dokümanı
20 Aralık 2008 Cumartesi 02:11

Yorum - RSSYorumlar (0)

Etiket:
Kategori: .NET Framework

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

.NET Framework 4.0 ve Visual Studio 2010 CTP

.NET Framework'ün Yeni Logosu Geçtiğimiz haftalarda gerçekleşen PDC 2008 etkinliğinde Microsoft önümüzdeki yıl resmi olarak duyurmayı planladığı .NET Framework 4.0'ın ve Visual Studio 2010'un CTP(Community Technology Preview) sürümlerini duyurdu. Yazılım geliştiriciler için her zamanki gibi önemli yeniliklerin geleceği bu sürümleri şimdiden tanımak ve incelemek için aşağıdaki linkten detaylı bilgileri elde edebilir ve gerekli kurulumları bilgisayarınıza yükleyebilirsiniz.

Visual Studio 2010 ve .NET Framework 4.0 Community Technology Preview

20 Kasım 2008 Perşembe 22:50

Yorum - RSSYorumlar (0)


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

.NET Framework 4.0 Posteri

Birden geçtiğimiz sene bu zamanlarda bloglarımızda ardı ardına yayınlanan poster başlıkları geldi aklıma. PDC 2008 etkinliğinde önümüzdeki aylarda ve yıllarda bizi bekleyen bir çok yenilik duyuruldu ve duyurulmaya da devam ediliyor. Yazılım dünyasını en çok heyecanlandıran yenilikler tabi ki .NET Framework 4.0 ve Visual Studio 2010. .NET Framework 4.0 ile mimariye katılması beklenen namespace ve tiplerin bulunduğu bir poster az önce RSS Reader'ıma düştü ve hemen paylaşayım dedim.

.NET Framework 4.0 - Namespace ve Tipler

Aşağıdaki linklerden .NET Framework 4.0 posterlerine erişebilirsiniz:

.NET Framework 4.0 Poster-PDF
.NET Framework 4.0 Poster-DeepZoom
30 Ekim 2008 Perşembe 11:23

Yorum - RSSYorumlar (0)


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

Mono 2.0 Resmen Duyuruldu

mono20_banner .NET Framework'ün platformdan bağımsızlığı adına oldukça önemli bir haber aslında bu. Zira Mono 2.0'ın resmi olarak duyuruldu, hem de sadece Linux işletim sistemleri için değil, Mac işletim sistemleri içinde! Evet yanlış duymadınız, .NET Framework uygulamaları artık Mono kurulu Linux ve Mac işletim sistemlerinde çalıştırılabilecek. Ayrıca Mono projesinin internet sitesinde yaptığım araştırmalarda Mono'nun IPhone üzerinde de çalıştırılabileceğine dair ibarelerde buldum ama net açıklama gözüme çarpmadığı için bu konuda kesin birşey söylemek doğru olmayabilir.

Projenin bu denli genişlemesinde elbetteki geçtiğimiz aylarda Microsoft ve Novell'in bazı konularda ortaklaşa çalışma kararı almalarının etkisi oldukça büyük. Zira Mono 2.0 projesi Novell'in desteğiyle geliştirilerek release olmuş durumda.

Mono kullanmayan birisi olarak sadece bu haberi sizlere duyurmak istedim. Test etme şansını bulanlar olursa bilgi ve tecrübelerini bizlerle paylaşmayı sakın unutmasınlar. D

8 Ekim 2008 Çarşamba 01:32

Yorum - RSSYorumlar (2)

Etiket:

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