Lazaro's profileLazaro Fernandes Lima - ...BlogListsNetworkMore Tools Help

Lazaro Fernandes Lima - Blog

Tornar o simples complicado é fácil, tornar o complicado simples é criatividade, vontade e conhecimento.

Lazaro Fernandes Lima

Occupation
Location
Interests

SQL Server Strategies

Loading...Loading...

SQL Server Advanced Querying

Loading...Loading...
January 14

ASP.NET Cache: Utilizando dependências

Cache em aplicações web já se tornou uma necessidade extrema para aplicações que crescem muito com o passar do tempo, sendo quase indispensável para que a aplicação seja escalável e performática.

Contudo, a maioria das aplicações não trabalha com cache.

Neste post iremos trabalhar com scripts de cacheamento em queries do banco de dados, cache de arquivos (proximos artigos).

Podemos trabalhar com cacheamento nativo utilizando DEPENCÊNCIAS desde o ASP.NET 2.0.

Irei utilizar um banco de dados próprio, com 10000 registros em uma determinada tabela, SQL Server 2005, Visual Studio 2008 com framework 3.5.

Cache de queries do banco de dados

Primeiramente, baixe o banco de dados, no SQL SERVER 2005, attache o mesmo à uma instância ativa do SQL SERVER 2005 para que possamos utilizar a tabela com os 10000 registros.

Após configurado o banco de dados vamos a etapa de desenvolvimento da aplicação.

Crie um WebSite, com framework 3.5.
Adicione os seguintes controles à pagina:

  1. Button (btnObterDados)
  2. Label (lblTempo)
  3. GridView (grvDados)

Adicione um evento de click ao btnObterDados, o mesmo deve ficar da seguinte maneira:

protected void btnObterDados_Click(object sender, EventArgs e)
{
    //O objeto StopWatch será o nosso MEDIDOR, irá contabilizar o 
tempo gasto para efetuar todo o processo System.Diagnostics.Stopwatch timer = new System.Diagnostics.
Stopwatch(); timer.Start(); //Verifico se cacheDados é nulo if (Cache["cacheDados"] == null) { //Crio os tradicionais SqlConnection, SqlCommand,
adapter e dataset
SqlConnection conn; SqlCommand command; SqlDataAdapter da; DataSet ds = new DataSet(); //Aqui esta a criança! SqlCacheDependency cache; //Neste ponto, nenhuma novidade conn = new SqlConnection(ConfigurationManager.
ConnectionStrings["auxiliarConnectionString"].ConnectionString); //Para que o SqlCacheDependency funcione, ele precisa ser
inicializado pela classe estatica SqlDependency
SqlDependency.Start(conn.ConnectionString); conn.Open(); //Aqui temos um novidade, para que o
SqlCacheDependency funcione algumas regras devem ser respeitadas //O select * from [tabela] não é permitido!, defina EXATAMENTE
quais colunas deseja obter //O nome da tabela deve ser informado de forma completa,
utilizando o (dbo.tabela) por exemplo.
command = new SqlCommand("Select auxiliarid,auxiliarname,
auxiliarfullname from dbo.Auxiliar"
, conn); //Neste post não irei entrar em detalhes de como as dependências
funcionam, porém, fica clara a necessidade de HABILITAR as notificações
SqlCacheDependencyAdmin.EnableNotifications(conn.
ConnectionString); SqlCacheDependencyAdmin.EnableTableForNotifications(conn.
ConnectionString, "dbo.Auxiliar"); //Instancio o objeto cache utilizando o SqlCommand cache = new SqlCacheDependency(command); da = new SqlDataAdapter(command); da.Fill(ds); //Adiciono o SqlCacheDependency inserindo no terceiro parâmetro Cache.Insert("cacheDados", ds.Tables[0], cache); conn.Close(); conn = null; } grvDados.DataSource = (DataTable)Cache["cacheDados"]; grvDados.DataBind(); //Executo o Stop para que a contagem sera visualizada em lblTempo timer.Stop(); lblTempo.Text = timer.Elapsed.ToString(); timer = null; }
 

O código esta auto-explicativo, baixe o projeto em meu skydrive para verificar uma implementação mais detalhada onde utilizei paginação no gridview.

Os resultados obtidos com esta implementação ficam claramente positivos quanto a performance da aplicação.

Vale lembra que, é necessário ter o Service Broker habilitado e que quaisquer alterações nos dados da tabela que incluem as colunas exibidas, como alteração de um campo, automaticamente, remove o cache da aplicação. É aqui que mora a vantagem da implementação tradicional.

Testem updates no banco.

Ate mais

Baixe o banco de dados e a aplicação.

October 12

Desenvolvendo com LINQ

 
Buscando utilizar uma sintaxe de pesquisa que atue sobre uma arquitetura orientada a objetos, o LINQ (Language Integrated Query) possibilita uma navegação mais próxima ao nível da mesma.
Em pesquisas baseadas em listas, estamos acostumados a iterar sobre cada elemento efetuando um foreach ou outro tipo de laço na linguagem utilizada e efetuando as condições para exclusão ou inclusão de registros. Com o LINQ necessitamos de uma lógica totalmente aplicada ao design OO, que busque iterar sobre estes objetos, o que torna nossa implementação organizada e com maior produtividade, baseado em arquiteturas orientadas a objetos.
Gosto de exemplos que busquem sua aplicação no mundo real e que possibilitem uma compreensão ampla do assunto, logo, abaixo estão implementadas três classes para um exemplo relativamente simples, sendo esta uma implementação de controle de usuários e suas  permissões no sistema.

/*===================================*/

public class PermissaoTipo
{
 private string description;
 public string Description
 {
   get { return this.description; }
   set { this.description = value; }
 }

 private bool status;
 public bool Status
 {
   get { return this.status; }
   set { this.status = value; }
 }
}

public class Permissao
{
 private IList<PermissaoTipo> permissaoTipo;
 public IList<PermissaoTipo> PermissaoTipo
 {
  
get { return this.permissaoTipo; }
   
set { this.permissaoTipo = value; }
 }

 private string description;
 public string Description
 {
   get { return this.description; }
   set { this.description = value; }
 }

 private bool status;
 public bool Status
 {
   get { return this.status; }
   set { this.status = value; }
 }
}

public class Usuario
{
 private IList<Permissao> permissao;
 public IList<Permissao> Permissao
 {
   get { return this.permissao; }
   set { this.permissao = value; }
 }

 private string nome;
 public string Nome
 {
   get { return this.nome; }
   set { this.nome = value; }
 }

 private bool status;
 public bool Status
 {
   get { return this.status; }
   set { this.status = value; }
 }
}

/*===================================*/

As classes acima contém a estrutura de permissões do nosso modelo de sistema para as pesquisas com LINQ.
Basta adicionar as classes acima e já podemos adicionar várias funções a usuários no sistema.
Nosso objetivo é efetuar pesquisas sobre estes usuários da forma tradicional e da forma orientada a arquitetura do LINQ, utilizando diversas sintaxes da mesma.
Antes de iniciarmos nosso modelo de sistema, vejamos abaixo uma pesquisa simples em um array de strings, o exemplo é demonstrado baseado no modo tradicional e outro utilizando LINQ, cada um retorna um IList<T> e IEnumerable<T> genéricos.
 
/*===================================*/

string
[] usuarios = new string[] { "Lazaro", "Zezinho", "Zezinha", "Liminha", "Lulinha" };
//iteração tradicional para obter somente usuários que iniciem com a letra "L"

IList<string> _lUsuarios = new List<string>();
foreach (string usuario in usuarios)
{
  if (usuario.Substring(0,1) == "L")
  _lUsuarios.Add(usuario);
}

//interação utilizando LINQ
var _lUsuariosLINQ = from usuario in usuarios
                             where usuario.StartsWith("L")
                             select usuario;

foreach (string usuario in _lUsuarios)
{
Console.WriteLine(usuario);
}

Console.WriteLine("");

foreach (string usuario in _lUsuariosLINQ)
{
Console.WriteLine(usuario);
}

Console.ReadKey();

/*===================================*/
 
Primeiramente, estamos utilizando interfaces para listas genéricas para armezenar o resultado da seleção, muitos desenvolvedores perguntam se este var é uma variante, a resposta é, não, é somente um atalho para um IEnumerable<T>.
Segundo, nossa sintaxe LINQ tem basicamente três passos, são eles:
 
 from
 where
 select
 
Onde, o objeto criado na cláusula from é uma variável que será tipada em tempo de compilação, sem a necessidade de instanciação da mesma para um tipo definido, este processo se torna transparente, uma vez que o compilador já sabe o que a lista passada irá retornar, este recurso é fantástico. Um dos mais poderosos recursos do framework 3.5 é a possibilidade de se trabalhar com este recurso como demonstrado acima, tornando o design altamente orientado a objetos, evitando Type Cast e possíveis erros de conversão em nossa lógica.
 
Terceiro, no exemplo com LINQ, estamos evitando a passagem de registro à registro para verificar se o mesmo atende ao critério definido para a pesquisa, acelerando e tornando o processo transparente para o desenvolvedor. Em artigos futuros iremos demonstrar que o LINQ se demonstra muito eficiente no quesito performance.

[Mão na massa]
 
Com base nas classes acima, nosso objetivo agora é cadastrar 4 funções no modelo de sistema.
Ao todo serão 3 passos antes de iniciarmos com as pesquisas, são eles: [definir os tipos de permissao], [definir as permissoes inserindo seus tipos] e [definir os usuário e suas funções]

/*===================================*/

#region
Definindo Tipos de Permissoes

IList<PermissaoTipo> olPermissaoTipo = new List<PermissaoTipo>();

PermissaoTipo
oPermissaoTipo = new PermissaoTipo();
oPermissaoTipo.Description = "Ler";
oPermissaoTipo.Status = true;
olPermissaoTipo.Add(oPermissaoTipo);

oPermissaoTipo = new PermissaoTipo();
oPermissaoTipo.Description = "Inserir";
oPermissaoTipo.Status = true;
olPermissaoTipo.Add(oPermissaoTipo);

oPermissaoTipo = new PermissaoTipo();
oPermissaoTipo.Description = "Editar";
oPermissaoTipo.Status = true;
olPermissaoTipo.Add(oPermissaoTipo);

oPermissaoTipo = new PermissaoTipo();
oPermissaoTipo.Description = "Remover";
oPermissaoTipo.Status = true;
olPermissaoTipo.Add(oPermissaoTipo);

#endregion

#region Definindo as Permissäes e suas caracter¡sticas

IList<Permissao> olPermissao = new List<Permissao>();

Permissao oPermissaoTotal = new Permissao();
oPermissaoTotal.Description = "Gerenciar Usu rios";
oPermissaoTotal.Status = true;
oPermissaoTotal.PermissaoTipo = olPermissaoTipo;
olPermissao.Add(oPermissaoTotal);

Permissao oPermissaoParcial = new Permissao();
oPermissaoParcial.Description = "Gerenciar Temas";
oPermissaoParcial.Status = true;
var permissaotipo1 = from TipoDePermissao in olPermissaoTipo
                               .Where(x=>x.Description.Contains("Ler") ||
                                          x.Description.Contains("Inserir") ||
                                          x.Description.Contains("Editar"))
                               select TipoDePermissao;
oPermissaoParcial.PermissaoTipo = permissaotipo1.ToList();
olPermissao.Add(oPermissaoParcial);

Permissao oPermissaoMinima = new Permissao();
oPermissaoMinima.Description = "Gerenciar Aparˆncia";
oPermissaoMinima.Status = true;
var permissaotipo2 = from TipoDePermissao in olPermissaoTipo
                               where TipoDePermissao.Description.Equals("Ler")
                               select TipoDePermissao;
oPermissaoMinima.PermissaoTipo = permissaotipo2.ToList();
olPermissao.Add(oPermissaoMinima);

#endregion

#region Definindo os usu rio e seus n¡veis de permissÆo

IList<Usuario> olUsuario = new List<Usuario>();

Usuario oUsuario1 = new Usuario();
oUsuario1.Nome = "Lazaro Fernandes Lima";
oUsuario1.Status = true;
var p1 = from permissao in olPermissao
             where permissao.Description.Contains("Gerenciar Usu rios")
             select permissao;
oUsuario1.Permissao = p1.ToList();
olUsuario.Add(oUsuario1);

Usuario oUsuario2 = new Usuario();
oUsuario2.Nome = "Lulinha";
oUsuario2.Status = false;
var p2 = from permissao in olPermissao
             where permissao.Description.Contains("Gerenciar Usu rios")
             select permissao;
oUsuario2.Permissao = p2.ToList();
olUsuario.Add(oUsuario2);

Usuario oUsuario3 = new Usuario();
oUsuario3.Nome = "Zezinho";
oUsuario3.Status = true;
var p3 = from permissao in olPermissao
             where permissao.Description.Contains("Gerenciar Temas")
             select permissao;
oUsuario3.Permissao = p3.ToList();
olUsuario.Add(oUsuario3);

Usuario oUsuario4 = new Usuario();
oUsuario4.Nome = "Luizinho";
oUsuario4.Status = false;
var p4 = from p in olPermissao
             .Where(x => x.Description.Contains("Gerenciar Aparˆncia"))
             select p;
oUsuario4.Permissao = p4.ToList();
olUsuario.Add(oUsuario4);

#endregion

#region Visualizando as permissoes e tipos de permissoes de cada usuario

foreach (Usuario usuario in olUsuario)
{
    Console.WriteLine("");
    Console.WriteLine("========================");
    Console.WriteLine("");
    Console.WriteLine(usuario.Nome);
    foreach (Permissao permissao in usuario.Permissao)
    {
        Console.WriteLine(" " + permissao.Description);
        foreach (PermissaoTipo permissaotipo in permissao.PermissaoTipo)
        {
        Console.WriteLine(" " + permissaotipo.Description);
        }
    }
}

Console.ReadKey();

#endregion

/*===================================*/

O retorno esperado é visualizado abaixo (clique para ampliar):

imagem

Perceba que em determinados momentos a sintaxe Lambda foi utilizada, como no seguinte exemplo.

from TipoDePermissao in olPermissaoTipo
                               .Where(x=>x.Description.Contains("Ler") ||
                                          x.Description.Contains("Inserir") ||
                                          x.Description.Contains("Editar"))
                               select TipoDePermissao;

Este tipo de sintaxe permite trabalharmos com métodos anônimos, na verdade, é uma evolução dos métodos anônimos do framework 2.0 e um recurso novo no framework 3.5, iremos futuramente ampliar nossos conhecimento em lambda em artigos futuros.

Conclusão
Como iríamos trabalhar o sistema modelo acima, inserindo listas filtradas de objetos sem a utilização de LINQ?
Ficam claras as vantagens quando desenvolvemos sistemas que se utilizam de listas em enorme quantidade e intensas pesquisas em listas.
Ao utilizar este novo recurso temos uma variedade enorme de possibilidades de desenvolvimento que aceleram e muito a produtividade, performance e qualidade de nossos sistemas.

Muito da arquitetura do LINQ precisa ser comentada e discutida pela comunidade, em breve novos artigos serão inseridos sobre a arquitetura do LINQ.

Para facilitar a visualização do resultado final, disponilizei o link para baixar toda a solução do modelo de sistema em meu SkyDrive http://cid-89940b8b6a6a9b23.skydrive.live.com/self.aspx/P%c3%bablico/Artigo|_Desenvolvendo|_com|_LINQ.rar

Espero que tenham gostado deste artigo
Até mais. ;)

Fontes



August 19

RAID - Análise

Olá pessoal,

Lendo um white paper sobre arquitetura de bancos de dados SQL Server da Microsoft obtive diversas respostas para dúvidas quanto as melhores práticas em uma arquitetura utilizando ambientes em RAID (Redundant Array of Independent Disks). Sendo altamente recomendado a sua utilização quando problemas relacionados a disponibilidade de informações e alta taxa de I/O de dados afetam nossos ambientes.

Segue abaixo descrição dos níveis 0, 1, 5 e 10 de ambientes RAID aplicados a estratégias de arquitetura de banco de dados.

RAID0 (simple striping): Baixo custo, sendo o tipo mais simples de se configurar dentre os apresentados, excelente performance para armazenamento e leitura, uma vez que, vários discos estarão responsáveis pelo I/O, acelerando no minimo 100% sua performance. Seu ponto fraco esta na falta de tolerância à possíveis falhas.

RAID1 (simple mirroring): Cria uma cópia exata, espelhada, de todos os dados entre dois ou mais discos. Este nível provê boa redundância, porém, baixa eficiencia em armazenamento, não sendo indicado para performance em I/O e sim, segurança e maior disponibilidade dos dados, uma vez que, todos os dados estarão sendo espelhados em outras unidades de disco automaticamente.

RAID5 (striping with parity):  Nível mais popular utilizado na indústria (segundo documentos da Microsoft). Indicado para menor Input e maior Output de dados, provê uma distribuição de paridade em discos de forma alternada ao qual estas informações sao utilizadas para montar os dados em caso de perda de algum disco, (numero total de drives – 1) / (numero total de drives), claro, baseado em discos idênticos  Wink

RAID10 (0+1) (stripe of mirrors): Este aqui é o mais indicado para a maioria dos casos, porém, 4 discos são necessários para a menor implementação deste nível de RAID, seu custo é o mais alto. Combina o melhor do RAID0 e RAID1, boa performance e excelente tolerância à falhas.

A eficiência na utilização dos níveis de RAID variam de casos para casos conforme podemos analisar acima.

A utilização deste ambiente RAID controlado via hardware é altamente recomendado, uma vez que, o S.O. estará totalmente responsável pelo controle deste ambiente, e sabemos que isto não cheira muito bem ;)

Referências:
http://www.microsoft.com/technet/prodtechnol/sql/2005/physdbstor.mspx (white paper ;] )
http://pt.wikipedia.org/wiki/RAID
http://www.clubedohardware.com.br/artigos/651

Até mais ;)

August 18

Dynamic SQL

Eu me lembro quando programava com ASP 3 e sempre dava uma olhada em scripts SQL montados diretamente no código fonte. Como abaixo:

dim sql
sql = "Insert MinhaTabela (codigo, nome) "
sql = sql & "values('" & request.form("codigo") & "', '" & request.form("nome") & "')"

objConn.execute(sql)

É um perigo esse tipo de codificaçao!
Porém, muito flexivel.

Já no SQL SERVER 2005, este tipo de construção de script também está disponível, ele se chamada Dynamic SQL.

Ele permite que scripts sejam 'montados' em uma variável, por exemplo, e executados como instruções tradicionais.
Sua flexibidade está em quando se necessita poupar caminhos condicionais, como IFs e CASEs dentro de uma procedure, por exemplo.

Ou em casos em que limpe, altere ou exiba uma determinada tabela com determinadas colunas, onde, no SQL SERVER 2000 esta funcionalidade de 'concatenar' um script com tabelas, colunas, instruções e executar a partir de uma variável não esta disponível.
Com esta flexibilidade podemos desenvolver scripts bem dinâmicos, como o próprio nome diz. Vejam abaixo:

DECLARE@COLUMNS AS VARCHAR(200)

DECLARE
@WHERE AS VARCHAR(200)
DECLARE
@SQL AS VARCHAR(MAX)
SET
@COLUMNS = 'NAME,CREATE_DATE'
SET
@WHERE = '''USER_TABLE'''
SET
@SQL = 'SELECT ' + @COLUMNS + ' FROM SYS.OBJECTS WHERE TYPE_DESC = ' + @WHERE
EXEC
(@SQL) -- Este script exibe as colunas NAME e CREATE_DATE, onde TYPE_DESC = 'USER_TABLE' .Dentro da tablea SYS.OBJECTS
 
A utilização de Stored Procedures SEM Dinamic SQL, ainda é mais rápida "na maioria dos casos", mas podemos utilizar o SP_EXECUTESQL, conforme link de documentação ao final do post, sendo esta Stored Procedure responsável por armazenar em batch o plano de execução da dynamic sql, vale lembrar que, principalmente em queries em que a sua utilização é constante, só utilize dynamic sql quando necessário, uma vez que, os scripts executados em Dynamic SQL não são armazenados em cache, exeto pelo uso da sp_executesql como comentado, quem possuir dados estatísticos por favor comentem ;)

A minha opinião sobre este assunto ainda é imatura pelo fato de não ter utilizado muito esta feature presente desde a versão 2005, mas questões como segurança ( Ataque SQL Injection ) e performance me preocupam bastante.

Fora isso, ele pode ser utilizado para melhorar o desempenho em SPs com muitas condicionais com certeza Wink
 
Até mais. ;)
 

Limpeza de registros otimizada

Uma das novidades mais interessantes do SQL SERVER 2005 são as CTE ( Common Table Expressions ) e novas funções de ranking.

Uma CTE funciona como uma tabela temporaria e uma subquerie ao mesmo tempo, ao qual podemos trabalhar somente no momento da execução do script, ela tem uma construção rápida e extremamente flexível.

No SQL SERVER 2000 quando precisávamos de uma tabela temporária local, por exemplo, era necessário construir a tabela com uma estrutura para comportar a inserção e logo após o seu uso era necessário efetuar o comando drop table ou deixar que o SQL SERVER fizesse o servico sozinho. Já com as CTE as coisas são mais rápidas e eficientes, com elas agora e possível inserir, editar e remover registros direto da tabela original por meio da CTE. Vejam abaixo:

CREATE TABLE minha_tabela1
(
col1  varchar(100),
col2  varchar(100),
col3  varchar(100)
)
go

insert minha_tabela1 values(1,'abc','1')
insert minha_tabela1 values(2,'def','1')
insert minha_tabela1 values(3,'ghi','1')
insert minha_tabela1 values(4,'jkl','1')
insert minha_tabela1 values(1,'abc','1')
insert minha_tabela1 values(2,'def','1')
insert minha_tabela1 values(3,'ghi','1')
insert minha_tabela1 values(4,'jkl','2')

go
WITH MinhaSelecao
AS
(
SELECT *,row_number() over (partition by col1,col2,col3 order by col1,col2,col3) as [Number] FROM minha_tabela1
)
DELETE MinhaSelecao
WHERE number > 1

GO
SELECT * FROM minha_tabela1
-- veja que neste ponto os registros duplicados foram removidos sem maiores segredos.

Vejam a simplicidade do script acima para limpeza de registros duplicados.

ROW_NUMBER() retorna um número sequencial (iniciado por 1) representando o número de um determinado registro agrupado pela clausula PARTITION BY, esta clausula faz a divisão dos registros duplicados e retorna a identificação do mesmo no resultado.

Esta nao e sua única utilidade, sendo possível tambem, construir queries recursivas.
Segue abaixo a estrutura retirada da documentacao no MSDN referente as CTE e a funcao ROW_NUMBER():

WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )
The list of column names is optional only if distinct names for all resulting columns are supplied in the query definition.
The statement to run the CTE is:
SELECT <column_list>
FROM expression_name


ROW_NUMBER ( )     OVER ( [ <partition_by_clause> ] <order_by_clause> )

 

até mais ;)

 
No list items have been added yet.

DataBase Design

Loading...Loading...