quarta-feira, 18 de dezembro de 2013

Evitar duplo submit com jQuery

No trabalho hoje me deparei com a necessidade de impedir que se façam múltiplos submits em uma página Web.

Na empresa onde trabalho não usamos asp.net MVC ou Web Forms, mas sim um framework próprio da empresa, feito sobre asp.net, que lembra muito o asp clássico.

Independente do ambiente ou framework, de ser em php, .net etc, se não houver um recurso nativo para evitar isso então você deve se preocupar: lentidão para enviar a página e o usuário com "dedinho nervoso" clicando em enviar mil vezes por segundo, levando a múltiplos posts (submits) e a inserir mútiplos dados idênticos no seu banco de dados.

Além do trabalho server-side, de validação, prevenção de duplicidades na camada de persistência, redirect para uma página de sucesso etc, deve haver alguma coisa client-side para prevenir o submit duplo.

Com javascript é possível desabilitar o botão no momento do click, e evitar que ele seja pressionado novamente.
Se for um Button do tipo AspButton (System.Web.UI.WebControls.Button), da paleta Web do Visual Studio, num ambiente Web Forms, talvez você deseje adicionar o script como um atributo do botão, no evento load da página.

Com jQuery fica muito fácil, e funciona de uma vez para todas as forms e botões de uma página.

De todos os scripts que eu fucei o desse site http://thepugautomatic.com/2008/07/jquery-double-submission/ me pareceu o melhor. Ele praticamente cria uma função nova no jQuery, que você pode reusar em todas as páginas com uma linha de código.



//isso você coloca em um arquivo js externo, a ser chamado depois do jquery, ou na tag script de uma página
            jQuery.fn.preventDoubleSubmit = function() {
              jQuery(this).submit(function() {
                if (this.beenSubmitted)
                  return false;
                else
                  this.beenSubmitted = true;
              });
            };

//isso você coloca dentro do $(document).ready
    $(document).ready(function() 
    { 
      jQuery('form').preventDoubleSubmit(); 
    }


E está pronto: só de incluir essa pequena linha já resolve todos os problemas de dedo nervoso nas suas páginas.

Tentei também outros scripts, e o script abaixo também funcionou muito bem, embora peque quanto à elegância e ao reaproveitamento de código:

//esse embora diferente, faz a mesma coisa que o script acima: atua na interceptação do submit da form, mas desabilita os botões logo após o submit
$('form').submit(function() {
                if(typeof jQuery.data(this, "disabledOnSubmit") == 'undefined') {
                    jQuery.data(this, "disabledOnSubmit", { submited: true });
                    $('input[type=submit], input[type=button]', this).each(function() {
                        $(this).attr("disabled", "disabled");
                  });
                  //$('input[type=submit], input[type=button]', this).attr("disabled", "disabled");
                  return true;
            }
            else
            {
                return false;
            }
});

Esse terceiro script não funcionou muito bem, não sei explicar porque, mas ele deveria atuar no evento click, sumindo com o botão (ou desabilidando-o) logo o clique aconteça.

//esse atua de forma diferente: tenta interceptar o click
            $('input[type=submit], input[type=button]').live('click', function(){
               
                if(!$(this).prop("disabled"))
                {
                    $('input[type=submit], input[type=button]', this).each(function() {
                        $(this).attr("disabled", "disabled");
                    });
                    
                    //$('input[type=submit], input[type=button]', this).attr("disabled", "disabled");

                    return true;
                }
                else
                {
                    return false;
                }
            });

Uma coisa que  o primeiro script não faz é desabilitar ou esconder os botões, mas basta adicionar a linha $('input[type=submit], input[type=button]', this).attr("disabled", "disabled") para fazer isso. Detalhe que em ambos os scripts eu deixei essa linha comentada. É que as linhas acima já fazem isso varrendo todos os resultados do seletor com um .each(), onde eu posso aproveitar para colocar mais comportamento nesse código.

No seletor, estou passando  thi como contexto, como visto na linha acima. Não sei porque, se omitir isso, não conseguimos fazer sequer uma submissão.

Fique atento, no jQuery, à diferença entre .prop e .attr, explicarei isso num próximo post.

sábado, 7 de dezembro de 2013

Como usar a mesma dll em uma aplicação windows forms e web forms

Recentemente me deparei com a seguinte situação em uma aplicação legada: tinha uma dll com as classes de negócio / aplicação, mas essas dlls faziam, de vez em quando, uso do namespace system.web. 

Meu desafio era utilizar estas classes em uma versão windows forms da aplicação, mas isso era impossível, uma vez que com a presença do namespace system.web a aplicação não compilava, e não era permitido a adição da minha dll. 

Você pode estar pensando, e eu também pensei, que o problema se resolveria facilmente adicionando uma referência à dll system.web.dll, mas se fosse só isso o problema estaria resolvido.

O que realmente me causou esse problema era o fato de que quando você cria novas aplicações windows forms ou console, por padrão o visual studio 2010 usa a versão "client profile" do .net framework, uma versão mais magra que não tem toda aquela parafernalha server. No entanto, minha aplicação fazia muito uso de HttpContext.Current.Session.Add, dentro de system.web.dll, que é justamente uma parafernalha server. 
Consegui resolver esse prodblema trocando o framework para o framework "comum", com isso pude adicionar minha dll e a aplicação até compilava, o que não quer dizer que funcionava. 

Vá em project --> properties e na aba application, mude o target  framework de .net framework 4 client profile para .net framework 4, simplesmente.

Vá em project --> properties e na aba application, veja o target framework

Mude o target framework para .net framework 4


Corrigi o problema na aplicação legada separando em classes que implementam uma mesma interface as partes que usam e que não usam HttpContext, e usei o framework de injeção de dependência/ IoC Unity para instanciar a classe correta dependendo da aplicação ser web ou windows. Mas também dá para resolver sem isso, usando a dll legada mesmo.

Fiz 4 exemplos de minhas possíveis soluções usando workarounds:
Usando portable class library
Usando o class library comum
Usando diretivas de compilação (preprocessor directives)
meu preferido: usando reflection

para ver o meu post no msdn americano

segunda-feira, 2 de dezembro de 2013

Manter a imagem de background fixa

No css, para manter fixa a imagem de background enquanto o texto rola por cima você deve usar o código:

body {background-image: url('fundo.jpg'); background-repeat: no-repeat; background-attachment: fixed }

Caso deseje que a imagem role junto, use:

body {background-image: url('fundo.jpg'); background-repeat: no-repeat; background-attachment: scroll }

Veja o teste abaixo, coloquei o texto em amarelo para que pudesse ser melhor visualizado sobre a imagem:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus vel consequat ligula, et facilisis lacus. Morbi quis auctor ante. Curabitur eros diam, faucibus ut fermentum a, euismod a libero. Duis sed faucibus est. Donec vel dui a dolor luctus luctus. Duis felis turpis, porta vitae nisi et, consequat consequat mi. Praesent enim enim, adipiscing vel dignissim id, adipiscing in neque. Suspendisse vitae lorem elit. Sed quis felis tempor, ornare ipsum id, suscipit justo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Morbi vitae luctus sapien, tincidunt porttitor tellus. Donec in fringilla dolor. Aliquam ut vulputate dui, id porta dolor. Nunc volutpat eleifend ornare. Integer metus orci, tristique vitae tellus non, euismod ornare orci. Donec dolor est, porta non vulputate nec, semper eget ligula. Duis non mi venenatis, malesuada lectus ut, faucibus massa. In hac habitasse platea dictumst. Integer lacinia auctor suscipit. Nam feugiat quam dui, gravida luctus neque porttitor sit amet. Donec malesuada ullamcorper velit et ultrices. Cras ultricies mollis vehicula.

Etiam a fringilla ante. Pellentesque non varius lectus. In interdum purus est, eget lobortis diam ornare ut. Praesent eget cursus ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec non purus porta, posuere elit a, convallis justo. Mauris non euismod risus. Nam sem dolor, tincidunt at mi eu, iaculis eleifend risus.

Fusce a bibendum eros. Suspendisse laoreet erat nec neque tristique, vitae viverra nibh imperdiet. Quisque porta mauris quis est porta tincidunt. Sed elementum leo tortor, ut dignissim nisi placerat ac. Aenean accumsan neque non sem tincidunt, ut aliquam enim tempus. Suspendisse sagittis fringilla elit in rhoncus. Aenean id risus justo. Nunc sit amet libero quam. Aenean facilisis metus nec tellus accumsan, in posuere velit egestas. Integer et mauris dignissim, sagittis nisi at, malesuada libero.

Donec at nunc risus. Nullam bibendum elementum interdum. Integer vulputate id nisi sit amet consectetur. Etiam vel lorem quis neque rutrum mollis. Aliquam a justo feugiat, imperdiet ipsum in, viverra est. Sed at aliquam sapien, in fermentum leo. Aenean ornare blandit urna. Suspendisse id nibh tristique, adipiscing libero eget, cursus nibh. Aenean quis diam in diam varius auctor. Nam ac augue quis enim convallis rhoncus vitae ac odio. Nam porta congue quam.

Nullam viverra mi nec fringilla tristique. Nunc et arcu vitae ante vehicula fringilla. Etiam sed leo vehicula, vulputate sem ut, viverra eros. In at aliquet enim. Mauris quis eros non sem mollis pharetra. Ut eu est quis velit eleifend consequat. Duis non hendrerit erat, eu fermentum metus. Morbi imperdiet mollis sollicitudin. Duis vel consequat turpis. Curabitur semper vel risus nec mollis. Quisque quis mi enim.

Pellentesque ultrices sit amet libero posuere posuere. Sed ullamcorper dolor id velit rutrum, ac pharetra lorem rhoncus. Ut vitae scelerisque dolor. Phasellus sit amet tincidunt diam. Nulla porttitor sem non libero pulvinar, eu adipiscing magna blandit. Sed pharetra placerat vehicula. Nullam suscipit felis ut leo suscipit condimentum. Mauris metus nibh, consectetur id iaculis ac, blandit vitae velit. In vel viverra dui. Suspendisse placerat massa eu nisi porta tempor. Vivamus suscipit eros eu tempus ultricies.

Integer vehicula nisl in metus porta viverra. Sed vulputate sodales ligula nec egestas. Sed at elementum dolor. Proin turpis odio, mollis ac bibendum sit amet, pulvinar in tortor. Praesent semper semper nisi, et tempor risus dapibus eget. Mauris placerat rhoncus metus, eu egestas lectus gravida ac. Suspendisse fermentum rutrum dictum. Nulla interdum mauris ac metus adipiscing, sed porttitor nisi aliquam. Pellentesque tempus, eros sit amet tempus laoreet, risus mauris laoreet mi, nec placerat risus quam sed dolor. Aenean pharetra tortor nec laoreet adipiscing. Aenean at tristique velit, vitae porttitor lacus. Curabitur laoreet tortor ac mauris vehicula, a vestibulum lacus vulputate. Aenean porttitor lobortis aliquet. In erat mi, adipiscing et tincidunt eu, venenatis sit amet justo. Nunc rutrum dapibus vehicula.

Quisque a sapien vel metus placerat laoreet. Morbi consectetur magna quam, et mollis nisl sodales ut. Maecenas nulla erat, viverra id erat et, auctor ultrices orci. Curabitur quis metus eros. Vivamus porttitor arcu ut placerat rutrum. Maecenas odio libero, pulvinar et varius ac, vulputate at urna. Integer volutpat, justo commodo luctus consequat, erat dolor volutpat dolor, nec vehicula nisl erat ut nisl. Vivamus auctor purus id enim interdum interdum ac non est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

Nulla facilisi. Quisque pellentesque, leo eu luctus vehicula, leo quam mollis odio, sit amet varius nunc mi vitae tellus. Pellentesque vehicula luctus purus. Morbi sodales pellentesque mauris ut scelerisque. Aliquam fringilla tellus in justo accumsan egestas. Integer fringilla gravida tortor id viverra. Nam varius justo sed dignissim congue. Nulla et ornare purus. Fusce leo purus, porta fermentum egestas vitae, faucibus quis urna. Mauris enim ipsum, volutpat volutpat metus quis, sagittis vulputate ipsum. Suspendisse vulputate vel metus id mattis.

Phasellus risus velit, tempus ut pulvinar at, ultricies ac lacus. Duis elementum metus eu risus bibendum luctus. Mauris aliquet pretium nisl, id vehicula nibh feugiat non. Sed tristique turpis sit amet arcu molestie, eget rhoncus justo molestie. Morbi a orci quis erat fermentum tempor. Donec sed velit cursus, interdum nibh quis, feugiat enim. Quisque sed turpis vel odio vehicula commodo. Suspendisse congue eu risus sed tristique. Etiam ac mauris ut dolor tempus pretium. Pellentesque fermentum quam est, a mollis sem commodo ac. Duis id enim ac tortor iaculis varius. Cras feugiat, leo ac tempor sollicitudin, est libero ornare magna, vel venenatis magna turpis auctor odio. Proin turpis neque, posuere ut sodales aliquam, eleifend id ligula. Quisque mauris nulla, ultricies in mollis at, pharetra vel ligula.

Vestibulum ac felis et eros viverra congue. Duis varius lacinia magna sed tincidunt. Phasellus quis lectus eu diam mollis vulputate. Vivamus faucibus lectus vel nisi fermentum, ac cursus eros rutrum. Curabitur tellus eros, viverra nec nisl nec, aliquam vulputate urna. Nulla ultrices molestie magna ut dignissim. Pellentesque turpis eros, commodo sed convallis et, placerat eu urna. Sed posuere diam ut felis pharetra, at pharetra velit euismod. Vestibulum commodo neque justo, eleifend pharetra lorem lobortis at.

Duis convallis sem felis, vehicula sodales purus dignissim id. Cras sit amet placerat massa. Donec vestibulum vulputate sapien, vel iaculis arcu volutpat suscipit. Nam rhoncus ipsum vel nisl convallis tempus. Mauris rutrum nisl a enim aliquam tincidunt a nec sapien. Vivamus pharetra dui in felis posuere tempor. Mauris at mi non velit mattis tempor. Curabitur tincidunt lorem eros, id mattis augue venenatis id. Pellentesque in tempor turpis, vel posuere purus. Integer ullamcorper congue lorem, vitae imperdiet purus aliquet non. Vestibulum nec velit pellentesque odio interdum semper. Donec ac commodo augue, sit amet mollis elit.

Donec sit amet semper lorem. Nunc ac sem eu mi rutrum viverra ut eget magna. Curabitur tristique mollis mi, consectetur tincidunt orci consequat vitae. Aliquam in sem malesuada leo ornare commodo. Curabitur nec velit vitae ante tempor tincidunt a ac massa. Sed leo urna, pulvinar blandit leo sed, lobortis viverra turpis. Phasellus sagittis gravida ornare. Aenean at venenatis nibh. Donec mattis leo erat, in tincidunt dolor sollicitudin non. Curabitur suscipit vitae libero id ornare.

Proin elementum, metus ac tincidunt consequat, dui turpis vehicula nunc, et eleifend nulla metus sit amet nisl. Curabitur sem odio, mollis sit amet consectetur sit amet, faucibus vitae arcu. In eget imperdiet nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eu metus massa. Sed vulputate aliquam tellus, vel placerat massa porttitor vitae. Nam ultricies quis nibh vitae bibendum. Nullam tempor sed nisi a interdum. Aenean consectetur ornare dolor gravida tincidunt. Sed vel dignissim velit. Vivamus leo mauris, faucibus vulputate adipiscing et, laoreet quis risus. Nunc ullamcorper pulvinar est eleifend suscipit. Nullam varius interdum metus nec faucibus. Aliquam sed fringilla sapien, at rhoncus massa.

Curabitur eget velit ligula. Vivamus turpis erat, iaculis eget pulvinar et, rutrum ut diam. Mauris nulla magna, congue nec consequat eu, faucibus nec nisi. Cras in lacinia quam. Sed ut dolor vel risus sollicitudin iaculis. Vivamus porttitor in lacus vitae commodo. Nulla a risus eget nunc posuere egestas convallis in urna. Ut eget laoreet turpis. Fusce sagittis, urna quis tempor condimentum, magna turpis rutrum leo, vitae blandit mauris nulla ac leo. Cras tincidunt metus consequat, dictum nibh laoreet, feugiat nunc. Cras non imperdiet metus. Nunc facilisis lacus sit amet augue ultricies, id rhoncus tortor hendrerit. Integer gravida ut lorem quis luctus. Phasellus dapibus nunc tristique aliquet sodales. Fusce scelerisque tellus justo. Donec ultrices velit vel pellentesque blandit.

Nulla mattis nibh at metus sollicitudin, eu volutpat felis fringilla. Maecenas adipiscing feugiat molestie. Ut lobortis gravida tortor ac sagittis. Cras sed leo in orci ullamcorper convallis vitae ac massa. Donec suscipit egestas magna in consequat. Nam a sagittis mi. Donec in ultricies lacus.

Integer mattis tellus tellus, a mattis quam hendrerit sed. Vestibulum in lorem dui. Curabitur viverra, turpis ac iaculis pulvinar, diam neque sollicitudin nisl, condimentum mollis ligula odio et sapien. Cras id diam eget metus interdum consectetur eget id turpis. Suspendisse et turpis eu libero luctus iaculis. Maecenas id tempus nisl, quis vehicula lorem. Donec fermentum nisl a ante pretium faucibus.

Nunc ac turpis a nulla dignissim imperdiet quis sit amet eros. Nam lobortis risus vel urna pharetra egestas. Suspendisse aliquam vestibulum ullamcorper. Nam elementum mollis elit id luctus. Proin fringilla lorem id eleifend ultricies. Maecenas odio ipsum, congue eu egestas nec, tincidunt et ante. Fusce vel tincidunt turpis. Aenean facilisis felis a iaculis tempor. Nulla eu dui ut ipsum porta pulvinar. Morbi velit sem, ultricies quis sollicitudin malesuada, ullamcorper ut elit. Curabitur at nunc non ligula vehicula posuere sit amet nec augue. Donec ac fermentum purus, id molestie orci. Ut et suscipit tortor. Curabitur nec bibendum magna.

Sed tincidunt justo sed dolor vehicula pulvinar. Suspendisse potenti. Pellentesque vel ligula orci. Donec tincidunt, eros quis hendrerit elementum, turpis urna elementum magna, sit amet eleifend risus lacus quis metus. Nullam sit amet vestibulum dui. In hac habitasse platea dictumst. Maecenas vitae neque tellus. Aenean ante eros, tempus eu arcu ac, suscipit condimentum leo. Nunc quis dapibus libero. Sed justo mi, dapibus euismod justo vel, congue tincidunt eros.

Listar todas as culturas do .net e a configuração corrente

Lidar com culture sempre é um problema, e a conversão de datas e números para strings e vice-versa é pior ainda se o seu sistema permite que usuários de múltiplas culturas imputem a informação da maneira a que estão habituados, ou se você tem dados sensíveis à cultura gravados em campos verchar do banco de dados.
 É importante que as formatações de dados sejam aplicadas à cultura apenas na exibição do dado, mas nunca no seu processamento e armazenamento. Ao gravar no banco de dados, grave sempre o dado bruto em seu formato e tipo de dado padrão (double, integer, datetime etc) usando para isso parâmetros tipados ou o Entity Framework, ou ainda o nHibernate.
 Quando necessário armazenar, por exemplo, uma data como string, então opte por um formato reconhecido internacionalmente, como o padrão ODBC: yyyy-MM-dd (2013-12-02).
Abaixo um programinha console que lista todas as Cultures disponíveis e mostra qual é a corrente configurada no .net.

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace CultureList
{
    class Program
    {
        static void Main(string[] args)
        {

            // get culture names
            List list = new List();
            foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
            {
                string specName = "(none)";
                try { 
                    specName = CultureInfo.CreateSpecificCulture(ci.Name).Name;
                    list.Add(String.Format("{0,-12}\t{1,-12}\t{2,-40}\t{3}\t{4}\t{5}\t{6}", 
                        ci.Name, 
                        specName, 
                        ci.EnglishName, 
                        ci.NumberFormat.NumberGroupSeparator,
                        ci.NumberFormat.NumberDecimalSeparator, 
                        ci.NumberFormat.CurrencyGroupSeparator, 
                        ci.NumberFormat.CurrencyDecimalSeparator));
                }
                catch { }

                
            }

            list.Sort();  // sort by name

            // write to console
            Console.WriteLine("CULTURE\tSPEC.CULTURE\tENGLISH NAME\tN-GROUP\tN-DECIMAL\tM-GROUP\tM-DECIMAL");
            Console.WriteLine("--------------------------------------------------------------");
            foreach (string str in list)
                Console.WriteLine(str);

   Console.WriteLine("");  
   Console.WriteLine("");    
            Console.WriteLine("Informações de cultura corrente:");
            Console.WriteLine("--------------------------------------------------------------");    
            Console.WriteLine("System.Globalization.CultureInfo.CurrentCulture -->" + System.Globalization.CultureInfo.CurrentCulture);
            Console.WriteLine("System.Globalization.CultureInfo.CurrentUICulture -->" + System.Globalization.CultureInfo.CurrentUICulture);
            Console.WriteLine("System.Globalization.CultureInfo.InstalledUICulture -->" + System.Globalization.CultureInfo.InstalledUICulture);

            Console.ReadLine();

        }
    }
}

Código disponível aqui

sexta-feira, 29 de novembro de 2013

Fazer uma consulta no SQL Server e enviar o resultado por e-mail, usando C#

Esse é um exemplo em C# que eu sempre passo para todo mundo que está começando na linguagem e quer aprender qualquer coisa um pouco mais complexa do que um "hello world".
Trata-se de um script / função que consulta um banco de dados e devolve o resultado, como um relatório, por e-mail. Então dois temas são abordados: conexão com o banco de dados consultando-o e envio de e-mail.
uso C# para me conectar com um banco de dados MS SQL Server e fazer uma query. O resultado da query é enviado por e-mail, como texto, no corpo do mesmo.
Originalmente escrevi esse programa como exemplo para um colega não programador, que já sabia programar um pouco, porém no VBA do Excel, e que já conhecia o Microsoft SQL Server muito bem e sabia fazer queries e trazê-las para uma planilha excel, mas não conhecia nada de C# e não tinha vivência como programador.

No entanto este tutorial também é destinado à webdesigners que precisam fazer um script para envio de e-mail, mas não querem se aprofundar no C#.

É um exemplo bem simples, e é mais simples ainda de se fazer no C#.

Se você precisar (com certeza vai) de algum relatório um pouco mais avançado que seja enviado por e-mail, pode fazer um script que consulte o banco de dados e devolva o resultado por e-mail, algo assim:






/*
    esqueleto básico de uma aplicação do tipo console 
*/


/*

	textos que devem ser trocados, de acordo com a situação:
	1)[[servidor]] Servidor do banco de dados MS SQL Server (host ou ip)
	2)[[banco_de_dados]] Nome do banco de dados
	3)[[login]] login do banco
	4)[[senha]] senha do banco
	5)[[tabela]] tabela, view ou procedure / sentença sql a ser executada
	6)[[campo 1]],[[campo n]] campos 1 ... n do resultado da consulta a serem trazidos no texto
	7)[[remetente]] remetente do e-mail
	8)[[destinatario]] destinatário do e-mail
	9)[[com copia]] com cópia para 
	10)[[servidor de e-mail]] Servidor de e-mail
	11)[[login do email]] login do e-mail
	12)[[senha do email]] senha do e-mail


*/

//essas estruturas depois da palavra using, do lado de fora da classe, chamam-se namespaces. 
//Cada namespace é uma subdivisão de uma hierarquia de bibliotecas de classes, que se encontram em um arquivo
//namespaces podem ser de sistema, que já vem junto, podem ser feitos por você, ou podem ser comprados
//using um.namespace significa que nesse arquivo sendo editado agora você usará recursos que estão nesse namespace. 

//a árvore completa de namespaces é tão grande que a microsoft tem um site onde você pode pesquisar, mais fácil do que colocar tudo num livro

using System; //este é o principal namespace, onde se encontram as funções básicas do sistema. Este é obrigatório.
using System.Collections.Generic; //este namespace serve para lider com coleções genéricas em memória. Não será usado aqui, e é desnecessário, mas o visual studio coloca automaticamente.
using System.Linq; //este namespace serve para fazer consultas "tipo sql" via C#, em coleções de memória. Pode ser usado com bancos de dados, mas não o usaremos aqui.
using System.Text; //este namespace serve para geração de textos longos e manipulação de arquivos de texto em geral. Este usaremos
using System.Data; //este namespace abstrai tudo o que tem a ver com bancos de dados e coleções de dados
using System.Data.SqlClient;//extensão da biblioteca acima, serve para conectar-se com o SQL Server
using System.Net.Mail; //namespace que serve para trabalhar com e-mails

//abaixo o namespace criado por você, toda aplicação ou biblioteca fica dentro de um namespace
namespace MandaConsultaEmail
{

    //este é o nome da classe. Em um programa orientado a objetos as funções (equivalentes as sub's do vb que você já conhece) ficam agrupadas dentro de classes
    class Program
    {
        //o método Main é o método principal de um programa executável, é o ponto de entrada
        //toda a aplicação windows deve ter um método main como este
        //ao dar dois cliques em um programa o windows executará o método Main do programa automaticamente
        //esses argumentos dentro do método Main são parâmetros de linha de comando que você pode passar para o aplicativo
        //o fato de ele ser estático significa que ele deve ser executado diretamente a partir da classe, e não a partir de um objeto da classe (explicação detalhada sobre diferença entre objeto e classe fica para um outro dia)
        static void Main(string[] args)
        {
            //string builder é uma classe que fica dentro de system.text. Ela será usada para criarmos o resultado que você quer por e-mail
            //stringbuilder é a classe e strb é o objeto (instância/exemplo) dessa classe
            //classe é uma categoria de objetos, um esqueleto
            //objeto, como o próprio nome diz, é um exemplo desta categoria

            //classe        //objeto
            StringBuilder   strb = new StringBuilder();

            //se você tivesse uma classe chamada Carro, um objeto desta classe poderia ser um fusca verde placa ngc6417
            //objetos de uma classe são construidos a partir da palavra new. Por exemplo new StringBuilder() cria um novo StringBuilder
            //a função StringBuilder() que tem o mesmo nome da classe StringBuilder é chamada de construtor

            SqlConnection oCon = new SqlConnection(@"Data Source=[[servidor]];Initial Catalog=[[banco_de_dados]];Persist Security Info=True;User ID=[[login]];Password=[[senha]]"); //cria um objeto de conexão com o banco
            //ao criar um objeto de conexão uma string pode ser passada como parâmetro
            //esta string é chamada de connection string e contém instruções para se conectar no SQL, como endereço da máquina, nome do banco, login e password.
            //é comum colocar-se a string de conexão em um arquivo de configuração e não no código, assim se mudar a senha do banco não precisa recompilar o programa. 
            //preceder uma string com @ desabilita as sequências de escape. Usar esse recurso sempre que a string contiver slashes "\" (este foi um erro que cometi escrevendo esse tutorial, até cachorro velho esquece dessas besteirinhas)

            SqlCommand oComm = new SqlCommand("select top 10 * from [[tabela]]", oCon);// cria um objeto-comando. Esse objeto servirá para disparar o comando no banco.
            //como todo comando precisa de uma conexão onde exeutar, passamos oCon (o objeto de conexão criado) como segundo argumento 

            //objetos de comando se criam assim: 
            //new SqlCommand(comandos , conexão)

            oCon.Open(); //antes de executar o comando deve-se abrir a conexão. não SEJE como eu, que sempre esqueço disso.

            SqlDataReader dr = oComm.ExecuteReader(); //cria um leitor de dados e o armazena no objeto dr

            //o SQLCommand oComm possui dois métodos principais, um ExecuteReader e o ExecuteNonQuery. 
            //o ExecuteNonQuery serve para executar comandos que não retornem dados, como insert, update, delete. 
            //o ExecuteReader serve para executar comandos que retornam dados  trazendo esses registros
            //o executereader cria um SQLDataReader internamente, por isso você não precisa dar o comando dr = new SQLDataReader. Um método que cria um objeto (não sendo o seu constructor) é chamado de factory method
            //se o comando for retornar dados crie um SQLDataReader para varrer os dados com SqlDataReader dr = oComm.ExecuteReader().
            //se não retornar dados não crie o datareader, use direto  o  oComm.ExecuteNonQuery()

            //no C# os argumentos do if sempre deverão vir entre parênteses
            if(dr.HasRows) //pergunto se o dr retornou alguma linha
            {
                while (dr.Read()) //read le uma linha e avança para a próxima, como uma barra de rolagem, mas ela só pode avançar, não pode retroceder
                {	
					strb.Append(dr["[[campo1]]"].ToString() + " - " + dr["[[campo n]]"].ToString() + "\r\n"); //adicionando campo i e campo 2 (e uma quebra de linha) no stringbuilder, para cada linha trazida da consulta, exemplo:
                    //strb.Append(dr["cpf"].ToString() + " - " + dr["nome"].ToString() + "\r\n"); //adicionando cpf e nome (e uma quebra de linha) no stringbuilder, para cada linha trazida da consulta
                    //o DataReader dr pode ser indexado de duas formas, pelo nome do campo ou pelo número do campo, exemplo:
                    //dr["cpf"] trará o valor do CPF
                    //dr[0] trará o valor do primeiro campo
                    //dr[1] trará o segundo campo, a lista começa em 0
                }
            }

            //saindo do loop temos que mandar o e-mail


            MailMessage mailMessage = new MailMessage(); //cria um novo objeto do tipo MailMessage chamado mailMessage
            //repare que o C# é sensível a maiúscula / minúscula, portanto mailMessage é diferente de MailMessage
            //eu particularmente não gosto de colocar o nome de um objeto igual ao nome de sua classe, mas você encontrará muito disso
            //você pode preceder seus objetos com uma abreviação do tipo de classe que eles são, por exemplo:
            //MailMessage mmFuncionarios = new MailMessage(); 
            
            mailMessage.From = new MailAddress("[[remetente]]"); //cria um objeto do tipo endereço de e-mail que é o e-mail do remetente 
            mailMessage.Sender = new MailAddress("[[remetente]]"); //remetente também
            mailMessage.To.Add("[[destinatario]]"); ////e-mail do destinatário
            mailMessage.CC.Add("[[com copia]]"); // é o com cópia

            //a diferença entre sender e from eu não sei, deve-se pesquisar no msdn

            mailMessage.Subject = "Assunto do email";

            //joga todo o texto do stringbuilder para o corpo do e-mail, com a lista completa do que você quer
            mailMessage.Body = strb.ToString();

            //define se o corpo do e-mail será html ou não
            mailMessage.IsBodyHtml = false;


            //SmtpClient é uma classe que funciona como o outlook, é um client de SMTP (o protocolo de comunicação utilizado por e-mails)
            SmtpClient smtpClient = new SmtpClient();

            smtpClient.Host = "[[servidor de e-mail]]"; //servidor de e-mail
            smtpClient.Port = 587;	//25; //porta, a 25 é a padrão de e-mail sem criptografia (era padrão na época deste tutorial, hoje o padrão é 587)
            smtpClient.UseDefaultCredentials = true; //true em grande parte dos casos, a saber, se o servidor se autentica com NTLM ou baseado em Kerberos. Caso seja true, o servidor usará as credenciais do usuário logado antes de enviar o e-mail. Leia:
			
				//http://msdn.microsoft.com/pt-br/library/system.net.mail.smtpclient.usedefaultcredentials(v=vs.110).aspx
				//http://msdn.microsoft.com/pt-br/library/system.net.credentialcache.defaultcredentials(v=vs.110).aspx
            
            //NetworkCredential  é uma classe que cria objetos que agrupam login e senha de redes, funciona assim:
            //new System.Net.NetworkCredential(login, senha);
            //no caso estamos usando o login e senha da intranet para mandar e-mail pelo servidor da mandic, considerando que intranet@lidertel.com.br é um e-mail válido na mandic
            smtpClient.Credentials = new System.Net.NetworkCredential("[[login do email]]", "[[senha do email]]");

            //setar isso para true em caso de gmail ou outro provedor que use ssl
            //smtpClient.EnableSsl = true;

            smtpClient.Send(mailMessage); //usa o client para enviar o message


            //depois de usar o connection, se não precisar mais, fexar e dar dispose()
            oCon.Close();
            oCon.Dispose();

            //o certo é fazer o dispose dentro de um bloco try... finally, mas depois falaremos disso. 

        } //fim do método main
    }//fim da classe Program
}//fim do namespace

//regras de boa conduta ao programar:
//1) métodos estáticos apenas quando forem ser executados por um programa de fora ou forem executados a partir da classe e valem para a classe inteira, independente do objeto
//2) métodos estáticos são stateless, ou seja, não devem alterar o estado interno dos objetos, campos e variáveis
//3) crie uma classe por arquivo.cs, sendo que o arquivo deve ter o mesmo nome que a classe
//4) métodos, variáveis, propriedades, campos, atributos, namespaces e classes devem ter nomes significativos
//5) identificadores compostos de mais de uma palavra devem ter a primeira letra de cada palavra em maiúscula





Ao escrever esse mini - tutorial me surpreendi com a quantidade de informação que precisa ser passada para uma pessoa nova em programação.
É diferente  de uma pessoa  nova em uma linguagem / ambiente / framework mas que já saiba programar o básico.

No caso desse exemplo, ele fazia uma consulta em umas tabelas do banco de dados do sistema Totvs RM Labore, retornando os dados em forma de texto no corpo do e-mail (podia muito bem ser um csv ou excel).
Omiti, obviamente, o nome do banco, da tabela e os parâmetros de conexão com o banco.

Esse é um tutorial sem passar pelas etapas "obrigatórias" de um tutorial de programação: palavras reservadas, declaração de variáveis, tipos de dados, operadores aritméticos, if ... etc não necessariamente nesta ordem.
Também muitas teorias são simplesmente ignoradas aqui: pulamos direto para a parte prática e o código.

Pelo menos o código está explicado linha a linha.

quinta-feira, 28 de novembro de 2013

Testar se uma variável é undefined no javascript

Para testar se uma variável é undefined ou null pode-se compará-la com as palavras chave undefined e null usando os operadores:
== (valor igual),
!= (valor diferente),
=== (estritamente igual, mesmo valor e tipo),
!== (diferente, tipo diferente)

No script abaixo existe uma variável y não declarada. Por causa dela uma exceção é gerada e tratada no catch logo abaixo.

Sempre é necessário verificar se uma variável/objeto é nula ou undefined antes de se usá-la, chamar seus métodos ou alterar suas propriedades.
É a não verificação dessas duas condições que sempre causa erros de script na página (objetos que não existem, que são nulos etc...).

O try ... catch vai garantir que você encontre o erro mesmo que esqueça dessas coisas essenciais, e seu script não para de funcionar, mesmo com erro.

 

 
 
  

Teste de Undefined

   var x;
   
   try
   {
    if(x == null)
    {
     document.getElementById('resultado').innerHTML   += "x é == null 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "x é != null 
";
    }
    
    
    if(x === null)
    {
     document.getElementById('resultado').innerHTML   += "x é === null 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "x é !== null 
";
    } 




    if(x == undefined)
    {
     document.getElementById('resultado').innerHTML   += "x é == undefined 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "x é != undefined 
";
    }
    
    
    if(x === undefined)
    {
     document.getElementById('resultado').innerHTML   += "x é === undefined 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "x é !== undefined 
";
    } 





    if(y == null)
    {
     document.getElementById('resultado').innerHTML   += "y é == null 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "y é != null 
";
    }
    
    
    if(y === null)
    {
     document.getElementById('resultado').innerHTML   += "y é === null 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "y é !== null 
";
    } 




    if(y == undefined)
    {
     document.getElementById('resultado').innerHTML   += "y é == undefined 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "y é != undefined 
";
    }
    
    
    if(y === undefined)
    {
     document.getElementById('resultado').innerHTML   += "y é === undefined 
";
    }
    else
    {
     document.getElementById('resultado').innerHTML   += "y é !== undefined 
";
    } 
   }
   catch(e)
   {
    alert(e.message);
   }

Peguei no stack overflow:

 http://stackoverflow.com/questions/7041123/test-if-something-is-undefined-in-javascript

quarta-feira, 27 de novembro de 2013

Wildcards nos seletores JQuery

O JQuery pode ser usado com Wildcards / Coringas nos seletores.

Queria aplicar uma regra de formatação e validação em todos os campos input onde deveriam ser inseridas datas, no entanto, como é um sistema legado, os elementos html não possuem ID's, ou pelo menos não alguns que eu tenha controle. Também não é possível no meu sistema atual atribuir classes aos elementos sem alterar o software que hera o html.

Minha última opção seria confiar nos seletores jQuery e na propriedade name dos inputs. Uma vantagem é que, na minha aplicação, todos os inputs de data tem o prefixo "dt_" no nome. Tudo que eu precisava era de uma forma de selecionar todos os inputs que começavam com "dt_".

O jQuery permite seletores por tipo de tag e atributo, então para selecionar inputs seria $("input[atributo='valor']"). No entanto não existe apenas o operador "=". Os operadores possíveis são:

*= (contém)
$= (termina com)
^= (começa com)

Então, para selecionar os que começam com "dt_" seria assim:

            //validação para campos data
            $("input[name^='dt_']").keyup(function () {
                if (this.value != this.value.replace(/[^0-9\/]/g, '')) {
                   this.value = this.value.replace(/[^0-9\/]/g, '');
                }
            });

terça-feira, 29 de outubro de 2013

Deletar todas as tabelas de um banco SQL Server

As vezes, quando estamos desenvolvendo software, precisamos simplesmente apagar todas as tabelas e recriá-las novamente, mas isso é impossível de se fazer caso existam chaves estrangeiras e outras constraints ou dependências dessas tabelas.

Você pode ter tentado o comando EXEC sp_MSForEachtable 'DROP TABLE ?', que é uma stored procedure não documentada que já vem de fábrica com o SQL e que executa um mesmo comando em cada tabela do banco de dados, substituindo ? pelo nome da tabela.
Mais sobre sp_MSForEachtable

Mesmo a sp_MSForEachtable não funcionaria para todas as tabelas. O ideal é que se apague tudo na ordem correta, ou se recrie o banco. Entretanto, se você deseja apagar as tabelas mas permanecer com suas procedures, por exemplo, e rodar o script para recriar as tabelas depois, você pode fazer o seguinte:

1) Conheça a view INFORMATION_SCHEMA.TABLE_CONSTRAINTS. 
Essa é uma  view de sistema que mostra os metadados do banco. No caso, ela lista todas as constraints relacionando-as com as tabelas que as possuem. Nela você pode ver todas as chaves primárias e estrangeiras.
Mais sobre INFORMATION_SCHEMA.TABLE_CONSTRAINTS

2) Conheça a view INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS.
Essa view de sistema lista todas as tabelas referenciadas por constraints, ou seja, é o lado "references" de uma chave estrangeira.
Mais sobre INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS

3) Faça o join das duas pelo campo CONSTRAINT_NAME

SELECT DISTINCT 
  tc2.TABLE_NAME,
  rc1.CONSTRAINT_NAME
 FROM 
  INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 LEFT JOIN 
   INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 
    ON tc2.CONSTRAINT_NAME = rc1.CONSTRAINT_NAME
Isso te dará uma lista de todas as chaves estrangeiras.

4) Gere um script para apagar as fk's Agora você pode construir um script para apagar cada uma das chaves estrangeiras concatenando da seguinte forma:
 SELECT DISTINCT 
  sql = 'ALTER TABLE [' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + ']'
 FROM 
  INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 LEFT JOIN 
   INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 
    ON tc2.CONSTRAINT_NAME = rc1.CONSTRAINT_NAME
Isso gerará uma coluna de resultado chamada SQL com várias linhas de comandos para apagar cada uma das fk's. Você pode dar um copiar/colar nesse script, ou executá-lo via cursor, da seguinte forma:

--deleta todas as FK's

DECLARE @Sql NVARCHAR(500) 
DECLARE @Cursor CURSOR

--cria um cursor para varrer o resultset
SET @Cursor = CURSOR FAST_FORWARD FOR
 SELECT DISTINCT 
  sql = 'ALTER TABLE [' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + ']'
 FROM 
  INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1 LEFT JOIN 
   INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 
    ON tc2.CONSTRAINT_NAME = rc1.CONSTRAINT_NAME
--atribui, a cada iteração do cursor, o valor do campo SQL na variável varchar @SQL
OPEN @Cursor FETCH NEXT FROM @Cursor INTO @Sql

WHILE (@@FETCH_STATUS = 0)

BEGIN

 --executa a string @SQL
 Exec SP_EXECUTESQL @Sql
 --Avança para o próximo cursor
 FETCH NEXT FROM @Cursor INTO @Sql

END

--fecha e desaloca o cursor
CLOSE @Cursor DEALLOCATE @Cursor

GO

--agora sim consegue executar sp_MSForEachtable e apagar todas as tabelas
EXEC sp_MSForEachtable 'DROP TABLE ?'

GO

Você pode também transformar esse código em uma stored procedure.

Have fun ;)

quarta-feira, 17 de julho de 2013

Uso de memória no SQL Server

O SQL Server, por padrão, tenta colocar o máximo possível de páginas na memória RAM.
Esse comportamento é padrão do SQL Server e é o esperado. Isso serve para assegurar o menor tempo de resposta possível na consulta.
Logicamente esse comportamento pode ser limitado. Podemos configurar o máximo de memória que o SQL Server pode consumir com paginação, deixando uma margem para outros serviços, no entanto isso não é recomendado pela Microsoft. A recomendação é que o SQL Server seja um servidor dedicado.
Recorri à documentação do SQL Server disponível no MSDN e outras fontes para fazer o performance tunning do servidor que eu administro e encontrei um artigo muito esclarecedor, gostaria de compartilhá-lo com vocês:
                Resumindo:
                1) O sql server sempre tentará usar TODA a memória disponível para cache de páginas
                2) Deveria ser exclusivo
               
                Indícios de que você precisa de mais memória:
                1) Seus mdf's tem mais que o dobro da ram disponível
                2) O tempo médio que uma página fica no cache é menor que 300 segundos
                3) Você não tem um DBA no time
                4) Você tem menos de 32 GB
                5) O preço da memória é menor do que R$ 2000
               
Dois fatos importantes é que deveríamos ter de RAM pelo menos a metade do tamanho do  arquivo do banco de dados, para que o tempo de vida das páginas em cache fosse mais alto (a troca dessas páginas onera o processador também).

Separei algumas queries úteis para verificar o consumo de memória do banco:

--queries usando mais memória
select memory_usage, host_name, program_name, host_process_id, login_name,original_login_name from sys.dm_exec_sessions order by memory_usage desc --uso de memória do servidor, por login e user

select granted_query_memory, * from sys.dm_exec_requests order by granted_query_memory desc  --uso de memória nas queries


--memória consumida pelo sql server atualmente
select
      CONVERT (VARCHAR,CAST(bpool_committed *8 AS MONEY),1)AS [SIZE],
      bpool_committed,
      bpool_commit_target
from
      sys.dm_os_sys_info     


--banco de dados com mais páginas alocadas no cache (use para descobrir em que banco rodar a query abaixo)
SELECT
      count(*)AS cached_pages_count,
      CASE database_id
            WHEN 32767 THEN 'ResourceDb'
            ELSE db_name(database_id)
    END AS Database_name
FROM
      sys.dm_os_buffer_descriptors
GROUP BY
      db_name(database_id) ,database_id
ORDER BY
      cached_pages_count DESC


--tabela com mais páginas alocadas no cache (use no baco indicado na query acima)
SELECT
      count(*)AS cached_pages_count,
      name,
      index_id
FROM
      sys.dm_os_buffer_descriptors AS bd INNER JOIN
        (
            SELECT
                  object_name(object_id) AS name,
                  index_id,
                  allocation_unit_id
            FROM
                  sys.allocation_units AS au INNER JOIN sys.partitions AS p ON au.container_id = p.hobt_id AND (au.type = 1 OR au.type = 3)
            UNION ALL
            SELECT
                  object_name(object_id) AS name, 
                  index_id,
                  allocation_unit_id
            FROM
                  sys.allocation_units AS au INNER JOIN sys.partitions AS p ON au.container_id = p.partition_id AND au.type = 2
        ) AS obj ON bd.allocation_unit_id = obj.allocation_unit_id
WHERE
      database_id = db_id()
GROUP BY
      name,
      index_id
ORDER BY
      cached_pages_count DESc
     



--expectativa de vida das páginas em cache (deve ser maior que 5 minutos ou 300 segundos)     
SELECT [object_name],
[counter_name],
[cntr_value]
FROM sys.dm_os_performance_counters
WHERE [object_name] LIKE '%Manager%'
AND [counter_name] = 'Page life expectancy'
              
Em anexo mando também um poster feito pela Quest Software (adquirida pela Dell) em parceria com a Dell e a IBM que mostra, de acordo com resultados de queries de sistema e de variáveis do performance monitor, qual é o componente sendo mais pressionado (memória, processador, disco) e as ações que devem ser tomadas para solucionar o problema.

Outros artigos interessantes:

Não podemos esquecer das pequenas queries do dia a dia. Dois problemas de lentidão que enfrentei nos meus servidores não foram referentes ao uso de memória. Em um deles, compartilhado com o IIS e uma aplicação asp.net, o problema era de CPU. Enquanto eu ganhava tempo otimizando a aplicação, configurei no application pool da aplicação asp.net para usar apenas 2 núcleos do processador de 4 núcleos (affinity mask) para que o SQL Server tivesse folga para processar. Configurei o máximo de memória que poderia ser usada pelo SQL Server e  o máximo que poderia ser usado pelo app pool, assim o SQL Server não deixaria o app pool sem memória. Esse foi um contexto onde os serviços não poderiam ser separados em servidores diferentes por uma questão de custo/benefício: não era uma aplicação vital para o negócio da empresa.
Numa outra ocasião, porém, o sistema permitia a inclusão de queries do usuário, tanto em relatórios como em cálculos e processamentos em lote ou fórmulas. Essas queries dos usuários avançados tiveram de ser revistas, pois era completamente esperado que esses usuários, por mais que sejam avançados, não tem conhecimento suficiente para evitar multiplicações cartesianas, subqueries desnecessárias, campos não usados e tirar o máximo proveito dos índices. 

Esse post foi para alertar que se o SQL Server estiver consumindo grandes quantidades de memória esse definitivamente NÃO É O MOTIVO  de alguma lentidão, mas que essa lentidão pode ser causada por n outros fatores. Na verdade quanto mais informação (relevante e frequentemente acessada) estiver na memória RAM, melhor. 

quarta-feira, 8 de maio de 2013

Delphi Prism, porque não vale a pena

Update
O Prism nunca foi da Embarcadero, ele pertence a Rem Objects, que tinha um contrato com a Embarcadero até esse ano, mas esse contrato já expirou e as duas empresas vão seguir seus caminhos.
O último Delphi Prism foi a versão XE3, se não me engano, mas o Rad Studio XE4 não virá com o Prism, como diz o Andreano Lanusse e o próprio pessoal da Rem Objects.
O Prism foi descontinuado na Embarcadero, mas continuará a evoluir na Rem Objects sob o nome de Oxygene (seu verdadeiro nome).
http://www.andreanolanusse.com/pt/delphi-xe4-conheca-as-novidades-para-ios-e-mais/
http://blogs.remobjects.com/blogs/mh/2013/04/17/p5822
http://www.itwriting.com/blog/7338-no-more-delphi-for-net-prism-removed-from-rad-studio-xe4.html
A partir desse momento a Embarcadero e a RemObjects não são mais parceiras, mas sim concorrentes, mas acredito que o Prism agora irá evoluir, implantando melhorias que não poderia antes devido às políticas da Embarcadero.
Sobre o velho assunto da "Morte do Delphi", eu acredito que o Delphi não irá morrer nunca, assim como o Cobol e o Clipper, mas está fadado à essa letargia: crescimento lerdo e muitas vezes na contra-mão. Todas as melhorias do Rad Studio 4 são tardias em comparação com outros ambientes, além de serem fortemente dependentes de aplicações / módulos de terceiros.
----------------------------------------------------


Antes de mais nada, Delphi Prism é uma excelente linguagem. Tem todos os recursos que uma linguagem moderna deve ter, é totalmente OO, tem sobrecarga de operadores, Generics, Lambda Expressions, Funções anônimas, é fortemente tipada mas permite type inference etc...

Longe de criticar a linguagem em si e quem investe nela, ou quem a concebeu, tenho algumas criticas com relação ao Delphi Prism que me levam a ponderar se vale mesmo a pena investir na linguagem, já explico o porque.

1) Você não vai economizar na curva de aprendizado. Se você acha que sendo um "Delpheiro" vai economizar na curva de aprendizado do Delphi Prism está enganado. Ao aprender, treinar e desenvolver em Delphi Prism você enfrentará os mesmos desafios que enfrentaria aprendendo C#. Isso se dá porque a maior parte desse aprendizado é referente à semântica (conjunto de bibliotecas, o como fazer) do que à sintaxe. A sintaxe você pega em um dia.

2) O Delphi Prism não é o Delphi .Net. O Delphi .Net (8, 2005, 2006 etc) morreu/foi descontinuado, uma pena. Mas este porduto ERA delphi. Não somente em termos de sintaxe e recursos da linguagem, mas em termos de semântica também. Afinal ele tinha uma "VCL", ou uma tentativa de se reimplementar a VCL no .Net Framework. Era um Framework dentro de outro. A migração do Delphi Win32 para o Delphi .Net era POUCO menos trabalhosa, e a curva de aprendizado um pouco mais suave.

3) Delphi Prism, ou Oxygene, é baseado em pascal, mas muito diferente se comparado com o Object Pascal. Não estou falando em termos de bibliotecas e frameworks que vem no pacote, mas sim de sintaxe. O uso da palavra method no lugar de function ou procedure na minha opinião é desnecessário. Agora, lembra do pascal? Para retornar o valor de uma função você fazia, no final do código da função, NomeFuncao := valor. Muito intuitivo, didático e inteligente. Internamente à função você pode usar o nome dela para atribuir-lhe o valor de retorno, como se ela fosse uma variável local dela mesma. Isso não impedia que se usasse chamadas recursivas ou ponteiros para função. No Delphi/Object pascal tivemos a introdução da variável artificial/implícita Result. Então se você tem uma função Soma, ela pode retornar o valor da maneira Soma := valor ou Result := valor. Result nada mais era do que um atalho, já que na época do pascal até meados do Delphi 1 todo mundo usava uma variável Result como acumulador temporário antes de retornar o valor da função. Mesmo assim Result ainda tem mais "cara" de variável do que de comando.
No Delphi Prism não existe mais a forma Soma := valor, ou método := valor. Você DEVE OBRIGATORIAMENTE usar Result. Result passou do status de "variável" para "comando". Result agora é para o prism o que o return é para as linguagens c-like. Só que sem a vantagem de abandonar imediatamente. Para isso você ainda deve forçar a saída do método. Na minha opinião tirar a sintaxe tradicional do pascal É UMA VIOLAÇÃO DO QUE É SANTO, sim, uma heresia.

4) Não existe (m) ferramentas, compiladores, alternativas gratuitas/open source para o Prism.  (o command line tools não conta, seu uso é muito restrito). Também não existem cursos/disciplinas dessa linguagem nos cursos técnicos ou tecnólogos. Logo, você não deve esperar facilidade em contratar um estagiário ou júnior nessa linguagem, muito menos um Sênior autodidata hobbista. A única maneira de ser um autodidata hobbista com Delphi Prism é com pirataria, e eu não faço apologia a pirataria.

Veja bem, continuo achando Delphi Prism demais, gosto muito mesmo, é uma iniciativa legal da Rem Objects e tem todo um histórico de coisas legais por trás disso, mas não programaria com ela se não fosse pago pra isso. Não que ela não tenha futuro, muito pelo contrário, ela pode ter um futuro promissor, mas tudo vai depender de como a dupla RemObjects Embarcadero divulgar e liberar a ferramenta. E não se iluda: Delphi Prism não é Delphi. Se você acha que pode fazer aplicações para IPhone / IPad / IPod no Delphi Prism muito mais facilmente simplesmente porque é "parecido" com Delphi está muito enganado. Utilizando C# você tem a mesma dificuldade, com a vantagem de instalar uma ferramenta a menos. (você não precisaria do prism, mas apenas do visual studio, mono develop, mono e monotouch). Mesmo assim desenvolver para IPhone / IPod / IPad é tão diferente do ambiente Delphi / Win32 que a acentuação na curva de aprendizado, ou seja, a sua dificuldade, estaria no ambiente, no sdk e nas api's da Apple, e não na linguagem. A dificuldade não mudaria muito se você escolhesse o objective-C. E não espere muita portabilidade do código feito em Prism para dentro e fora do windows/mac/linux. Isso porque as bibliotecas e apis que você usa em um não encontrará no outro. A unica coisa que dá para migrar são classes de negócio, as mais abstratas possíveis, e bibliotecas de serviços/funções feitas por você que são algoritmos puros, sem o uso de outras bibliotecas. Nada de GUI ou comunicações.

Basicamente é isso. Se por um acaso a linguagem ganhasse uma implementação open-source (GPL de preferência) e talvez um plugin para integrar sua sintaxe no Eclipse, Sharp Develop ou Mono Develop tudo bem. Enquanto esse dia não chega o Prism não passará de mais uma linguagem .Net entre tantas, com a desvantagem de ser para minorias e de ser só mais uma linguagem fechada e cara dentre tantas abertas e/ou gratuitas.

Críticas? Serão bem vindas. Trollagem? Dê-me audiência.

terça-feira, 30 de abril de 2013

Interfaces no Delphi e destruição prematura de objetos / access violation


Recentemente me perguntaram no site da DevMedia se é correto ou não misturar interfaces e objetos em uma conversão temporária, como esta abaixo:

var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
Segue links da Thread:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

Isso por causa do problema de se zerar o contador de referências em interfaces tendo mais uma referência ao objeto em uma variável do tipo objeto e destruir esse objeto prematuramente.

Basicamente nesses casos o comportamento esperado seria um erro.
Pode acontecer de o objeto continuar acessível na memória, ou pelo menos alguns métodos que não usem dados internos do objeto, mas esse comportamento não é o esperado e é causado simplesmente por "sujeira" na memória.
Fiz um teste aqui e também funcionou, mesmo assim eu não recomendo. Diferenças na versão do Delphi ou na plataforma podem ocasionar erros.
Na época em que escrevi esse artigo eu usava Delphi XE em um computador de 64 bits, mas não me lembro a versão do Windows.
Hoje fiz o teste tanto no Delphi XE como no Delphi XE 4 usando Windows 7, mas num computador de 32 bits.

O porquê de eu esperar que essa construção cause um erro eu explico:
Dado que interfaces contam referências, então são "Reference Counted" e dado que você cria, converte e usa um objeto dentro de um mesmo método (como o evento click de um botão).

Se você criar o seu objeto já atribuindo ele à uma variável do tipo interface, assim:
var i: ICarro;
begin
 i:= TCarro.Create; //1 referencia a um TCarro
 (i as ICarro).DeslocarSe; //2 referencias ao TCarro //não precisa disso, apenas mostro como funciona normalmente
  i.OutroMetodo;
end; //final do método, 0 referencias ao mesmo TCarro, então  o objeto "hospedado" em I é destruido com free.
É esperado que um objeto apresente erro nesse tipo de conversão principalmente quando ele possui outro objeto dentro. Considere um objeto que tenha um StringList como um de seus campos.
var i: TCarro;
begin
i:= TCarro.Create; //1 referencia a um TCarro, no entanto I é um objeto, portanto não conta referências

(i as ICarro).DeslocarSe; //1 referencias ao TCarro hospedado em I convertido para ICarro

 i.OutroMetodo; //antes de chegar aqui já temos 0 referências (pois não existe mais a referência temporária que estava sendo usada na conversão para ICarro), portanto Free é acionado e o objeto em I é destruído "por engano". Aqui deve ocorrer um erro ao se executar OutroMetodo, pois o próprio objeto está destruído .
end;


Veja esse exemplo que eu criei no Delphi Xe 4, links:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;



  IVeiculo = interface  (iinterface)
  ['{1D2F0F1D-60CC-47BE-AD22-4DA59D87C7CA}']
    procedure DeslocarSe;
    procedure Parar;
  end;

  IVeiculoTerrestre = interface (IVeiculo)
  ['{CD718BB9-FE3A-4C19-8CE2-01EBB1843B96}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  ICarro = interface (IVeiculoTerrestre)
  ['{3B167780-8EA0-4F59-9DBF-A9F68F54F595}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  //TCarro = class(TInterfacedObject,  ICarro)  //nessa forma você pode atribuir a criação de um TCarro a uma variável ICarro, IVeiculoTerrestre e IVeiculo, mas não pode fazer conversões do tipo AS entre eles
  TCarro = class(TInterfacedObject,  ICarro, IVeiculoTerrestre, IVeiculo) //esta forma permite qualquer tipo de atribuição e conversão.
  private
    FPlaca: TStringList;
    function GetPlaca: string;
    procedure SetPlaca(value: string);
  public
    constructor Create;
    destructor Destroy; override;

    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
    property Placa: string read GetPlaca write SetPlaca;
  end;


var
  Form1: TForm1;


implementation

{$R *.dfm}

{ TCarro }

constructor TCarro.Create;
begin
  FPlaca := TStringList.Create;
end;

procedure TCarro.DeslocarSe;
begin
  ShowMessage(Placa + ' Deslocando-se. ' + self.RefCount.ToString() + ' referências');
end;

destructor TCarro.Destroy;
begin
  FPlaca.Free;
  inherited;
end;

function TCarro.GetPlaca: string;
begin
  Result := FPlaca.Text;
end;

procedure TCarro.Parar;
begin
  ShowMessage(Placa + ' Parando.' + self.RefCount.ToString() + ' referências');
end;

procedure TCarro.Rodar;
begin
  DeslocarSe;
end;

procedure TCarro.SetPlaca(value: string);
begin
  FPlaca.Text := value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
  C.Rodar;
  C.Parar;

end;

procedure TForm1.Button2Click(Sender: TObject);
var C: TCarro;
  I: IVeiculoTerrestre;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias
  I.Rodar; //uso normal
  I.Parar; //uso normal
  I.Rodar; //uso normal
  I.Parar; //uso normal

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button3Click(Sender: TObject);
var C: TCarro;
  I: IVeiculo;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias

  I.DeslocarSe;
  I.Parar;

  //I agora pode ser convertido para qualquer uma das interfaces ancestrais de ICarro sem perder o controle de referência
  (I as ICarro).Rodar;
  (I as ICarro).Parar;
  (I as IVeiculoTerrestre).Rodar;
  (I as IVeiculoTerrestre).Parar;
  (I as IVeiculo).DeslocarSe;
  (I as IVeiculo).Parar;

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button4Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ivt1 := (C as IVeiculoTerrestre);
  iv1  :=  (C as IVeiculo);

  ivt2 := (ic as IVeiculoTerrestre);
  iv2  :=  (ic as IVeiculo);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;

end;

procedure TForm1.Button5Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ic.DeslocarSe;
  ic.Parar;

  if Supports(c, IVeiculoTerrestre, ivt1) then
  begin
    ivt1.Rodar;
    ivt1.Parar;
  end;

  if Supports(c, IVeiculo, iv1) then
  begin
    iv1.DeslocarSe;
    iv1.Parar;
  end;

  if Supports(ic, IVeiculoTerrestre, ivt2) then
  begin
    ivt2.Rodar;
    ivt2.Parar;
  end;

  if Supports(ic, IVeiculo, iv2) then
  begin
    iv2.DeslocarSe;
    iv2.Parar;
  end;

end;

procedure TForm1.Button6Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := ICarro(C);

  ivt1 := IVeiculoTerrestre(C);
  iv1  := IVeiculo(C);

  ivt2 :=  IVeiculoTerrestre(ic);
  iv2  :=  IVeiculo(ic);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;
end;

end.

Ele não deu erro como eu esperava no primeiro botão, mesmo assim eu não aconselho a fazer um uso de interfaces + objetos como aquele, principalmente quando seus objetos são passados para métodos ou retornados deles.
Infelizmente não consegui reproduzir esse erro hoje. Compilei e rodei esse código no Delphi XE, no Delphi XE4 e no Lazarus, e em todos os casos funcionou.
Mesmo assim, cuidado redobrado ao se passar variáveis do tipo interface como parâmetros.

quarta-feira, 17 de abril de 2013

Diferença entre Log Shipping, Mirroring e Replication

Li alguns artigos sobre a diferença entre Log Shipping, Mirroring e Replication e gostaria de compartilhar.
http://www.replicationanswers.com/ReplicationLogShippingMirroring.asp
Difference between Log Shipping, Mirroring and Replication
http://simplesql.blogspot.com.br/2011/01/replication-vs-mirroring-and-what-to.html

Basicamente, o Log Shipping é uma estratégia só de Disaster Recovery, mas não muito eficiente para relatórios ou replicação/distribuição dos dados. Ele nada mais faz do que restaurar em uma outra base os logs de transação periodicamente. Esta base fica com lock exclusivo, então consultas nela só podem ser feitas usando-se nolock (dirty reads) ou isolation level snapshot.
Log Shipping restaura o banco inteiro, inclusive tabelas de sistema, views, procedures etc.

Mirroring também é uma solução para Disaster Recovery e funciona para o banco todo, inclusive dados de sistema. Sua vantagem é que pode ser configurado com failover automático, ou seja, para entrar no ar assim que o servidor principal apresentar algum problema. Sua desvantagem é que não pode ser usado como um banco de dados de relatórios sem o uso de um custoso snapshot, cusando uma certa latência ou restringindo os relatórios a D-1.
Mirroring é a melhor solução para disaster recovery.

Replication é a melhor solução para relatórios com baixa latência e alta performance, distribuição, replicação e integração de dados. Pode ser usado como disaster recovery também, com baixíssima latência, mas não replica dados de sistema, apenas dados de aplicação, e não permite failover automático, necessitando de reconfiguração das aplicações. Portanto Replicação é melhor para os cenários onde você deseja ter uma cópia de uma ou mais tabelas de um banco A em outro banco B para consultas e relatórios mais rápidos, integrações ou mesmo uma replicação em duas vias para bancos de dados distribuídos.

Conclusão: Use Mirroring para Disaster Recovery se seu ambiente permitir. Mesmo assim o backup periódico do transaction log sempre é bem vindo.
Para aquela sua tabela, daquela sua outra aplicação, que precisa puxar ou copiar quase em tempo real os dados de uma tabela de um banco legado, use Replication.

Postagens populares

Marcadores

delphi (60) C# (31) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) .Net (9) Humor (9) javascript (9) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) Web (4) banco de dados (4) HTML (3) PHP (3) Python (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Ajax (2) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)