Olá pessoal,

Hoje gostaria de comentar um pouco sobre Tables do Windows Azure. O primeiro ponto a ser levado em consideração é que, apesar do nome, não é uma tabela relacional e sim uma estrutura de dados do tipo tabela. Consequentemente, tanto a leitura e quanto a gravação de dados são diferentes de como isso tradicionalmente é feito com um banco de dados relacional.

Uma tabela pode armazenar várias entidades, que por sua vez podem ter várias propriedades. Não existe limite para a quantidade de entidades armazenadas em cada tabela, o limite é o que a conta de storage suportar, por padrão até 100TB. Já cada entidade pode ter até 1MB divididos em até 255 propriedades.

Quanto as propriedades, elas podem ser dos tipos String, binary, bool, DateTime, GUID, int, int64 e double. Além disso, 3 propriedades são obrigatórias:

  • PartitionKey: do tipo String, é utilizado para agrupar o armazenamento de entidades com o mesmo valor de partição. Em relação à desempenho, o Windows Azure faz um balanceamento de carga entre as partições suportando até 500 requisições por segundo em uma única partição. Maiores detalhes no post Windows Azure Storage Abstractions and their Scalability Targets;
  • RowKey: do tipo String, é o identificador único da entidade dentro da uma partição. Logo, a combinação da PartitionKey com a RowKey é utilizado como o identificador único da entidade na tabela como um todo;
  • Timestamp: do tipo DateTime, utilizado para gerenciamento de concorrência na atualização das informações da entidade.

Do ponto de vista de índices, cada tabela é indexada pela PartitionKey e pela RowKey, então sempre que possível utilize esses campos quando realizar consultas na tabela. Se as propriedades não forem utilizadas, a consulta precisará ser realizada em todas as partições e até em todas as entidades da tabela, que dependendo da quantidade de dados armazenados problemas de desempenho podem ocorrer.

Para definir uma entidade, você pode criar uma classe com propriedades, incluindo as 3 obrigatórias, ou herdar de Microsoft.WindowsAzure.StorageClient.TableServiceEntity e definir somente as propriedades necessárias para sua regra de negócio. A classe TableServiceEntity já traz a implementação para a PartitionKey, RowKey e Timestamp. Veja exemplo abaixo:

using Microsoft.WindowsAzure.StorageClient;
namespace ExemploTable
{
    class Usuario : TableServiceEntity
    {
        public string Nome { get; set; }
        public string EMail { get; set; }
        public int Idade { get; set; }
        public bool AceitaReceberEMails { get; set; }
    }
}

Em seguida é necessário criar um contexto para comunicação com a tabela. Isso é feito herdando da tabela TableServiceContext, que por sua vez herda do DataServiceContext do WCF Data Services. Olha só, reaproveitamento de conhecimento existente :)

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace ExemploTable
{
    class ExemploTableContext : TableServiceContext
    {
        public ExemploTableContext(string baseAddress, StorageCredentials credentials) :
            base(baseAddress, credentials)
        {
        }
    }
}

Em seguida é necessário adicionar um IQueryable para a entidade utilizada:

class ExemploTableContext : TableServiceContext
{
        private const string TabelaUsuarios = "Usuarios";

        public IQueryable<Usuario> Usuarios
        {
            get
            {
                return this.CreateQuery<Usuario>(TabelaUsuarios);
            }
        }

}

Para adicionar, atualizar e remover entidades, basta utilizar os métodos AddObject, UpdateObject e DeleteObject da classe de contexto, em conjunto com o método SaveChanges para persistir as informações. Segue abaixo exemplo dessa chamada encapsulada:

class ExemploTableContext : TableServiceContext
{
    public void AddUsuario(Usuario usuario)
    {
        this.AddObject(TabelaUsuarios, usuario);
        this.SaveChanges();
    }

    public void UpdateUsuario(Usuario usuario)
    {
        this.UpdateObject(usuario);
        this.SaveChanges();
    }

    public void DeleteUsuario(Usuario usuario)
    {
        this.DeleteObject(usuario);
        this.SaveChanges();

    }
}

Abaixo você pode ver todo o código da classe de contexto:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
namespace ExemploTable
{
    class ExemploTableContext : TableServiceContext
    {
        private const string TabelaUsuarios = "Usuarios";

        public ExemploTableContext(string baseAddress, StorageCredentials credentials) :
            base(baseAddress, credentials)
        {
        }

        public IQueryable<Usuario> Usuarios
        {
            get
            {
                return this.CreateQuery<Usuario>(TabelaUsuarios);
            }
        }

        public void AddUsuario(Usuario usuario)
        {
            this.AddObject(TabelaUsuarios, usuario);
            this.SaveChanges();
        }

        public void UpdateUsuario(Usuario usuario)
        {
            this.UpdateObject(usuario);
            this.SaveChanges();
        }

        public void DeleteUsuario(Usuario usuario)
        {
            this.DeleteObject(usuario);
            this.SaveChanges();

        }
    }
}

Para utilizar as tabelas, primeiro é necessário cria-las de acordo com a estrutura definida no contexto, para isso utilizamos a classe CloudTableClient, conforme abaixo:

var account = CloudStorageAccount.DevelopmentStorageAccount;

CloudTableClient.CreateTablesFromModel(
    typeof(ExemploTableContext),
    account.TableEndpoint.AbsoluteUri,
    account.Credentials);

 

Depois podemos instancar uma entidade e nosso contexto para realizar operações na tabela:

Usuario usuario = new Usuario()
{
    PartitionKey = DateTime.UtcNow.ToString("yyyyMMdd"),
    RowKey = Guid.NewGuid().ToString(),
    Nome = "Rafael Godinho",
    EMail = "rafaelgodinho@contoso.com",
    Idade = 30,
    AceitaReceberEMails = true
};

ExemploTableContext context = new ExemploTableContext(account.TableEndpoint.ToString(),
    account.Credentials);

context.AddUsuario(usuario);

usuario.AceitaReceberEMails = false;
context.UpdateUsuario(usuario);

Um ponto interessante é que a pesquisa de informações na tabela pode ser feita via Linq, veja em seguida:

//Retorna os usuarios cadastrados no dia
var usuarios = from u in context.Usuarios
               where u.PartitionKey == DateTime.UtcNow.ToString("yyyyMMdd")
               select u;

foreach (var item in usuarios)
{
    Console.WriteLine("Nome: {0}\nEmail: {1}\nAceita Receber Emails: {2}\n",
        item.Nome,
        item.EMail,
        item.AceitaReceberEMails);
}

Lembre-se de sempre que possível adicionar a PartitionKey nas suas consultas. À seguir você pode ver o exemplo completo.

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExemploTable
{
    class Program
    {
        static void Main(string[] args)
        {
            var account = CloudStorageAccount.DevelopmentStorageAccount;

            CloudTableClient.CreateTablesFromModel(
                typeof(ExemploTableContext),
                account.TableEndpoint.AbsoluteUri,
                account.Credentials);

            Usuario usuario = new Usuario()
            {
                PartitionKey = DateTime.UtcNow.ToString("yyyyMMdd"),
                RowKey = Guid.NewGuid().ToString(),
                Nome = "Rafael Godinho",
                EMail = "rafaelgodinho@contoso.com",
                Idade = 30,
                AceitaReceberEMails = true
            };

            ExemploTableContext context = new ExemploTableContext(account.TableEndpoint.ToString(),
                account.Credentials);

            context.AddUsuario(usuario);

            usuario.AceitaReceberEMails = false;
            context.UpdateUsuario(usuario);

            //Retorna os usuarios cadastrados no dia
            var usuarios = from u in context.Usuarios
                           where u.PartitionKey == DateTime.UtcNow.ToString("yyyyMMdd")
                           select u;

            foreach (var item in usuarios)
            {
                Console.WriteLine("Nome: {0}\nEmail: {1}\nAceita Receber Emails: {2}\n",
                    item.Nome,
                    item.EMail,
                    item.AceitaReceberEMails);
            }

            context.DeleteUsuario(usuario);
        }
    }
}

O código para esse post pode ser encontrado aqui. Estou utilizando o Visual Studio 2012 para meus códigos.

RG