daron yöndem | Microsoft Regional Director | Silverlight MVP
Microsoft Regional Director | Nokia Developer Champion | Azure MVP
Bu yazıyı yazmam 12 dakikamı aldı. Siz normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Dün Türkiye'deki MSP (Microsoft Student Partner) programının açılış etkinliğindeydim :) Etkinlikte biraz kişisel gelişim, kariyer ve biraz da Azure konuştuk gençlerle. Ülkedeki Azure konusundaki eksiklere artık şaşırmıyorum. Alıştım :) ama emin olun Cloud'un ne olduğunu bilmeyen veya bilerek saptıran kurumlara karşı bilinçli bir gençlik geliyor :) Birkaç üniversite oturumu ile bu kadar optimist olmam garip gelebilir ama optimizm iyidir ;)

Microsoft Student Partner Kick-Off Toplantısı

Gün boyunca diğer gördüğüm bir sorun ise aslında ilk defa tanışmadığım ve mentorluk yaptığım gençlerde de gördüğüm bir "çok şey öğrenmeliyim" sendromu :) Bu konuda uzun uzun konuşabilirim fakat özünde beni "Uykudan Önce Bir Doz" gibi başka kitaplar yazmaya da itiyor diyebilirim. Nasıl ki bundan bir süre önce "Hangi programlama dilini öğrenmeli?" sorusuna biraz garip bir cevap verdiysem aslında gençlerin kendi içlerinde sordukları çoğu sorunun yanlış cevapları aradığını düşünüyorum. Yavaş yavaş bu konunun bir "nesil" farkı yansıması olduğunu da düşünmeye başlıyorum. Anlayacağınız bu konuda bir şeyler paylaşabilmek için biraz daha düşünmem ve hazırlanmam gerekiyor :)

MSP açılış etkinliğinde beni misafir eden, Microsoft'tan sevgili Mustafa Kasap'a buradan teşekkür ediyorum. Benim için çok eğlenceli bir kaç saat oldu. Umarım gençler için de faydalı olmuştur ;)

Görüşmek üzere.

Bu yazıyı yazmam 61 dakikamı aldı. Siz normal bir okuma hızı ile ortalama 4 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Konuyla ilgili bir önceki yazı; Azure Storage Table Services

Table Services ile bir önceki yazıda bazı konuları bir sonraki :) yazıya yani bugüne bırakmıştık. Gelin şimdi kaldığımız yerden devam edelim.

Table Service ile uğraşırken relation desteğinin olmadığından ve aslında bunun da en büyük farklılıklardan biri olduğundan bahsetmiştik. Hatta eğer ki kuvvetli bir relational yapıya ihtiyacınız varsa tabi ki SQL Azure tarafına doğru kaymanız gerektiğini de söylemiştik :) Amma :) tabi ki mini relationlarla ilgili ilginç çözümler uygulayabiliriz. Bu ilginç çözümleri uygularken de isteyeceğimiz bir diğer şey Entity Group Transaction'ların mantıksal relation yapımızda kullanılabiliyor olması. Biliyorsunuz EGT'ler sadece aynı table ve aynı partition içerisinde olan nesneler arasında kullanılabiliyordu. Peki bir relational iki farklı nesneyi nasıl olur da aynı partition ve aynı table'da tutarız?

Çözüm basit!

Gerçekten basit :) Çünkü birincisi Table Services'daki her table'ın şeması tamamen esnek. Yani bir table yaratırken şema vermediğimiz gibi sonradan bir table'a istediğimiz kadar farklı şemada farklı nesneler koyabiliyoruz. Önemli olan tek birşey var, tüm nesnelerin kesinlikle PartitionKey, RowKey ve TimeStamp'i olmalı. Peki iki farklı nesneyi nasıl tek tabloda tutarız?

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
await table.CreateIfNotExistsAsync();

var yeniUrun = new Urun
{
    PartitionKey = "Musteri1",
    RowKey = "Ürün1",
    Adi = "Deneme",
    Aciklama = "Açıklama"
};
var yeniDetay = new UrunDetay
{
    DetayMetin = "Deneme",
    PartitionKey = "Musteri1",
    RowKey = "detay_Ürün1"
};

TableBatchOperation batchOperation = new TableBatchOperation();
batchOperation.InsertOrMerge(yeniUrun);
batchOperation.InsertOrMerge(yeniDetay);

IList<TableResult> results = await table.ExecuteBatchAsync(batchOperation);

Durum çok basit değil mi? Artık tabloda iki farklı şemada iki farklı nesne var. Bu noktada düşünmemiz gereken birkaç şey var :) Birincisi bir tabloya PartitionKey, RowKey harici bir sorgu gönderirsek ne olacak? Yani "where" kısmında "DetayMetin" ile ilgili araması olan bir sorgu gönderirsem aynı tabloda bütün Urun nesneleri de gezilecek mi? Oysa Urun'lerin böyle bir özelliği yok. İşte tam da bu sorunu çözmek ve aynı tablodaki bu iki farklı nesne tipini birbirinden rahat bir şekilde ayırt edebilmek için .... RowKey'e dikkat :) Aslında iki nesne arasındaki ilişkiyi de RowKey üzerinde tutmuş durumdayım. İlk nesnenin RowKey değerini alıp önüne bir "preFix" ekleyip UrunDetay nesnesinin RowKey'ine verdim. Böylece rahatlıkla istediğimiz zaman bir detay bilgisi almak istediğimizde ana nesnenin RowKey'inin başına "detay_" ekleyerek ikinci bir sorguyla da detayını alabiliriz ;)

Unutmadan, yukarıdaki senaryoda hem ana, hem de detay nesnesini aynı transaction'da gönderdiğimize de dikkat çekmek istiyorum :) Nitekim bu iki yapıyı iki farklı tabloda tutsak bu gibi pratik bir transaction desteğimiz olmayacaktı.

Peki tek tabloda iki farklı nesne tipi saklarsak okuma işlemi esnasından ne olur? Standard prosedürle devam ederseniz hiçbir şey olmaz :) Gelen verinin ne olduğuna, ne tipte olduğuna dinamik nesneler alarak tek tek kontrol etmeniz gerekir. Table Services arka planda sizin verdiğiniz farklı nesne şemalarında dinamik kısımları XML'e serialize ederek SQL'de saklıyor. Table Services'ın iç yapısı tahmin ettiğinizden daha basit aslında. Önceden tanımlı kolonlar olan PartitionKey, RowKey ve TimeStamp dışında her şey XML'e serialize edilip saklanıyor. Siz okumaya kalktığınızda ise malum deserialize edilirken aynı anda farklı şemalarda nesneler alırsanız bunu bir DyanmicEntity tablosu olarak size verecektir. Bu tablodaki her DynamicEntity'nin farklı bir şeması oluyor. Tabi bizim istediğimiz bu şemalardaki farklılıkların elimizdeki entity'lerle eşleşmesi. Örneğimizde kullandığımız entitylerde ufak bir değişiklik yaparak süreci kolaylaştırmak adına UrunDetay nesnesini Urun'den Inherit edecek şekilde ayarlayalım. Sonra da "Resolver" neymiş ona bakalım.

[C#]

EntityResolver<Urun> resolver = (pk, rk, ts, props, etag) =>
{
    Urun birUrun = null;
    if (props.ContainsKey("DetayMetin"))
    {
        birUrun = new UrunDetay(); 
    }
    else
    {
        birUrun = new Urun(); 
    }
    birUrun.PartitionKey = pk;
    birUrun.RowKey = rk;
    birUrun.Timestamp = ts;
    birUrun.ETag = etag;
    birUrun.ReadEntity(props, null);
    return birUrun;
};

TableQuery<DynamicTableEntity> query = new TableQuery<DynamicTableEntity>()
    .Where(TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Musteri1"),
        TableOperators.And,
        TableQuery.CombineFilters(
             TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "Ürün1"),
             TableOperators.Or,
             TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, 
                string.Format("detay_{0}", "Ürün1")))));
var result = table.ExecuteQuery(query, resolver);

Yukarıdaki kod içerisinde iki yeni yapı ile karşılaşıyoruz. Bunlardan ilki "EntityResolver". EntityResolver'ın görevi içerisindeki mantığa göre gelen DynamicEntity'lerin tam olarak neye çevrileceğine karar vermek. Biz örneğimizdeki basit bir şekilde gelen nesnenin "DetayMetin" adındaki bir property'si var mı, yok mu noktasını kontrol ediyoruz. Eğer DetayMetin yoksa demek ki gelen arkadaş bir Ürün :) Bu mantığa göre hangi nesnenin Initialize edileceğine karar verip mapping işini yine SDK'ye bırakıyoruz. Resolver'ımız hazır olduktan sonra sıra geliyor uygun sorguyu Table Services'a göndermeye. Öyle bir sorgu göndermeliyiz ki bir defada hem ana nesneyi hem de detay nesnesini alalım. Nitekim bu noktada "Arkadaş böyle yapacaksan bunu flatten etseydin ya!" diyebilirsiniz :) Senaryoya değil neyin nasıl yapıldığına odaklanın. Aksi halde tabi ki NOSQL ortamında sürekli beraber atomik şekilde kullanacağınız şeyleri iki ayrı entity olarak tutmak süper bir fikir değil.

Konumuza geri dönersek, yukarıdaki kod örneğinde Table Services'a karşı komplike bir sorgunun nasıl oluşturulduğunu görebilirsiniz. Amacımız yine tek sorgu ile tam istediğimiz veriyi almak. Sorguda PartitionKey verdiğimiz için zaten Cross-Partition bir gezinme yaşanmayacak. İçerisinde bulunduğumuz Partition'da da RowKey üzerinden arama yapıyoruz. Anlayacağınız her şey yolunda :)

Sorguyu çalıştırdığımızda geriye içerisinde iki entity bulunan bir liste geliyor. Liste içerisinde her nesne doğru şekilde bizim Urun ve UrunDetay nesnelerimizden yaratılacak deserialize edilmiş durumda. Her şey tam da istediğimiz gibi ;)

Görüşmek üzere!

Bu yazıyı yazmam 12 dakikamı aldı. Siz normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Bir süre önce sinsi bir şekilde Twitter ve Facebook'dan duyurmuştum :) Istanbul Azure Meetup adında bir buluşma grubu kurduk. Hatta ilk buluşmamızı da Aralık ayında gerçekleştirdik. Bu ay da ikinci buluşmamızı yaptık ve bu sefer konumuz "Azure ve Docker"dı. Azure Linux Ekibi'nden sevgili Ahmet Alp Balkan da bize misafir olarak katıldı.

Bundan sonraki aylarda da ayda bir buluşma olarak devam edeceğiz. Bazen konuklarımız olacak bazen de beraber oturup sohbet edeceğiz. Eğer Azure ile ilgileniyorsanız ayda bir hafta sonu beraber kahve içmek isterseniz :) bize katılın). Şubat buluşmasının konusunu, tarihini çoktan duyurduk.

Görüşmek üzere.

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Dün Microsoft'un organizasyonunda "Cloud DevCamp" gerçekleştirildi. Ben de gün boyunca üç oturum ile Compute Services, Mobile Services ve Azure Web Sites anlattım.

Malum bu bir community etkinliği değildi :) özellikle belirtiyim istiyorum çünkü kafalar karışabiliyor. Microsoft'un hafta içi sadece kurumsal katılım için organize ettiği bir etkinlikti. Ben de sizlerle twitter ve facebook üzerinden paylaşmıştım öncesinde.

Etkinlik community etkinliğinden öte Microsoft'un bir etkinliği olunca yukarıdaki gibi tweetler de kaçınılmaz oldu :) Neredeee.... community etkinliğinde açık büfe görmek pek mümkün değil tabi :)

Etkinliğe katılan herkese çok teşekkürler.

Görüşmek üzere!

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Geçen hafta sonu Eskişehir Osmangazi Üniversitesi'ndeydim. "Cloud Development 101" adını koyduğum sıfırdan Cloud Nedir gibi konseptleri anlatan ve içerisinde de Microsoft'un Azure ile yaptıklarını gösteren bir sunum yaptım. İtiraf etmek gerekirse günübirlik bir etkinlik için 10 saat yolculuk son birkaç yıldır yapmadığım bir şeydi ve yıllar sonrasında "yılların etkisini" görmemi sağladı. Sözün özü... yaşlanmışım :) Şaka zannederlere sesleniyor... "10 yıl sonra görüşürüz."

Tüm yorgunluğa rağmen :) benim için çok eğlenceli bir etkinlikti. Etkinlikte emeği geçen tüm arkadaşlarla onlarca teşekkürler. Gösterdikleri hassasiyet takdir edilesi seviyelerdeydi. Bu gibi üniversite etkinliklerine katılan öğrenci kardeşlerim :) genelde maalesef farkında olmuyorlar fakat etkinliklere gönüllü olarak katılan benim gibi konuşmacıların emeklerinin yanı sıra etkinliğin organizasyonunda çalışan arkadaşlarının da inanılmaz katkısı var. Emin olun, en basiti bile olsa, bir organizasyonu yapmak ve alnının akı ile işin içinden çıkmak hiç kolay değil.

Bir dahakine görüşmek üzere Eskişehir! ;)

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Bu hafta içi (dün) Konya, Karatay Üniversitesi'ndeydim. Her zamankinden farklı olarak teknik bir oturum yerine ilk defa aylar önce yine Konya'da sunduğum "Boş durma, boşa çalış." adındaki oturumumu sundum. Bu oturum özellikle benim "Uykudan Önce Bir Doz" ile paylaşmaya çalıştıklarımla aynı yönde bir paylaşım diyebiliriz.

Tabi ki dönüşte gençlerle bir etli ekmek yemeyi de unutmadık :) Nitekim eski performansımı gösteremiyorum artık ama olsun :) Benim için süper eğlenceliydi. Umarım katılan herkes için de faydalı olmuştur. Etkinliğin organizasyonunda görev alan tüm arkadaşlara da ayrıca çok teşekkürler. Elinize sağlık.

Görüşmek üzere!

Bu yazıyı normal bir okuma hızı ile ortalama 7 dakikada, göz gezdirerek ortalama 2 dakikada okuyabilirsiniz ;)

Azure ortamında Storage Services kapsamında yarattığımız her bir Storage account ile beraber bize bir blob, bir table, bir de queue servisi sağlandığını biliyoruz. Bugüne kadar bloblarla ilgili birçok konuya değinmiş olsak da daha Table servisi ile ilgili birşey yapmadık :) Şimdi gelin Table Services yapısına bir giriş yapalım.

Bir NOSQL hikayesi....

NoSQL genel olarak çoğu developer'ın ilgi alanı dahilinde olmamış bir konseptti. İtiraf etmek gerekirse konseptin adlandırılmasında da bazı problemler var aslında :) Özünde NoRel de denilebilir belki :) Nedenine gelirsek, Azure'taki Table Services'in belki de normal bir SQL'den en büyük farklılıklarından biri "relation" bulunmaması. Bazılarınız şu anda "nasıl yani?" derken bazılarınızın da "tamam sorun değil ben yazılım katmanına çözerim onu" gibi bir tepki verebileceğini tahmin ediyorum. Aslında burada en tehlikeli tepki ikinci tepki :) çünkü eğer ki NoSQL yapısında gidip kendi normal relational SQL sanal yapımızı oluşturmaya çalışırsak büyük hata yapmış oluruz... Neden mi? Relational yapıya ihtiyacınız varsa zaten Azure SQL Database var. Neden kastıralım ki? :)

Table servis'in nerelerde, nasıl ve hangi nedenle kullanılabileceği konusunu biraz da ileri makaleler bırakacağım. Şimdilik ipucu olarak No-SQL, Big Data and Hadoop vs diye sıralayabilir fikir vermesi için. Sonuç itibari ile table service'in çalışma yapısını, kullanımını, yapabildiklerini veya yapamadıklarını ve arkasındaki nedenleri :) bilmeden / anlamadan çıkıp da Table Services nerde kullanılmalı'nın tanımını yapmak faydasız olacaktır. İşte bu yüzden :) Gelin direk işin içine girelim ve bakalım neler oluyor.

İlk Table Service örneğine doğru...

Tüm Storage servislerinde olduğu gibi Table Services de tamamen REST API'leri ile kullanılabiliyor.

[C#]

public class Urun2
{
    public string Timestamp { get; set; }
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Adi { get; set; }
    public string Aciklama { get; set; }
}

Yukardaki kod malum bizim uygulamamızda kullanacağımız basit bir entity. Bu Entity'nin özellikle ilk üç property'si eminim ki ilginç gelecektir :) İsterseniz hemen o üçünü incelemeye başlayalım.

Timestamp : Bu property'e biz genelde dokunmuyoruz :) Bu property azure table services tarafından concurrency kontrolü için kullanılıyor. Yani biz hiçbir zaman bu arkadaşa bir değer atamıyor veya genelde oradaki değeri alıp birşeyler de yapmıyoruz. O nedenle bu property kesinlikle bulunması gereken fakat bizi pek de ilgilendirmeyen bir property.

PartitionKey : İlk olarak bu property'nin bir String olduğuna dikkat çekiyim :) PartitionKey property'si çok kritik bir property. Table servis içerisinde bir table'da bulunan entity'lerinizin gerektiğinde farklı sunucularda farklı partitionlara dağıtılması noktasında dağıtım işlemi partitionkey'e göre yapılıyor. Yani eğer ki "Arabalar" diye bir table'ınız varsa ve içinde 100 milyon araba varsa.. ve bu table'daki performans düşüşü nedeniyle Storage Services bu table'ı partitionlama kararı alırsa... bu işlemi ancak partitionkey'e göre yapar. Peki bunun tam olarak anlamı nedir?

Örnek bir
partitioning. Örnek bir partitioning.

Yukarıdaki örneği bir inceleyelim isterseniz. Aslında yukarıdaki tek bir tablomuz var. Arabalar tablosu :) PartitionKey olarak arabanın tipi kullanılmış ve arabalara partitionkey değeri olarak Sedan, Coupe gibi değerler verilmiş. Table Services performansın düştüğünü algıladığında hemen PartitionKey'e göre iki partition yaratıp tabloyu iki farklı sunucuya dağıtmış. Buraya kadar manzara süper. Peki şimdi soruyorum :) Ben kırmızı arabaları getir dersem ne olacak? Tabi ki cross-partition yani partittionlar arası / genel bir sorgu çalıştırmış olacağım. İşte can alıcı nokta da zaten burası :) Çünkü olabildiğince cross-partition sorgulardan kaçmam gerek. Ha cross-partition sorgu olamaz mı? tabi ki olabilir fakat bu durumda ben partitionlamadan kazanacağım performans kazanamam.

Bir başka örnek
partitioning. Bir başka örnek partitioning.

Oysa yukarıdaki şekilde partitionKey'lerimde renk bilgileri olsaydı "bana kırmızı arabaları getir" dediğimde çok basit bir şekilde tek bir partition (yani disk / makine) üzerinden veri gelecekti. Sorgum diğer partition'a gitmeyecekti bile. Böylece tüm partitioning konseptinden faydalanmış olacaktır. Özünde Table Service'in datamı partitionlaması bir işe yaramış olacaktır.

Şimdi eminim ki aklınıza hemen ya ben hem renge hem de tipe göre sorgu yazacaksam sorusu geliyordu :) Birincisi tabi ki bunu yapabilirsiniz ve partitionging'den faydalanmaktan vaz geçmiş olursunuz ama ikincisi ise :) belki de çok karışık sorgularınız varsa SQL'i düşünmelisiniz :) Burada "düşünmelisiniz" derken "vazgeçin buralardan" demiyorum aman dikkat. SQL ile Table Service arasındaki kararı vermek çoğu zaman birçok değişkeni olan ve zor bir karar. Daha da önemlisi %99 ihtimal ile iki servisi beraber kullanacaksınız :) bunun da kendince nedenleri olacak. Umarım bu yazıda ve ilerikilerde vereceğim bilgiler doğru kararları alabilmeniz adına işine yarar fakat buradan "sorgunuz karşıkça SQL'e gidin" gibi genel mesajlar alıp çıkmamanız çok önemli. Benim yapmaya çalışacağım şey size bilgileri vermek, ihtimalleri göstermek, düşünmeniz gerekenlere dikkat çekmek, gerisindeki kararın emin olun birçok değişkeni var.

Sanırım PartitionKey'in anlamını ve değerini artık anladık. PartitionKey property'si string olduğu için rahatlıkla istediğiniz değerleri verebilirsiniz. Tabi ufak bir uyarı daha... PartitionKey vermiş olmanız datanızın illa partitionlandığı anlamına da gelmiyor :) Performans açısından ihtiyaç olunduğunu düşünürse Storage Services'deki "Smart NLB" partitioning'i kendisi tetikleyecektir (sizin tetikleme şansınız yok).

RowKey: Bir property'miz daha var :) O da RowKey. Tahmin edebileceğiniz üzere RowKey artık bir satırı doğrudan tanımlayan key anlamına geliyor. Tam olarak Primary Key diyemem çünkü RowKey ile PartitionKey beraber PK rolü oynuyorlar. Yani bir table'da aynı RowKey'den fakat farklı PartitionKey'de bulunan iki entity olabilir. Tabi karar tamamen sizin isterseniz RowKey'i tek de tutabilirsiniz tek başına.

RowKey ile PartitionKey'in beraber ne şekillerde tanımlandıkları çok önemli. Table Service'de en yüksek performansı alabileceğiniz sorguların kesinlikle beraberlerinde RowKey ve PartitionKey getirmeleri gerekiyor. Böylece sorguyu hem bir Partition ile sınırlamış oluryorsunuz, sonra da Partition içerisinde RowKey üzerinden arama yaparak sonucu alabiliyorsunuz.

[C#]

public class Urun : TableEntity
{
    public string Adi { get; set; }
    public string Aciklama { get; set; }
}

Her sınıf tanımını yaparken bu üç property'yi de tek tek tanımlamak istemiyorsanız :) Basit bir şekilde sınıflarınızı  StorageClient ile beraber gelen TableEntity'den türeterek ilerleyebilirsiniz. Böylece tüm bu sınıflar direk üç property'ye de sahip olacaktır.

Herşeyin öncesinde TableService'a bağlanıp bir table yaratmamız gerek.

[C#]

CloudStorageAccount storageAccount =  CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
try
{
    if (await table.CreateIfNotExistsAsync())
    {
        Console.WriteLine("İşlem tamam");
    }
    else
    {
        Console.WriteLine("Bu isimde bir tablo var.");
    }
}

Blob'larla uğraştıysanız buradaki kod da epey basit gelecektir :) Bu sefer bir blobClient almak yerine CloudTableClient alıyoruz. Üzerinden de CreateTableIfNotExists gibi süper bir metod kullanıp :) tablomuzu yaratmış oluyoruz.

Gelin şimdi hızlı bir şekilde basit CRUD (Create, Read, Update, Delete) işlemlerine göz atalım.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
Urun2 urun = new Urun2()
{
    PartitionKey = "Musteri1",
    RowKey = (new Random().Next(1, 100)).ToString(),
    Adi = "Deneme",
    Aciklama = "Açıklama"
};
TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(urun);
TableResult result = await table.ExecuteAsync(insertOrMergeOperation);

Ben ürünleri müşterilere göre partitionlama kararı aldım. Özellikle birden çok müşterinin kendi tenant'larını kullandıkları bir yapıda bu çok anlamlı olacaktır. Sonuç itibari ile bir müşterinin diğer müşterinin verisine ulaşma gibi bir ihtimali zaten sıfır. RowKey'i ise şimdilik Random bir sayı olarak verdim ama unutmayın RowKey ve PartitionKey Table Service'deki ana iki Index'imiz. O nedenle sorgularınızı da düşünerek Index'lemek istediğiniz bilgileri bu property'lere atamak süper mantıklı olacaktır.

Kodun geri kalanının pek Azure ile bir alakası yok :)

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
TableOperation retrieveOperation = TableOperation.Retrieve<Urun2>("Musteri1", "1");
TableResult result = await table.ExecuteAsync(retrieveOperation);
Urun2 urun = result.Result as Urun2;
if (urun != null)
{
    TableOperation deleteOperation = TableOperation.Delete(urun);
    await table.ExecuteAsync(deleteOperation);
}

Kayıt silme konusunda yukarıdaki kodu kullanabilirsiniz.

Entity Group Transactions

[C#]

TableBatchOperation batchOperation = new TableBatchOperation();

for (int i = 0; i < 100; i++)
{
    batchOperation.InsertOrMerge(new Urun2()
       {
           PartitionKey = "Musteri" + i.ToString(),
           RowKey = (new Random().Next(1, 100)).ToString(),
           Adi = "Deneme",
           Aciklama = "Açıklama"
       });
}
IList<TableResult> results = await table.ExecuteBatchAsync(batchOperation);

foreach (var res in results)
{
    var eklenenUrun = res.Result as Urun2;
}

Süper önemli noktalardan biri de "Entity Group Transaction"'lar. Elinizde birden çok işlem var ve bunları toplu olarak gönderebilmek istiyorsunuz. İşte o zaman EGT'leri kullanmanız şart. Bunun için normal TableOperation'lar yerine TableBatchOperation yaratıyorsunuz. Sonrasında kullanım batch olmayan operasyonlarla aynı şekilde. Tek farkı "ExecuteBatch" ile tüm işlemleri aynı anda REST API üzerinden gönderebilecek olmanız. Fakat burada da birkaç dikkat edilmesi gereken nokta var. Birincisi her EGT en fazla 100 işlem taşıyabilir. İkincisi ise bir EGT paketi en fazla 4MB büyüklüğünde olabilir. Bunları göz önüne alarak uygulamanızı tasarlamanız kritik. Tam "sınırlardan" bahsederken bir entity'nin 1MB'dan büyük olamayacağını (binary datalar bloblara gitsin lütfen :)) ve entity başına 255'dan fazla property olamayacağını da belirtiyim :)

EGT'lerin çalışması için ayrıca bir EGT içerisinde işlemlerin tek bir table ve tek bir partition'ı hedefliyor olması şart. Cross-Partition-Transaction yok. Tam da bu noktada eminim ki bazılarınıza "nasıl ya iki table'da aynı transaction içinde işlem yapamıyor muyuz?" diyecektir :) cevabım : "evet yapamıyorsunuz". Ama yazının başından beridir bahsetmediğim ilginç bir nokta var aslında :) Dikkat ettiyseniz Table yaratırken Field tanımlamaları yapmadık. Table'ların şemaları statik değil! Yani bir table içerisinde aslında farklı şemalarda nesneler bulunabiliyor. Ama ben şimdilik bu konuyu bir sonraki yazımıza bırakıyorum ;)

Görüşmek üzere.

Bu yazıyı normal bir okuma hızı ile ortalama 5 dakikada, göz gezdirerek ortalama 2 dakikada okuyabilirsiniz ;)

Bloblarla ilgili birçok detayı daha önceki yazılarda inceledik. Hatta PageBlob ile BlockBlob arasındaki farklılığa da değinmiştik. Şu ana kadar bildiklerimizin üzerinden geçmek gerekirse PageBlob'ların 512 byte'lık paketler (max4MB'lık paket olabilir) şekilde veri saklayabildiğini ve daha ağırlıklı olarak random read/write için uygun olduklarını BlockBlob'ların ise daha büyük paketlerle çalışabildiklerini (4MB gibi MAX 64MB Paket olabilir, Storage Client'taki maksimum destek 32MB'a kadar.) ve ufaktan da bir transaction yapılarının olduğunu biliyoruz. PageBlob'lar 1TB'a kadar veri saklayabilirken BlockBlob'lar 200GB ile sınırlı. Gelin şimdi biraz daha detaylara girerek işlemlerin daha alt seviyelerde nasıl yapılabildiğince göz atalım.

Bir BlockBlob'ın kullanımı

Normal şartlarda StorageClient'ın yüksek seviyeli diyebileceğimiz API'lerini kullandığınızda PageBlob veya BlockBlob gibi ayrımlarla uğraşmazsınız ama eğer isterseniz birazdan yapacağımız gibi daha alt seviyeli API'lere doğru ilerleyerek tüm kontrolü ele almanız da mümkün.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki giriş kodumuz ile kendimize sıfırdan bir container yaratalım. Container'ımıza blob seviyesinde public access de verelim ki herkes ulaşabilsin. Şimdi ise yavaş yavaş bir BlockBlob yaratarak kullanımına bakalım.

[C#]

CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");

Blockblobları yaratırken CloudBlockBlob nesnesini kullanıyoruz. Genel mantık tek tek bloklar yüklemekten oluşuyor. Elinizdeki datayı istediğiniz boyutlarda bloklara bölerek bu blobları ayrı ayrı sunucuya gönderiyorsunuz. Her gönderdiğiniz bloğun bir de tabi ki blockID'si oluyor. İsterseniz bu blokları asenkron olarak gönderebilirsiniz, sırasız gönderebilirsiniz. Yani özetle kafanıza göre takılabilirsiniz :)

[C#]

List<string> blockIds = new List<string>();
for (int i = 0; i < 10; i++)
{
    var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
    blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
    blockIds.Add(newId);
}

Yukarıdaki örnek kodda da görebileceğiniz üzere her bir Block'u PutBlock ile sunucuya gönderiyoruz. PutBlock'un toplam üç parametresi var, bunlardan ilki söz konusu bloğa ait ID bilgisi. ID'nin kesinlikle Base64 olması gerekiyor. İkinci parametre datamaızın ta kendisi. Ben deneme amaçlı bir çalışma için basit bir metin gönderiyorum ama siz elinizdeki büyük dosyaları da bloklara bölüp ayrı ayrı gönderebilirsiniz. Hatta multithread takılıp aynı anda birden çok blok bile gönderebilirsiniz. PutBlock'un son parametresi normalde verinin bir MD5 Hashi oluyor. Ben tembellikten eklemedim :)

Tüm bu işlemler sürerken BlockID'lerini bir listede tutuyoruz. Bunun nedeni ise aslında PutBlock dediğimizde verinin doğrudan Blob'a yansımıyor olması. Tüm blokları gönderdikten sonra gönderdiğiniz blokların ID'lerini ayrı bir liste olarak gönderip tüm block'ların commit olmasını sağlıyorsunuz.

[C#]

blob.PutBlockList(blockIds);

Böylece upload işlemi tamamlanmış oluyor. İşte bir BlockBlob'un çalışma yapısı bu şekilde. Aşağıdaki kodu bir bütün olarak çalıştırıp sonuca bir göz atın.

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    for (int i = 0; i < 10; i++)
    {
        var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
        blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
        blockIds.Add(newId);
    }
    blob.PutBlockList(blockIds);
Response.Write(blob.Uri.ToString()); }

Ekrana yazdırılan adrese tarayıcıdan gittiğinizde doğal olarak arka arkaya yazılmış 10 tane TEST kelimesi olan bir metin dosyası ile karşılacaksınız.

Eğer isterseniz bir BlockBlob'un istediğiniz bir blokunu değiştirebilir veya üzerine yeni bloklar da ekleyebilirsiniz. Sonrasında commit ettiğiniz sürece dosyada değişikler anında gözükecektir. İşin güzel tarafı SnapShot almışsanız ve sadece tek bir block değiştirmişseniz sadece değiştirdiğiniz block'un boyutu içi para ödüyorsunuz ;)

[C#]

blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                      .Select(b => b.Name));
blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
blob.PutBlockList(blockIds);

Kodumuzun ilk satırında önemli bir detay var. DownloadBlockList(BlockListingFilter.Committed) dediğinizde commit edilmiş tüm blockların ID'lerini alabiliyorsunuz. Aynı şekilde UnCommitted bloklara da ulaşabilir ve commit edilmeden yine üzerlerinde değişiklik yapabilirsiniz. Bir Storage Account'ta toplam 100.000 uncommitted block olabilir ve toplam veri miktarı da 400GB'ı geçemez. Şimdilik örnek olması amacı ile ben gelen listeden ikinci block'u seçip metni değiştiriyorum. Sonunda ise tekrar blockID'leri gönderiyorum ki dosya son haline toparlansın.

Kodun son hali şu şekilde;

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                          .Select(b => b.Name));
    blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
    blob.PutBlockList(blockIds);
    Response.Write(blob.Uri.ToString());
}

Unutmadan, eğer bir bloğu bir hafta boyunca commit etmezseniz söz konusu block otomatik olarak silinecektir. Aynı şekilde upload edilmiş bir Block'un ID'si eğer bir PutBlockList listesinde bulunmazsa hemen PutBlockList komutu sonrasında listede bulunmayan ve blob'a ait diğer havada kalan blocklar silinir.

PageBlob'lara bakış...

PageBlob'lar daha nokta atışı çalışmaya yönelik. Ayrıca önce veriyi veriyim sonra beraber commit ederim gibi bir durum da yok. Yapılan her değişiklik anında sonuca yansıyor. İşin kötü tarafı ise bloblara 512 byte'ın katları şeklinde veri sakalamak zorunda olmamız :) Yani elinizde stream varsa ona göre ilerlemeniz gerekecek.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki gibi klasik işlemlerimizi yapalım ve PageBlob'umuzu yaratmaya hazırlanmış olalım.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt")

Yukarıdaki kodu sanırım tahmin etmişsinizdir. Güzel bir PageBlob yaratarak yolumuza devam ediyoruz. Bundan sonrasında örnek olarak ben diskten bir TXT dosyası okuyup onu göndereceğimiz PageBlobumuza.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    //BURAYA BİRŞEYLER GELECEK       
}
finally
{
    fileStream.Close();
}

İlk olarak gelin yukarıdaki koda hızlıca bir bakalım. PageBlob'umuzu yaratmışız. Basit bir şekilde diskten bir dosyayı FileStream ile açmışız. Sonra da bazı değişkenlerimizi tanımlamışız. Hazırlık belli ki bir While ile stream'i okumak için yapılmış :) Önemli noktalardan biri buffer'ımızın 512 byte olması. Sürekli 512 byte'lık paketler göndereceğiz PageBlob'a. PageBlob'a gönderebileceğiniz paketler 512 byte ile 4MB arası değişebilir ama kesinlikle 512'nin katı olmak zorundalar.

[C#]

var EnYakin512Kati = ((length + 511) / 512) * 512;
pageBlob.Create(EnYakin512Kati);

while ((count = fileStream.Read(buffer, 0, 512)) > 0)
{
    pageBlob.WritePages(new MemoryStream(buffer), sum);
    buffer = new byte[512];
    sum += count; 
}

Bir üstteki kodun orta yerine yukarıdakini alıyoruz :) İlk olarak elimizdeki dosyanın stream uzunluğuna en yakın 512'nin katını bularak o boyuta denk gelecek şekilde pageBlob'un Create metodu ile Blob'u gerçekten yaratıyoruz :) Sonra bir while ile buffer'ımızı doldurup doldurup WritePages ile de bloba aktarıyoruz.

Kodun tamamını aşağıda bulabilirsiniz;

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    var EnYakin512Kati = ((length + 511) / 512) * 512;
    pageBlob.Create(EnYakin512Kati);
    while ((count = fileStream.Read(buffer, 0, 512)) > 0)
    {
        pageBlob.WritePages(new MemoryStream(buffer), sum);
        buffer = new byte[512];
        sum += count; 
    }       
}
finally
{
    fileStream.Close();
}
Response.Redirect(pageBlob.Uri.ToString());

İtiraf etmem lazım PageBlob'larla çalışma hiç kolay değil. En azından ben BlockBlob'ları daha çok seviyorum :) PageBlob'lar ise tek tek 512bytelık aralıklara kadar müdahale sağlıyor. PageBlob'lardan snapshot aldığınızda sadece değişen 512 byte'lar için para ödeyeceğini de hatırlamakta fayda var ;)

Kendinize çok iyi bakın.

Bu yazıyı normal bir okuma hızı ile ortalama 3 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Azure SDK'yi yüklediğinizde makinenize iki farklı emülatör yükleniyor. Bunlardan biri DevFabric de denilen Compute Emulator, yani uygulamanıza Azure ortamını yaşatan arkadaş, diğeri ise Storage Emulator, Azure Storage servislerini lokalde test etmemizi sağlayan araç. Her iki araca da Visual Studio ile ilk projenizi F5'e basarak çalıştırdıktan sonra "notification bar" dan ulaşabilirsiniz.

Azure Emülatörleri İş
Başında Azure Emülatörleri İş Başında

Storage Emulator

Storage Emulator localde sadece bir storage account sağlayabildiği için projeler arası çakışmalara neden olabilir. Özellikle aynı isimler container'lar kullanıyorsanız farklı datalar farklı projelerde kendini göstermeye başlayabilir :) Bu durumu engellemek adına Storage Emülatör'ün arayüzünü açıp tüm storage'ı resetleyebilirsiniz.

Storage Emulator'ü gidiyor... yerine command line geliyor... Storage Emulator'ü gidiyor... yerine command line geliyor...

"Herkes gider mersine biz gideriz tersine." sözünü hatırlatan kıvamda :) Azure Emulatorü'nün UI sürümünü Microsoft artık geliştirmeyeceğini söylüyor. Scripting desteği verebilmek açısından command line araçlarının gelmesi önemli fakat bu durum "UI'dan vaz geçme"yi de gerektirmez bence. Neyse, kişisel yorumlarımı bir kenara koyalım :) "Yeni command line" arayüzünü (şaka gibi) açtıktan sonra karşınıza kullanabileceğiniz komutların bir listesi geliyor.

Storage Emülatörü komutları karşımızda. Storage Emülatörü komutları karşımızda.

Storage Emulator içerisinde toplam üç storage servisi (Blob, Queue, Table) için gerekli endpointler yaratılmış durumda.

Servislerin local endpointleri. Servislerin local endpointleri.

Bu Endpoint'lere ister doğrudan REST Call'larla gidin ister StorageClient ile gidin bire bir Azure ortamındaki Storage servisleri ile aynı tepkileri vermekle yükümlüler ve itiraf etmek gerekirse şu ana kadar herhangi bir yanlışlarını görmedim :)

Arkada neler oluyor...

Esas işin ilginç tarafı şimdi başlayacak :) Storage Emulator SQL'e bağımlı. Bunun nedeni tüm datayı aslında SQL'de tutuyor olması. Burada çok önemli bir detay var; Storage Emulator'ın datayı SQL'de tutuyor olması Azure ortamında da aynı şeyin yapıldığı anlamına gelmiyor. Hatta açıklamalara göre Azure'daki Storage servislerinin arkasında SQL yok (Zaten mantıksız olurdu) ama arkada ne var diye sorarsanız :) bilmiyorum :)

Lokal yüklemede emülatörün arkasında SQL olması işimizi özellikle debuggine senaryolarında kolaylaştırabiliyor. Ne kadar ki üretkenliğinizi yükseltmek adına Storage Explorer tadında araçlarla çalışmak zorunda kalacak olsanız da :) işin dibine inmek isterseniz aslında basit bir şekilde SQL Management Studio ile bağlanıp emülatörün kullandığı veritabanını da inceleyebilirsiniz.

Storage Emülatörü arkasında SQL'e bir
bakış. Storage Emülatörü arkasında SQL'e bir bakış.

Yukarıdaki ekran görüntüsünde basit bir şekilde BlobContainer listesini görebiliyorsunuz. Bunun gibi tüm Storage verisini local emülatör SQL'de tutuyor. Acaba yolladıklarım kaydoluyor mu gibi testleri buralarda yapmak mümkün. Tabi yine tekrar etmem gerek; çok alt seviye bir testing aracı olarak düşünmek lazım bu yöntemi.

Azure Emulator

Gelelim bir de işin Azure Emulator tarafına. Azure Emulator bilgisayarınızdaki IIS Express'i kullanarak Web Role emülasyonu yapıyor. Instance'larınızı ve konsollarını tek tek emülatör arayüzünden görebiliyorsunuz. Bir anlamda local bir FC rolü oynamaya çalışıyor.

Azure Emulator
UI Azure Emulator UI

Yukarıdaki ekran görüntüsünde gördüğünüz deployment'ta sadece bir instance web role var. Buradaki rollerin her birine bağlanıp durumlarını takip edebiliyoruz. Ayrıca buradan instance'ları suspend edebilir farklı durumlara verdikleri tepkileri de ölçebilirsiniz.

Local emülatörün
parçaları... Local emülatörün parçaları...

Yukarıda kabaca local Azure Emülatörünün mimari olarak nasıl da gerçek Azure ortamına benzetilmeye çalışdığını görebilirsiniz. IISConfigurator'dan tutun WaIISHost'larımıza kadar herşey neredeyse aynı. Her Instance için bir HostBootStrapper var ve instance tipine göre de WaWorkerHost veya WaIISHost kullanılarak uygulama ayağa kaldırılıyor. Böylece Azure ortamına çok benzeyen bir debugging deneyimi yaşayabiliyoruz.

Hepinize kolay gelsin.

Bu yazıyı normal bir okuma hızı ile ortalama 3 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Blobların en sevdiğim özelliklerinden biri blob başına snapshot alabiliyor olmamız :) Sınır yok, istediğiniz kadar snapshot alabilirsiniz :) (yeter ki parasını ödeyin) Şaka bir yana SnapShot almanın kolaylığı ve "yedekleme" amaçlı olarak süper faydalı olması geliştireceğiniz uygulamalarda eminim ki ciddi katma değer sağlayacaktır.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");
var snapshotBlob = blob.CreateSnapshot();

Response.Write(snapshotBlob.SnapshotQualifiedUri);

Yukarıdaki kod içerisinde basit bir blob bulduktan sonra hemen üzerinden CreateSnapshot metodunu çağırıyoruz. Metodu çağırdığımız anda aslında artık blobun snapshot'ı yani bir kopyası tarihin tozlu raflarında yerini almış oluyor :) Blobun snapshot'ına direk ulaşmak isterseniz ancak SnapshotTime üzerinden snapshot'ın alındığı tarihi kullanarak ilerleyebiliyorsunuz. Local testte aldığımız URL aşağıdaki gibi oluyor.

http://127.0.0.1:10000/devstoreaccount1/dosyalar/istanbulazurelogo.png?snapshot=2014-12-2T11:33:32.5370000Z

Güzel, bir snapshot yarattık ve belli ki ona ulaşabiliyoruz. Fakat ya bir blob'un birden çok snapshot'u alınmışsa? Onlara nasıl ulaşırız? İşte bu noktada klasik blob listeleme senaryosunda geri dönüyoruz. Normal blobları listelermiş gibi ilerlerken ek olarak SnapShot'ların da listelenmesi için bir parametre göndermemiz yeterli oluyor.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");

var snapshots = container.ListBlobs(blob.Name, true, BlobListingDetails.Snapshots);
foreach (CloudBlockBlob snap in snapshots)
{
    if(snap.IsSnapshot )
    {
        Response.Write(snap.SnapshotQualifiedUri);
    }                
}

Yukarıdaki kod içerisinde ListBlobs'a verdiğimiz BlobListingDetails işimizi görecektir. Ayrıca söz konusu metoda verdiğimiz ilk parametrede de eldeki blobun adını vererek sadece o bloba ait kayıtların dönmesini sağlamış oluyoruz. Ama burada ufak bir uyarıda da bulunmam gerek. ListBlobs'un ilk parametresi bir Prefix aslında. Yani one2one match olarak ismine göre Blob aramaz. O nedenle eğer prefix ile tutturabileceğinizden emin değilseniz içeride ayrıca BlobName kontrol yapmakta fayda var. Elimize gelen listede dolaşırken orijinal blobu dışarıdan bırakmak için IsSnapShot kontrolü yapıyor, sonrasında da SnapshotQualifiedUri'yi alıyoruz.

Doğal olarak bu noktadan sonra artık SnapShot'lar da birer blob oldukları için SnapShot silme işlemini de normal bir blob siler gibi yapabiliyoruz ama ufak bir sorun var :) O da normal şartlarda SnapShot'ı olan bir ana blobun SnapShot'ların hepsi silinmeden silinemiyor olması. Bunun için SnapShot listesi alıp for'la gezip tek tek SnapShot silmeye gerek yok ;)

[C#]

blob.Delete(DeleteSnapshotsOption.IncludeSnapshots);

Yukarıdaki gibi silme işlemine de DeleteSnapshotsOption verebilirsiniz. Burada IncludeSnapshots veya DeleteSnapshotsOnly diyebiliyorsunuz.

[C#]

birBlob.StartCopyFromBlob(snapshotBlob);

Eğer ki bir SnapShot Blob'unu alıp bir başka bloba kopyalamak isterseniz yukarıdaki kod yardımcı olabilir. Unutmayın ana blobları kopyalarken SnapShot'lar taşınmaz ve kopyalanmaz. Snapshot her zaman ilk yaratılığı bloba bağlıdır. StartCopyFromBlob metodunu bir snapshotı restore etmek için de kullanmanız gerekecek. SnapShot'tan orijinal blobun üzerine kopyalama yapmak yeterli olacaktır.

Birkaç detay...

SnapShotların çalışma şeklini bilmek ne için ne kadar para ödeyeceğinizi bilmek adına da önemli. Bir SnapShot aldığınızda eğer SnapShot yapıldıktan sonra Blob'da değişiklik olmadıysa ek bir ücret ödemiyorsunuz. Özetle aslında sadece değişen Block veya Page için SnapShot'larda para ödersiniz. Fakat ufak bir uyarı :) eğer SnapShot'lar sizin için çok önemliyse ve çok kullanacaksanız StorageClient ile beraber gelen UploadFile, UploadText, UploadStream veya UploadByteArray gibi metodları kullanmayın. Bu metodlar upload esnasından tamamen sıfırdan upload yaptıkları için tüm page'ler veya block'lar değişiyor ve bu da sıfırdan bir snapshot almış olmak anlamına geliyor. Nitekim bu şekilde de ilerlenebilir ama bilerek ilerlemekte fayda var :) Bahsettiğimiz metodların kullanımları kolay ama SnapShot'ta da ufak bir dezavantajları var. Eğer böyle kullanacaksanız tavsiyem SnapShot'larınızı doğru yönetin ki gereksiz yere binlerce SnapShot durmasın kenarlarda.

SnapShot'ları çok yoğun kullanacaksanız ideal yöntem bir alt seviyeye inip kaynaklarınızı doğrudan Page veya Block seviyesinde yönetmek. StorageClient içerisinde PutBlock ve PutBlockList gibi işinize yarayabilecek metodlar mevcut ;)

Kolay gelsin.

Twitter
RSS
Youtube
RSS Blog Search
Arşiv'de tüm yazıların listesi var. Yine de blog'da arama yapmak istersen tıkla!
Instagram Instagram