sábado, 24 de outubro de 2009

Mudar PartitionKey e RowKey? "NÃO", diz o Azure SDK

Table Storage do Azure. Criamos uma classe "Usuario", pra ser armazenada "lá" (na(s) nuvem(ns)). Baseando o código num dos exemplos que encontramos por aí, tudo rodou ok.

"Beleza", digo eu. "Vamos codificar na vera agora". Bem, estávamos usando o identificador da empresa como Partition Key, e o e-mail do usuário como Row Key. Só que meu código estava meio torto. Por exemplo, pra acessar o e-mail do usuário a gente escrevia

string email = Usuario.RowKey; // Usuario.RowKey???

Pra acessar a empresa:

int codigoEmpresa = Usuario.PartitionKey; // Hã?

Obviamente, quero usar Usuario.EMail e Usuario.CodigoEmpresa. Aí começou meu quebra-pau com o Azure.

Quando a gente cria uma classe que representa as entidades (= linhas) de uma tabela (= tabela) no Table Storage do Azure, esta classe tem que ser marcada com o atributo DataServiceKeys. No exemplo de código que pegamos, a classe estava marcada com

[DataServiceKeys("PartitionKey", "RowKey"]
public class Usuario
{
int PartitionKey { get; set; }
string RowKey { get; set; }
//... Resto do código
}

"Simples", penso eu. "É só especificar o nome das propriedades que serão as Partition e Row Keys naquele atributo". Rá. Ledo engano. (Não sei o que é "ledo", mas sempre que você está totalmente enganado, o engano é um "ledo engano"). Mudei o nome das propriedades PartitionKey e RowKey para CodigoEmpresa e EMail. Renomeei as propriedades no código. Recompilei. Tudo ok.

[DataServiceKeys("CodigoEmpresa ", "EMail "]
public class Usuario{
int CodigoEmpresa { get; set; }
string EMail { get; set; }
//... Resto do código
}

Aí fui rodar o DevTableGen para gerar as tabelas no Development Storage. A tabela de usuários não foi gerada.

Peculiaridade nº 1: Se o atributo DataServiceKeys não recebe literalmente as strings "PartitionKey" e "RowKey" como parâmetro, o DevTableGen não gera a tabela.

"OME", digo eu. "Oh My Egg". Vamos então manter as propriedades PartitionKey e RowKey na minha classe, mas vamos pelo menos criar propriedades que fazem mais sentido:
[DataServiceKeys("PartitionKey", "RowKey"]
public class Usuario{
int PartitionKey { get; set; } // aqui vai ficar o código da empresa
string RowKey { get; set; } // aqui vai ficar o e-mail do usuário
// Por aqui eu leio o código da empresa
int CodigoEmpresa {
get { return PartitionKey; }
}
// Por aqui eu leio o e-mail do usuário
string EMail {
get { return RowKey; }
}
//... Resto do código
}

Coloquei propriedades read-only somente para poder dar acesso às informações que estão em PartitionKey e RowKey, mas com nomes que fazem mais sentido para a minha classe ("mais de acordo com o domínio do problema", como diriam os cientificamente corretos). Só que o DevTableGen não gerou a tabela Usuario, pois (peculiaridade nº 2) ele se recusa a gerar a tabela se há propriedades read-only na classe.

"OMLE", pensei. "Oh My Left Egg". Já cansado desta discussão e sem pensar direito, coloquei então o set das propriedades CodigoEmpresa e EMail:

[DataServiceKeys("PartitionKey", "RowKey"]
public class Usuario
{
int PartitionKey { get; set; } // aqui vai ficar o código da empresa
string RowKey { get; set; } // aqui vai ficar o e-mail do usuário
// Por aqui eu leio o código da empresa
int CodigoEmpresa {
get { return PartitionKey; }
set { PartitionKey = value; }
}
// Por aqui eu leio o e-mail do usuário
string EMail {
get { return RowKey; }
set { RowKey = value; }
}
//... Resto do código
}

A consequência (óbvia, por sinal) é que o DevTableGen gerou a tabela com ambos os pares de propriedades: agora eu tinha uma tabela de usuários que guardava o código da empresa em PartitionKey e CodigoEmpresa, e o e-mail em RowKey e EMail. Não interessa para ele se a propriedade é só uma "acessora" (accessor) a valores armazenados em outro lugar; a propriedade existe, então ele gera uma coluna pra ela.

Ainda fiz mais algumas tentativas, mas no final das contas acabei com a classe Usuario guardando o código da empresa em PartitionKey e o e-mail do usuário em RowKey, e acrescentando outra sugestão no Connect, pra ver se a Microsoft dá um jeito nisto.

A resposta foi "Obrigado por reportar esta situação. Vamos mandar para o time e blablabla vão ver se implementam para a próxima versão". Hum. Quero só ver...

domingo, 18 de outubro de 2009

AdventureWorks: Só o MDF!!!

"Pra quê simplificar se você pode complicar". Criaram um projeto no Codeplex pra instalação dos bancos AventureWorks, um conjunto de exemplos que a Microsoft começou a distribuir com o SQL Server 2005. Baixei o "troço". Tem lista de pré-requisitos - pra instalar uns BD´s de exemplo!!! E tem que ter "FILESTREAM" habilitada, o que - depois de muito apanhar - eu fiquei sabendo que não tem como habilitar na minha instalação: um SQL Server 2008 Express 32 bits em um sistema operacional de 64 bits. "By design", dizia um post no blog de um cara do time do SQL Server da M$. FILESTREAM não roda em WoW - Windows On Windows, i.e., aplicações de 32 bits rodando sobre uma emulação de um Windows 32 bits num Windows de 64 bits. A pergunta óbvia, já que existe uma versão 64 bits do SQL Server Express, é por que eu não instalei essa versão. Sei lá. Tava com sono. Tava distraído. Tava trocando a fralda da Júlia enquanto baixava o SQL Server Express.

Bem, precisei do "troço", aí baixei e instalei. O "troço" cria um monte de sub-diretório, instala uns scripts que criam a estrutura do banco, vazia, e depois carregam - via o tal de FILESTREAM - um monte de arquivos CSV nas tabelas do banco. Caraca, isso é que é complicar o que é simples. E um .BAK com um backup do banco, ou um MDF pra poder anexar o banco ao servidor, nada.

Acho que o tal do time que tá trabalhando no projeto do Codeplex deve ter alguma ótima razão pra criar um projeto de setup pra isso (tomara!!!). Mas depois de perder a paciência com esse "troço", resolvi dar um chute: baixei, lá do Codeplex mesmo, o projeto de setup do AdventureWorks da versão *2005* do SQl Server. Um MSI que tem dentro os arquivos MDF e LDF do banco AdventureWorks. Pronto! Simples. Descompactei o MSI, e anexei o BD ao meu SQL Server Express, que apesar de ser 2008, lê perfeitamente os arquivos do SQL 2005.

Pra baixar o MSI com o banco, vá em http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004. A opção é o "AdventureWorksDB.msi".

Pra descompactar o MSI, rode em um command prompt o seguinte comando:
msiexec -a NomeArquivo.MSI -qb TARGETDIR=caminho_completo_local_descompactação
Pra anexar o BD, abra o Management Studio e, no Object Browser, clique com o botão direito em "Databases" e selecione "Attach..".

Precisa de um projeto de setup pra isso???

sábado, 17 de outubro de 2009

INSERT em bloco de transação

Geek que é geek se fascina com essas coisas. Estou eu neste sábado *desesperado* estudando para o curso de SQL Server que vou começar a dar na segunda. Olhando o capítulo sobre execução de consultas no Inside SQL Server 2005: Query Tunning and Optimization, o cara (não foi a Delaney que escreveu esse capítulo - ela agora está "sub-contratando" autores pra série Inside...) me solta essa sem nem um comentariozinho sobre o código:

O cara tá fazendo uma sequência de 250.000 INSERT´s. Pra otimizar, ele abre uma transação antes do laço, e a cada 1.000 INSERT's dá um COMMIT e abre outra transação. Isso evita que seja feita uma transação pra cada comando INSERT executado. (Sim, Gafanhoto. Quando você não faz BEGIN TRAN, o SQL Server abre automaticamente uma transação pros comandos executados fora de uma transação. Isso permite, p.e., que você se arrependa e dê um ROLLBACK dentro de um trigger, mesmo sem aparentemente haver uma transação aberta).

Usando esse bloco de transações, ele roda os INSERT's em 250 transações, em vez de 250 mil transações. Lindo. Simplesmente lindo.

E acho que não precisa nem dizer, mas sim: sou geek. :-)

domingo, 11 de outubro de 2009

Tratador de Erros Global para ASP.NET e Windows Forms

Você não deve deixar *nenhuma* parte da sua aplicação sem tratamento de erro. Mas isto não significa que você terá que espalhar blocos try/catch em todo o seu código. Tanto aplicações Windows Forms quanto aplicações ASP.NET oferecem a possibilidade de criação de um tratador de erro global, que será ativado sempre que uma exceção não tratada (i.e., uma exceção que ocorreu fora de um bloco try/catch) acontecer.

Windows Forms

Como sempre, as coisas são mais fáceis em aplicações Windows Forms do que em ASP.NET. Windows Forms provê para o desenvolvedor o objeto Application que, dentre outas coisas, tem o evento ThreadException. Basicamente, devemos adicionar um tratador de eventos a este evento, e o tradador de erros global da nossa aplicação estará pronto.

Crie um novo projeto VB.NET Windows Forms e adicione um botão no formulário Form1. Depois, substitua seu código pelo seguinte:


Imports System.Diagnostics

Public Class Form1

Const NOME_FONTE_EVENTOS As String = "Minha Aplicação"

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Cria a fonte de eventos no Log de Aplicação do Windows
' Isto só precisa ser feito uma vez (p.e. no setup da aplicação)
If Not EventLog.SourceExists(NOME_FONTE_EVENTOS) Then
EventLog.CreateEventSource(NOME_FONTE_EVENTOS, "Application")
End If
' Define o tratador de errors global
AddHandler Application.ThreadException, AddressOf Me.TratadorErros
End Sub

Sub TratadorErros(ByVal sender As Object, ByVal e As Threading.ThreadExceptionEventArgs)
' Acesso à exceção ocorrida
Dim erro As Exception = e.Exception
' Escreve dados da exceção no Log de Aplicação
EventLog.WriteEntry(NOME_FONTE_EVENTOS, _
"Erro não tratado: " + vbNewLine + erro.ToString(), _
EventLogEntryType.Error)
' Mostra mensagem de erro não-técnica ao usuário
MsgBox("Um erro inesperado ocorreu. Por favor entre em contato com o administrador do sistema.", _
MsgBoxStyle.Critical)
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' Simula uma exceção não tratada
Throw New Exception("Oh oh Erro acessando o banco de dados")
End Sub
End Class

Clique no botão para simular uma exceção não tradada. Uma mensagem será mostrada e se você olhar o Log de Aplicação do Windows no Visualizador de Eventos (Iniciar > Executar > ebentvwr.msc), lá estará a exceção.

ASP.NET

ASP.NET disponibiliza o evento de aplição Application_Error, que é disparado sempre que uma exceção não tratada ocorre. Este tratador de evento pertence à classe de aplicação global (global application class), como o Visual Studio a chama. O código da classe de aplicação global fica no arquivo Global.asax. As aplicações ASP.NET, por default, não tem um arquivo Global.asax. Você pode adicionar um indo no menu Project e selecionando Add New Item > Global Application Class.

No tratador de evento Application_Error, você terá que usar dois métodos especiais:
  • Server.GetLastError(), que dá acesso à exceção ocorrida.
  • Server.ClearError(), que evita que o ASP.NET mostre a página com a mensagem de erro default.
Crie um novo projeto ASP.NET Web Application, adicione um arquivo Global.asax, e substitua o seu código com o seguinte:


Imports System.Web.SessionState
Imports System.Diagnostics

Public Class Global_asax
Inherits System.Web.HttpApplication

Const NOME_FONTE_EVENTOS As String = "Minha Aplicação"

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Cria a fonte de eventos no Log de Aplicação do Windows
' Isto só precisa ser feito uma vez (p.e. no setup da aplicação)
If Not EventLog.SourceExists(NOME_FONTE_EVENTOS) Then
EventLog.CreateEventSource(NOME_FONTE_EVENTOS, "Application")
End If
End Sub

Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Acessa a exceção ocorrida
Dim erro As Exception = Server.GetLastError()
' Instrui o Framework a não mostrar a mensagem de erro default
Server.ClearError()
' Escreve dados da exceção no Log de Aplicação
EventLog.WriteEntry(NOME_FONTE_EVENTOS, _
"Erro não tratado: " + vbNewLine + erro.ToString(), _
EventLogEntryType.Error)
' Redireciona usuário para página com mensagem de erro não-técnica
Response.Redirect("/erro.aspx")
End Sub

End Class

Crie uma página chamada "erro.aspx"; esta é a página para a qual os usuário serão redirecionados quando da ocorrência de uma exceção não tratada.

Por último, adicione um botão à página Default.aspx, e substitua seu código com o seguinte:


Partial Public Class _Default
Inherits System.Web.UI.Page

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
' Simula uma exceção não tratada
Throw New Exception("Oh oh Aquele erro de acesso ao banco de novo")
End Sub
End Class

Clique no botão e você será redirecionado para a página erro.aspx, e um evento será logado no Log de Aplicação com a informação da exceção.



UPDATE: O código no evento Application_Start vai falhar no Windows Vista e Windows 7, devido à segurança mais restrita destes sistemas operacionais. O código tenta criar uma fonte de eventos no Log de Aplicação para logar os erros nesta fonte. Os métodos EventLog.SourceExists() e EventLog.Create() tentam varrer os logs do Windows para verificar se a fonte especificada já existe. Isto causa o erro, pois por default não é permitido o acesso ao Log de Segurançapor aplicações ASP.NET. Há várias maneiras de se solucionar isto, mas uma solução simples e rápida é criar a fonte "na mão": crie no Registry a seguinte chave: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\eventlog\Application\NOME_APLICACAO, trocando NOME_APLICACAO pelo nome da fonte de eventos que você quer criar. Um caminho "mais correto" seria p.e. criar uma aplicação console que executasse o código do evento Application_Start, e empacotar esta aplicação em no pacote de setup do seu site, para ser executada ao final do setup.

quinta-feira, 8 de outubro de 2009

Hã? Silverlight?

Para quem está iniciando no desenvolvimento em Silverlight, tem uma série de artigos do Tim Heuer (Gerente de Programa da Microsoft para o Silverlight) que são bastante didáticos e dão um empurrão bom em quem ainda está na linha de partida:

http://timheuer.com/blog/articles/getting-started-with-silverlight-development.aspx

O investimento de tempo vale a pena!

SQL Server Compact is not intended for ASP.NET development

O SQL Server Compact é uma versão do SQL Server bastante restrita em termos de recursos, mas que roda in-process, ou seja, não roda como um serviço. Desta forma, aplicações que o usam não precisam de direitos daministrativos na sua instalação, o deploy destas aplicações é simplificado (em relação a instalar outras edições do SQL Server), e o uso de recursos é baixo. Roda até em Windows Mobile!

Bem, tentei usar um banco do SQL Server Compact Edition em um site ASP.NET de teste que estava fazendo pra dar aula. Aí apareceu a mensagem de erro "SQL Server Compact is not intended for ASP.NET development". Isto porque, como a mensagem diz, o SQL Server Compact não foi projetado para desenvolvimento ASP.NET. Algo a ver com problemas de acesso multi-usuário. Mas se você quiser usá-lo só para um teste rápido, basta colocar no seu Global.Asax o seguinte código:


Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs
AppDomain.CurrentDomain.SetData("SQLServerCompactEditionUnderWebHosting", True)
End Sub

Desta forma você "está se responsabilizando" pelo uso do SQL CE na sua aplicação ASP.NET, e o erro não volta a ocorrer.

quarta-feira, 7 de outubro de 2009

www.whatismyip.org

Ao procurar um gerador de hashes SHA-1 pra strings, esbarrei com este site: www.whatismyip.org. O gerador de hashes fica em http://www.whatsmyip.org/hash_generator/. Tem outras ferramentas úteis, como um codificador/decodificador de strings base-64 e um contador de caracteres.

domingo, 4 de outubro de 2009

Não consigo subir VM no Virtual Server

As VM's instaladas pelo Virtual Server são disparadas pelo Virtual Server Administration Website. Só que quando eu tentava iniciar uma VM, e também em algumas outras opções (ex: troca do tipo de disco de IDE para SCSI), a tela HTML do Vitual Server Administration Website simplesmente não respondia. E ainda aparecia na barra de status do IE: "Error on page", que nem quando você acessa uma página com erro de JavaScript. Bem, não sei o que estava acontecendo, mas se você clicar naquele botãozinho de "Modo de Exibição de Compatibilidade" do IE8, a página funciona normalmente.

LINQ to SQL x LINQ to Entities

Este anúncio tem quase 1 ano, mas só esbarrei com ele agora, ao começar a estudar LINQ To SQL para o curso 2310. O Program Manager para LINQ to SQL e LINQ to Entities anunciou no blog da equipe do ADO.NET que "a partir do .NET Framework 4.0 [Visual Studio 2010], LINQ to Entities será a solução recomendada [pela Microsoft] de acesso a dados com LINQ em cenários relacionais". LINQ to SQL será mantido, mas pelo jeito, não será mais evoluído.

sexta-feira, 2 de outubro de 2009

400 Bad Request acessando o Blob Storage do Azure

Rapidinha que já perdi MUITO tempo com esse erro imbecil. Fui acessar o Blob Storage do Azure e qualquer operação que eu chamava retornava "400 - Bad Request". Depois de 3 horas virando, revirando e testando mil coisas, descobrimos que o erro era que na hora que eu ia instanciar um objeto da classe BlobStorage, através de BlobStorage.Create(), eu passava as credenciais do Table Storage, em vez das credenciais do Blob Storage.

Código errado:
BlobStorage blobs = BlobStorage.Create(
StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());
Código certo:
BlobStorage blobs = BlobStorage.Create(
StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration());
Tem 3 métodos de recuperação de credenciais: GetDefaultTableStorageAccountFromConfiguration(), GetDefaultBlobStorageAccountFromConfiguration(), e GetDefaultQueueStorageAccountFromConfiguration(). Cada um correspondendo ao storage do Azure que você quer acessar. Ok, eu chamei o método errado. Mas "Bad Request"??? Sinceramente, as mensagens de erro do Azure estão precisando de uma revisada...