quinta-feira, 10 de novembro de 2011

Receita de bolo para EF CODE FIRST e ORACLE 10G

Utilizando Entity Framework no desenvolvimento de aplicações, temos a vantagem de trabalhar de forma independeente com qualquer banco de dados, desde que haja um "provider" específico para Entity Framework.

Neste post, mostro que é possível utilizar EF com o banco de dados Oracle. Existem diversos providers de Oracle para o EF, um bom exemplo é o da DevArt (dotConnect), porém é uma solução paga. A própria Oracle está desenvolvendo um provedor com suporte ao Entity Framework, porém até o momento deste POST ainda se encontra na versão Beta 3, porém em breve teremos a versão de produção, aí então é só atualizar.


Pré-requisitos:

* ORACLE 10G express edition previamente instalado
http://www.oracle.com/technetwork/database/express-edition/database10gxe-459378.html

* ODAC1120250Beta_EntityFramework_32bit_xcopy
http://www.oracle.com/technetwork/developer-tools/visual-studio/downloads/index.html

* EntityFramework Code First 4.1
http://www.microsoft.com/download/en/details.aspx?id=26825

Mãos à massa!

Crie um novo usuário no Oracle, para este post criei um usuário chamado REPOSITORIO. O banco de dados para este exemplo consite em uma tabela de PESSOAS, que possui zero ou mais telefones, relacionada com uma tabela de TELEONES A estrutura do banco de dados ficou assim:

CREATE TABLE "PESSOAS"
( "ID" NUMBER,
"NOME" VARCHAR2(50),
CONSTRAINT "PESSOAS_PK" PRIMARY KEY ("ID") ENABLE
)
/
CREATE TABLE "TELEFONES"
( "ID" NUMBER NOT NULL ENABLE,
"PESSOA_ID" NUMBER NOT NULL ENABLE,
"NUMERO" VARCHAR2(10) NOT NULL ENABLE,
CONSTRAINT "TELEFONES_PK" PRIMARY KEY ("ID") ENABLE,
CONSTRAINT "TELEFONES_FK" FOREIGN KEY ("PESSOA_ID")
REFERENCES "PESSOAS" ("ID") ENABLE
)
/
Rem Nenhum function encontrado para gerar a DDL.
CREATE UNIQUE INDEX "PESSOAS_PK" ON "PESSOAS" ("ID")
/
CREATE UNIQUE INDEX "TELEFONES_PK" ON "TELEFONES" ("ID")
/
Rem Nenhum procedure encontrado para gerar a DDL.
CREATE SEQUENCE "PESSOAS_SEQ" MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 21 CACHE 20 NOORDER NOCYCLE
/
CREATE SEQUENCE "TELEFONES_SEQ" MINVALUE 1 MAXVALUE 999999999999999999999999999 INCREMENT BY 1 START WITH 21 CACHE 20 NOORDER NOCYCLE
/
CREATE OR REPLACE TRIGGER "BI_PESSOAS"
before insert on "PESSOAS"
for each row
begin
select "PESSOAS_SEQ".nextval into :NEW.ID from dual;
end;
/
ALTER TRIGGER "BI_PESSOAS" ENABLE
/
CREATE OR REPLACE TRIGGER "BI_TELEFONES"
before insert on "TELEFONES"
for each row
begin
select "TELEFONES_SEQ".nextval into :NEW.ID from dual;
end;
/
ALTER TRIGGER "BI_TELEFONES" ENABLE
/
view raw repositorio.sql hosted with ❤ by GitHub


Crie uma nova aplicação Console e adicione as seguintes referencias:

* EntityFramework.dll
* Oracle.DataAccess.dll (na pasta de instalação do Client do Oracle)

Veja o código completo da aplicação Console (coloquei todas as classes no mesmo arquivo Program.cs para este post, obviamente costumo separar um arquivo para cada classe nos meus projetos) :

//POST: http://silverlightrush.blogspot.com/2011/11/receita-de-bolo-para-ef-code-first-e.html
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using Oracle.DataAccess.Client;
namespace ConsoleApplication3
{
/// <summary>
/// Classe que retorna a conexão com o banco de dados
/// </summary>
public class OracleConnectionFactory : IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
return new OracleConnection(nameOrConnectionString);
}
}
/// <summary>
/// Classe mapeada para a tabela de Pessoas
/// </summary>
[Table("PESSOAS", Schema = "REPOSITORIO")]
public class Pessoa
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("ID")]
public decimal Id { get; set; }
[Column("NOME"), StringLength(50)]
public string Nome { get; set; }
public virtual ICollection<Telefone> Telefones { get; set; }
public Pessoa()
{
Telefones = new HashSet<Telefone>();
}
}
/// <summary>
/// Classe mapeada para a tabela de Telefones
/// </summary>
[Table("TELEFONES", Schema = "REPOSITORIO")]
public class Telefone
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("ID")]
public decimal Id { get; set; }
[Required, Column("PESSOA_ID")]
public decimal? PessoaId { get; set; }
[ForeignKey("PessoaId")]
public virtual Pessoa Pessoa { get; set; }
[StringLength(10), Required, Column("NUMERO")]
public string Numero { get; set; }
}
/// <summary>
/// Classe que representa o Contexto a ser utilizado para acesso ao banco
/// </summary>
public class MyDbContext : DbContext
{
private const string ConnectionString
= "DATA SOURCE=SERVER_ORACLE; User Id=REPOSITORIO; PASSWORD=repositorio; Persist Security Info=true";
static MyDbContext()
{
Database.DefaultConnectionFactory = new OracleConnectionFactory();
}
public MyDbContext()
: base(ConnectionString)
{
}
public DbSet<Pessoa> Pessoas { get; set; }
public DbSet<Telefone> Telefones { get; set; }
}
/// <summary>
/// Código para a aplicação Console
/// </summary>
class Program
{
static void Main(string[] args)
{
using (var db = new MyDbContext())
{
//adicionar uma pessoa
var pessoa = new Pessoa { Nome = "Jone Polvora" };
db.Pessoas.Add(pessoa);
db.SaveChanges();
//adicionar um telefone para a pessoa
var telefone = new Telefone { Numero = "6712345678", Pessoa = pessoa };
db.Telefones.Add(telefone);
db.SaveChanges();
//imprime a lista de pessoas
foreach (var p in db.Pessoas.ToList())
{
Console.WriteLine(p.Nome);
//imprime os telefones da pessoa atual
foreach (var t in p.Telefones)
{
Console.WriteLine(t.Numero);
}
}
//alterar a pessoa
pessoa.Nome = "Outro nome";
db.SaveChanges();
//remover o telefone da pessoa
db.Telefones.Remove(telefone);
db.SaveChanges();
//remover a pessoa
db.Pessoas.Remove(pessoa);
db.SaveChanges();
Console.WriteLine("Pressione Delete para remover todos os registros");
var key = Console.ReadKey();
if (key.Key == ConsoleKey.Delete)
{
db.Telefones.OrderBy(t => t.Id).ToList().ForEach(t => db.Telefones.Remove(t));
db.Pessoas.OrderBy(p => p.Id).ToList().ForEach(p => db.Pessoas.Remove(p));
db.SaveChanges();
}
}
}
}
}
view raw Program.cs hosted with ❤ by GitHub
Não esquecer de corrigir a string de conexão com o valor correto (geralmente é o nome da estação local).

segunda-feira, 7 de novembro de 2011

A plataforma .NET


Estava pensando aqui sobre a plataforma .NET. É uma plataforma para todos, completa, extensa, robusta, com uma IDE incrível (Visual Studio 2010), com versões express, a maioria do que vou relacionar abaixo são produtos free ou royalty-free, sendo que muitos são OpenSource!

Linguagens criadas para o .NET:

C#
Boo
F#
Visual Basic.NET
Nemerle

Linguagens portadas para .NET:

Programar em Java? IKVM

Programar em Ruby ? IronRuby

Programar em Python? IronPython

Programar p/ Android ? Monodroid

Programar p/ iOS ? Monotouch

Programar em delphi/pascal ? Oxygene

Programar em PHP ? Phalanger.net

Tipos de Aplicativos

Desenvolver para Web ? ASP.NET

Desenvolver para desktop ? WPF, Windows Forms

Desenvolver aplicações RIA? Silverlight

Desenvolver para Cloud ? Azure

Desenvolver WebServices? Windows Communication Foundation


Rodar tudo isso no Linux ? Mono

Existem muitas outras tecnologias envolvendo a plataforma .NET, conforme eu vá encontrando links interessantes vou alterando este post.

terça-feira, 1 de novembro de 2011

Customizando o Repositório - Parte II

Podemos adicionar novas funcionalidades a um determinado repositório e manter a testabilidade. Uma das maneiras de se fazer isso é com o auxílio de uma interface adicional:

Customizando o Repositório - Parte I

Existem alguns métodos que podem ser sobrescritos e customizados com "override" ao extender a classe base do Repositório. Aqui vai um exemplo:

Abaixo segue uns Unit Tests mostrando a utilização do padrão.
Repository Pattern

Utilizando o padrão repositório, as telas e páginas da aplicação que fazem acessos a banco de dados passam a acessar classes intermediárias que atuam como repositórios de dados, tornando a aplicação independente de um banco de dados real. Dessa forma, há uma melhor separação de responsabilidades, onde as telas não possuem lógica de persistência, que passa a ser de responsabilidade do repositório. Isso facilita a troca de banco de dados (utilizar SQL Server, Oracle, MySQL, etc) e também os testes, uma vez que podemos criar repositórios “fake”, trabalhando com listas em memória, além de separar lógica de persistência da lógica de interface com o usuário.

Unit Of Work Pattern

Recentemente refatorei a minha versão de repositório que utilizo em minhas aplicações para dar suporte ao padrão UnitOfWork. Utilizando esse padrão, os repositórios passam a compartilhar entre si um objeto que faz a coordenação das transações dos repositórios a ele anexados em uma única operação Commit/Rollback. Ou seja, ao instanciar os repositórios para uma determinada operação, devo instanciar antes um objeto que implementa IUnitOfWork, e passá-lo na construção desses repositórios. Ao chamar o método Commit() do UnitOfWork, todos os repositórios anexados serão persistidos.


Como foi implementado o repositório?

Após uma refatoração completa, a interface do repositório agora depende de uma interface IUnitOfWork:

using System;
using System.Linq;
namespace Metavision.Infra.Data
{
public interface IUnitOfWork : IDisposable
{
T Find<T>(params object[] keyValues) where T : class , new();
/// <summary>
/// Deve adicionar um registro ao repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
void Add<T>(T entity) where T : class;
/// <summary>
/// Deve remover um registro do repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
void Remove<T>(T entity) where T : class;
/// <summary>
/// Deve retornar um objeto IQueryable do repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IQueryable<T> Get<T>() where T : class;
/// <summary>
/// Deve retornar a quantidade de repositórios que estão anexados ao UnitOfWork atual
/// </summary>
int AttachedRepositories { get; }
/// <summary>
/// Persiste os dados de todos os repositórios anexados ao UnitOfWork atual
/// </summary>
/// <returns></returns>
int Commit();
/// <summary>
/// Deve cancelar todas as operações pendentes
/// </summary>
void RollBack();
}
}
view raw IUnitOfWork.cs hosted with ❤ by GitHub


Com essa interface criada, podemos criar agora a interface IRepositorio:


using System;
using System.Collections.Generic;
namespace Metavision.Infra.Data
{
public interface IRepositorio : IDisposable
{
bool HasChanges { get; set; }
IUnitOfWork UnitOfWork { get; }
}
public interface IRepositorio<T> : IRepositorio where T : class, new()
{
Func<T> Factory { get; }
T CreateNew();
void AddEntity(T entity);
void RemoveEntity(T entity);
T GetEntityByKey(params object[] key);
T GetEntity(Func<T, Boolean> specification = null);
IEnumerable<T> GetAll(Func<T, bool> specification = null);
}
}
view raw IRepositorio.cs hosted with ❤ by GitHub


Uma classe de extensão para alguns métodos úteis aos repositórios que forem implementados:

using System;
using System.Collections.Generic;
using System.Linq;
using Metavision.Infra.Data;
namespace Metavision.Infra.Extensions
{
public static class DataExtensions
{
#region extensoes para Repositório
public static void RemoveMany<T>(this IRepositorio<T> repositorio, Func<T, bool> where) where T : class, new()
{
if (repositorio == null) return;
var results = repositorio.GetAll(where);
foreach (var result in results)
{
repositorio.RemoveEntity(result);
}
}
public static IQueryable<T> GetQueryable<T>(this IRepositorio<T> repositorio) where T : class, new()
{
return repositorio.GetAll().AsQueryable();
}
public static PagedResult<T> GetPagedResult<T>(this IQueryable<T> queryable, int size) where T : class, new()
{
return new PagedResult<T>(queryable, size);
}
internal static void AttachToUnitOfWork(this IRepositorio repositorio)
{
if (repositorio == null) return;
if (repositorio.UnitOfWork is UnitOfWorkBase)
{
((UnitOfWorkBase)repositorio.UnitOfWork).AttachRepository(repositorio);
}
}
#endregion
}
}

Temos o repositório, agora falta a implementação da interface UnitOfWork. Para implementar esta interface, achei conveniente criar uma classe base:
using System;
using System.Collections.Generic;
using System.Data;
namespace Metavision.Infra.Data
{
public abstract class UnitOfWorkBase : IDisposable
{
protected readonly Stack<IRepositorio> StackRepositories
= new Stack<IRepositorio>();
protected IDbTransaction CurrentTransaction;
protected readonly Dictionary<Type, object> Sets
= new Dictionary<Type, object>();
/// <summary>
/// Deve anexar um repositório
/// </summary>
/// <param name="repositorio"></param>
protected internal void AttachRepository(IRepositorio repositorio)
{
StackRepositories.Push(repositorio);
}
protected void SetHasChangesToFalse()
{
foreach (var rep in StackRepositories)
{
rep.HasChanges = false;
}
}
public int AttachedRepositories
{
get { return StackRepositories.Count; }
}
/// <summary>
/// Deve indicar se há uma transação aberta com o banco de dados
/// </summary>
public bool InTransaction
{
get
{
return
CurrentTransaction != null &&
CurrentTransaction.Connection != null &&
CurrentTransaction.Connection.State == ConnectionState.Open;
}
}
/// <summary>
/// Deve iniciar uma transação
/// </summary>
internal abstract void StartTransaction();
#region Implementation of IDisposable
public virtual void Dispose()
{
while (StackRepositories.Count > 0)
{
StackRepositories.Pop().Dispose();
}
if (InTransaction) CurrentTransaction.Dispose();
}
#endregion
}
}

Precisamos da classe Container para nos auxiliar nas implementações de UnitOfWork:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Metavision.Infra
{
/// <summary>
/// Service Locator simples
/// </summary>
public static class Container
{
#region Fields
/// <summary>
/// Assemblies que serão incluídos na pesquisa de tipos por string
/// </summary>
internal static readonly List<Assembly> Assemblies = new List<Assembly>();
/// <summary>
/// Dicionário que guarda instâncias singleton
/// </summary>
private static readonly Dictionary<Type, object> ActiveServices = new Dictionary<Type, object>();
/// <summary>
/// Dicionário que guarda um ponteiro para um método que fabrica um determinado tipo
/// </summary>
private static readonly Dictionary<Type, Func<object>> Factories = new Dictionary<Type, Func<object>>();
/// <summary>
/// objeto utilizado para lock de threads
/// </summary>
private static readonly object LockerObj = new object();
/// <summary>
/// Dicionário que guarda configurações para o Container
/// </summary>
private static readonly Dictionary<Type, Type> ServiceEntries = new Dictionary<Type, Type>();
/// <summary>
/// Dicionário que guarda tipos encontrados por string
/// </summary>
private static readonly Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
#endregion Fields
#if !SILVERLIGHT
static Container()
{
Assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
}
#endif
#region Methods
public static void Initialize()
{
}
public static void DisposeService<TService>()
{
if (ActiveServices.ContainsKey(typeof(TService)))
{
var disposable = ActiveServices[typeof(TService)] as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
ActiveServices[typeof(TService)] = null;
}
public static TServiceOut GetService<TService, TServiceOut>(bool singleton = true, object[] args = null)
where TServiceOut : class
{
return GetService(typeof(TService), singleton, args) as TServiceOut;
}
public static TService GetService<TService>(bool singleton = true, object[] args = null)
{
return (TService)GetService(typeof(TService), singleton, args);
}
public static object GetService(Type serviceType, bool singleton, object[] args = null)
{
//thread safety
lock (LockerObj)
{
if (serviceType == null) return null;
object service = null;
if (singleton && ActiveServices.ContainsKey(serviceType))
{
service = ActiveServices[serviceType];
if (service != null)
return service;
}
try
{
var typeToActivate = ServiceEntries.ContainsKey(serviceType)
? ServiceEntries[serviceType]
: serviceType;
if (!typeToActivate.IsInterface && !typeToActivate.IsAbstract)
{
service = Factories.ContainsKey(serviceType) ? Factories[serviceType]() : Activator.CreateInstance(typeToActivate, args);
}
else if (typeToActivate.IsGenericType)
{
var closedGeneric = ResolveOpenGeneric(typeToActivate);
if (closedGeneric != null)
{
service = Activator.CreateInstance(closedGeneric, args);
}
}
}
finally
{
if (singleton) ActiveServices[serviceType] = service;
}
return service;
}
}
public static void Register<TBase>(Func<TBase> factory = null)
where TBase : class
{
Register<TBase, TBase>(factory);
}
public static void Register<TBase, TConcrete>(Func<TBase> factory = null)
where TBase : class
where TConcrete : class
{
Register(typeof(TBase), typeof(TConcrete), factory as Func<object>);
}
public static void Register(Type tipoBase, Type tipoConcreto, Func<object> factory = null)
{
if (tipoBase == null) throw new ArgumentNullException("tipoBase");
if (tipoConcreto == null)
tipoConcreto = tipoBase;
ServiceEntries[tipoBase] = tipoConcreto;
ActiveServices[tipoBase] = null;
if (factory != null)
Factories[tipoBase] = factory;
}
public static TConcrete RegistraInstancia<TBase, TConcrete>(TConcrete instancia)
{
ServiceEntries[typeof(TBase)] = typeof(TConcrete);
ActiveServices[typeof(TBase)] = instancia;
return instancia;
}
public static TBase RegistraInstancia<TBase>(TBase instancia)
{
ServiceEntries[typeof(TBase)] = instancia.GetType();
ActiveServices[typeof(TBase)] = instancia;
return instancia;
}
public static void Reset()
{
ServiceEntries.Clear();
ActiveServices.Clear();
Factories.Clear();
TypeDictionary.Clear();
}
public static object TryResolve(string typeName, bool singleton)
{
var foundType = SearchType(typeName);
return foundType != null ? GetService(foundType, singleton) : null;
}
public static Type SearchType(string typeName)
{
Type foundType = null;
if (!string.IsNullOrEmpty(typeName))
{
if (TypeDictionary.ContainsKey(typeName))
{
return TypeDictionary[typeName];
}
foundType = Type.GetType(typeName, false) ?? SearchInAssemblies(typeName);
//registra mesmo que não tenha encontrado o tipo
//para da proxima vez não perder tempo
TypeDictionary[typeName] = foundType;
}
return foundType;
}
private static Type SearchInAssemblies(string typeName)
{
if (Assemblies != null)
{
foreach (var assembly in Assemblies)
{
var foundType = assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
if (foundType != null) return foundType;
}
}
return null;
}
private static Type ResolveOpenGeneric(Type type)
{
var newTypeToResolve = type.GetGenericTypeDefinition(); //openGeneric
var extractedType = type.GetGenericArguments(); //parameter
var objetoRegistrado = ServiceEntries.FirstOrDefault(o => o.Key == newTypeToResolve);
if (objetoRegistrado.Key != null)
{
var concreteType = objetoRegistrado.Value.MakeGenericType(extractedType);
return concreteType;
}
return null;
}
#endregion Methods
}
}
view raw Container.cs hosted with ❤ by GitHub


Em seguida todas as implementações para os diversos bancos de dados que desejarmos devem herdar desta classe base, implementando a interface. A seguir a implementação padrão que utiliza Entity Framework CodeFirst, onde no construtor passa-se o DbContext.
using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
namespace Metavision.Infra.Data
{
/// <summary>
/// Implementação de UnitOfWork para EntityFramework (CodeFirst)
/// Basicamente funciona abstraindo as operações do DbContext
/// Permitindo trabalhar com injeção de dependência
/// </summary>
public class CodeFirstUnitOfWork : UnitOfWorkBase, IUnitOfWork, IObjectContextAdapter
{
#region Members
private readonly DbContext _dbContext;
#endregion
#region Ctor
public CodeFirstUnitOfWork(DbContext dbContext = null)
{
_dbContext = dbContext ?? Container.GetService<DbContext>(false);
}
#endregion
#region Implementation of IObjectContextAdapter
public ObjectContext ObjectContext
{
get { return ((IObjectContextAdapter)_dbContext).ObjectContext; }
}
#endregion
#region Implementation of IUnitOfWork
public T Find<T>(params object[] keyValues) where T : class, new()
{
return GetDbSet<T>().Find(keyValues);
}
public void Add<T>(T entity) where T : class
{
GetDbSet<T>().Add(entity);
}
public void Remove<T>(T entity) where T : class
{
GetDbSet<T>().Remove(entity);
}
public IQueryable<T> Get<T>() where T : class
{
return GetDbSet<T>();
}
private DbSet<T> GetDbSet<T>() where T : class
{
DbSet<T> set;
if (Sets.ContainsKey(typeof(T)))
set = (DbSet<T>)Sets[typeof(T)];
else
{
set = _dbContext.Set<T>();
Sets[typeof(T)] = set;
}
return set;
}
internal override void StartTransaction()
{
if (!InTransaction)
{
ObjectContext.Connection.Close();
if (ObjectContext.Connection.State != ConnectionState.Open)
{
ObjectContext.Connection.Open();
CurrentTransaction = ObjectContext.Connection.BeginTransaction();
}
}
}
public int Commit()
{
var hasChanges = StackRepositories.Any(w => w.HasChanges);
if (!hasChanges) return 0;
try
{
StartTransaction();
var errors = _dbContext.GetValidationErrors();
var errorsCount = errors.Count();
if (errorsCount > 0)
{
RollBack();
return errorsCount * -1;
}
var count = _dbContext.SaveChanges();
CurrentTransaction.Commit();
SetHasChangesToFalse();
return count;
}
catch (Exception)
{
RollBack();
return -1;
}
}
public void RollBack()
{
SetHasChangesToFalse();
if (!InTransaction) return;
foreach (var dbEntityEntry in _dbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.State != EntityState.Added)
dbEntityEntry.Reload();
}
CurrentTransaction.Rollback();
}
#endregion
public override void Dispose()
{
base.Dispose();
_dbContext.Dispose();
}
}
}

A seguir a implementação padrão que utiliza Entity Framework tradicional, onde no construtor passa-se o ObjectContext.



Solução

Download da solution Abaixo segue o código demonstrando como utilizar o Entity Framework 4.1 Code First com 2 tabelas (Pessoas e Enderecos), com relacionamento Many-to-Many, e abstraindo o EFramework com o padrão repositório da forma mais simples, sem injeção de dependência.

Mostre código! 

Classe Pessoa


Classe Endereco


Classe de Mapeamento e Configuração do Entity Framework


Método de teste

sábado, 23 de abril de 2011

Teste a performance do .NET em sua máquina

Momento distração.

Recentemente vi um post do Ayende com a seguinte questão: Quantos objetos podem ser criados por segundo? Conversando com os amigos num Pub, houve chutes de 2.000, 5.000 ou 50.000 objetos por segundo. Então ele fez um teste e para a surpresa dele, o resultado foi 7.000.000 de objetos, isso mesmo, 7 milhões de objetos criados em apenas um segundo com uma aplicação Console.

Fiz o mesmo teste e meu resultado foi melhor: 14.106.434, mais de 14 milhões de objetos por segundo.

Faça o teste em sua máquina, crie uma aplicação console e cole esse código:



    public class MyClass
    {
        public string A;
        public int B;
        public DateTime C;
    }

    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Start();
                var k = Console.ReadKey();
                if (k.Key != ConsoleKey.Enter) break;
            }
        }

        static void Start()
        {
            var sp = System.Diagnostics.Stopwatch.StartNew();
            int i = 0;
            while (sp.ElapsedMilliseconds < 1000)
            {
                new MyClass();
                i++;
            }
            sp.Stop();
            Console.WriteLine("Create {0} in {1}", i, sp.Elapsed);
        }
    }


Faça o teste e comente aqui seu resultado.

Padrão Façade para trabalhar com Sessions em uma aplicação ASP.NET

Trabalhar com strings hard-coded é algo que pode nos levar a introduzir bugs por falta de atenção e erros de digitação.

É algo que pode ocorrer facilmente quando trabalhamos com Sessions dentro de uma aplicação Asp.NET. Para evitar isso, recomenda-se criar constantes e utilizá-las para gravar ou recuperar os valores armazenados nas Sessions.

Uma alternativa interessante é utilizar uma classe Facade para acessar e gravar os valores das Sessions.

Fiz uma classe chamada SessionVars para isso.

public static class SessionVars
    {
        /*constantes para as Sessions*/
        public const string IdUsuario = "IdUsuario";        

        /// 
        /// Verifica se a Contexto da Session é válido
        /// 
        private static bool IsSessionValid
        {
            get { return HttpContext.Current != null && HttpContext.Current.Session != null; }
        }

        /// 
        /// Atalho para a Session
        /// 
        private static HttpSessionState Session
        {
            get { return HttpContext.Current.Session; }
        }

        #region UsuarioId
        public static int? UsuarioId
        {
            get
            {
                if (IsSessionValid)
                {
                    if (Session[IdUsuario] != null)
                    {
                        int idUsuario;
                        if (Int32.TryParse(Session[IdUsuario].ToString(), out idUsuario))
                            return idUsuario;

                        return int.MinValue;
                    }
                }
                return null;
            }
            set
            {
                if (IsSessionValid)
                {
                    Session[IdUsuario] = value;
                }
            }
        }
        #endregion
    }


Em qualquer página aspx.cs basta acessar a classe estática e a propriedade desejada para ler o valor ou gravar.

if (SessionVars.UsuarioId != null)
{                //
SessionVars.UsuarioId = 10;
}


Crie todas as constantes para os nomes das sessions e a propriedade com Get/Set específica.

sábado, 16 de abril de 2011

Porquê Silverlight?

Prefácio

Perfil de uma aplicação Desktop
• Executa diretamente no sistema operacional (Delphi/C++/C).
• Ou através de bytecodes interpretados por uma máquina virtual que faz a compilação JIT (Just-In-Time) em tempo de execução (Java/.NET).
• Em ambas situações, toda a camada de apresentação é criada pela aplicação, que busca os dados em um banco de dados local ou em rede.

Perfil de uma aplicação Web
• Utiliza um software do tipo browser que é executado na estação do cliente que envia e recebe requisições http Get/Post a um servidor WEB http.
• O Servidor recebe as requisições e retorna conteúdo (response) que é interpretado pelo navegador que por sua vez cria componentes visuais de acordo com as intruções recebidas.

Perfil de uma aplicação RIA
• Une o melhor dos dois mundos: Executa na estação cliente e distribuíção é através da Web, com atualizações automáticas (ao carregar a página).
• Utiliza um navegador web que atua como um contêiner para a aplicação.
• A executada da aplicação é controlada por um plugin (Flash, Adobe AIR, Java, Silverlight).
• O plugin é executado no sistema operacional.


Experiências

Delphi

Antigamente eu desenvolvia aplicações utilizando Delphi. Fiz sistemas comerciais para desktop, gerando executáveis nativos para a plataforma Windows, acessando banco de dados Interbase/Firebird, Oracle e SQL Server.

• Prós: ótima IDE, facilidade e rapidez para criar uma aplicação, arrastando componentes visuais e configurando suas propriedades. Cumpre o prometido: RAD - rapid application development.

• Contras: As aplicações têm de ser distribuídas e instaladas diretamente nas estações dos clientes, gerando um trabalho extra e dor de cabeça quando era necessário atualizar versões.

Após a versão 8 do Delphi, muitos programadores passaram a desacreditar no futuro da linguagem, com a Borland vendendo o produto para outras empresas, gerando dúvidas e incertezas. Então os desenvolvedores mais prevenidos passaram a estudar uma outra linguagem, como VB.NET, ou migrar para o mundo WEB, como eu fiz.

PHP

Com a popularização da internet, surgiu uma demanda para criação de aplicações específicas para a Web, e com isso, a necessidade de aprender uma linguagem dinâmica. Dominar Html, CSS e Javascript se tornou essencial. O PHP então era a linguagem mais popular para aplicações web e tinha uma ampla comunidade de desenvolvedores.

• Prós: Ampla comunidade, muitos exemplos na internet e muito material disponível.

• Contras: Na minha experiência com PHP, percebi que não havia um padrão a se seguir, o PHP permite criar aplicações mal codificadas, mal escritas, código “macarrão” (html e código php misturado), e por si só não incentiva o programador a aprender a fundo a linguagem, não exige orientação a objetos nem separação de responsabilidades. É uma linguagem de script, interpretada a cada requisição. Para ter algum padrão, é necessário utilizar um framework. Caso contrário, basta copiar e colar qualquer exemplo de código já existente pela internet simplesmente porque alguém já fez e funcionou.

ASP.NET

Essa percepção “negativa” que tenho hoje do PHP aumentou depois que conheci a plataforma .NET, mais especificamente o ASP.NET, a plataforma da Microsoft para a desenvolvimento de aplicações do tipo “WebForms”. As aplicações WebForms abstraem o conceito de programação para a Web onde cada página é uma tela com componentes visuais, similar a um aplicativo desktop, mas renderizando obviamente conteúdo HTML.

• Prós: Separação de código HTML do código da aplicação, linguagem tipada, estática e compilada, não interpretada a cada requisição.

• Contras: O resultado final ainda é a renderização de HTML,CSS e Javascript que será interpretado no navegador, da mesma forma que qualquer linguagem para Web. Para manter o estado dos webforms e seus controles entre as requisições, simulando uma aplicação desktop), cada webform gera um overhead adicional, utilizando artifícios como Sessions, ViewStates, etc.


A web não foi feita para aplicações

A Web inicialmente não previa a execução de aplicações, como no desktop. Na verdade ela previa apenas a apresentação de conteúdo. Com o tempo, a web foi se adaptadando para simular aplicações. O protocolo HTTP evoluiu, depois surgiu o javascript, a seguir os browsers adicionaram suporte a javascript e plugins, etc. E o que temos hoje é uma mistura de várias linguagens e tecnologias trabalhando em conjunto para fornecer ao usuário um ambiente próximo a um aplicativo desktop. O resultado disso é que cada navegador implementa de maneira diferente essas tecnologias, obrigando o desenvolvedor a prever qual browser o usuário está utilizando, e enviar HTML/CSS/JAVASCRIPT específico para tal navegador (Chrome/IE/Firefox/etc).


Com a Web, sua estação de trabalho é um terminal burro

Vamos resumir o ciclo de vida de uma aplicação Web:

1 – Abrir seu navegador preferido.
2 – Informar a URL do site que contém a aplicação que desejada.
3 – O navegador resolve o endereço IP e encontra o servidor web onde a aplicação está hospedada e envia uma requisição do tipo HTTP/Get.
4 – O servidor web, independente da linguagem em que a aplicação foi feita, vai processar a requisição, executar as instruções necessárias e retornar ao navegador um “Response” contendo o HTML gerado pelo servidor.
5 – O navegador recebe a resposta, e partir daí começa a executar todas as instruções que o código HTML determina. Provavelmente isso vai demandar novas requisições para buscar imagens e outros “includes” como javascript/css, num ciclo interno até que finalmente a página possa ser renderizada no navegador.
6 – Esta página que está sendo exibida, pode conter links para outras páginas, nesse caso, ao clicar num link, inicia-se novamente a partir do item 3.
7 – Esta pagina pode conter um formulário que irá submeter dados ao servidor. Nesse caso, o navegador enviará os dados do formulário para o servidor, através de uma requisição do tipo HTTP/POST. O servidor web receberá esse formulário, a aplicação irá tratar as informações, executando as intruções necessárias e devolverá uma resposta novamente ao navegador. Voltamos então a partir do item 4.

Assim, vemos que uma aplicação web se resume a um navegador que envia requisições do tipo GET/POST, em que todas as respostas geradas pelo servidor web incluem novamente todo o conteúdo necessário para se renderizar uma página com imagens, textos a serem exibidos, código javascript, código CSS para formatação de estilos, etc.

O tráfego de informações que é gerado entre uma requisição e outra não se resume somente aos dados utilizados pela aplicação. Por exemplo, se você solicitar 10 vezes a mesma página ao navegador, todo o ciclo será repetido, e nas 10 vezes você receberá todo o mesmo conteúdo novamente.

Por isso costumo entender que de certa maneira, seu computador quando navega na internet é praticamente um terminal burro conectado a um grande mainframe, em que o mainframe processa toda a informação e envia instruções para o cliente exibir as páginas.

Silverlight – Rich Internet Application

Desenvolver com Silverlight é como desenvolver para desktop, utilizando processamento local para formatar telas através de instruções compiladas, com a facilidade de distribuição e atualização que a web oferece. E ainda com a vantagem de reaproveitar todo seu conhecimento em .NET, sem se preocupar com HTML, Javascript e CSS.

Uma aplicação Silverlight incialmente é um objeto carregado dentro de uma página Html. Após esse objeto ser carregado, a aplicação é gerenciada pelo runtime do Silverlight, dentro de uma área isolada e protegida conhecida como sandbox. Nesse ambiente, o silverlight por padrão faz requisições e recebe as respostas através de sua própria implementação de cliente http. Todo o conteúdo necessário para exibir e renderizar as telas e controles não precisam ser carregados ou interpretados novamente, tudo o que a aplicação precisa já está no objeto que foi carregado dentro do navegador. A comunicação com servidores Web se resume apenas a troca de dados, e não mais formatação de telas e regras de negócios.
Portanto, com o Silverlight, você programa praticamente como se programasse para desktop, utilizando uma única linguagem (no meu caso, C#), sendo que a distribuição da aplicação é direto pela Web, unindo o útil ao agradável.

domingo, 27 de março de 2011

Classe base para ViewModels - Padrão MVVM - Silverlight

Para facilitar a utilização do padrão MVVM, devemos utilizar uma classe base para as viewmodels. Segue abaixo o código que uso para minha classe ViewModelBase (http://pastebin.com/h5WD4V6q):



Toda vez que alteramos um valor de uma propriedade ou comando em uma viewmodel, devemos disparar um método NotifyOfPropertyChanged (muitos frameworks chamam de RaiseProperyChanged ou nomes parecidos).

Isso serve para notificar à view que o valor da propriedade foi alterado e que o componente visual relacionado àquela propriedade deve ter seu conteúdo atualizado (Binding Mode=OneWay/TwoWay).

Uma classe simples que implementa essa classe base ficaria assim:



Código comentado

sábado, 19 de março de 2011

Continuando o artigo anterior – Utilizando MEF com Silverlight na prática

No artigo anterior, criamos dois novos UserControl’s com atributos [Export] e mostrei como visualizá-los dentro da MainPage usando MEF.

Uma vez que o MEF criou uma instância de uma classe que foi exportada, toda vez que solicitarmos novamente ao catálogo essa mesma classe, será retornada a mesma instância.

Para deixar isso bem claro, coloquei um textblock que informa a data e hora em que o MEF criou o objeto. No caso de precisarmos atualizar essa data sem criar uma nova instância, teremos que simular um evento que indica quando aquele usercontrol foi solicitado novamente ao Catálogo.

Uma solução para isso é criarmos uma interface que chama um método para realizar essa tarefa de atualizar a data/hora do textblock. Mãos à obra!

Vamos criar duas interfaces: uma interface chamada IMainPage que conterá um método MostrarTela(string contrato), e outra interface chamada ITela que conterá um método Activate().

Crie uma pasta chamada “Contratos” dentro do projeto silverlight e adicione essas duas interfaces da seguinte maneira:

//interface IMainPage
namespace SilverlightApplication1.contracts
{
    public interface IMainPage
    {
        void MostrarTela(string contrato);
    }
}

//interface ITela
namespace SilverlightApplication1.contracts
{
    public interface ITela
    {
        void Activate();
    }
}


Abra o arquivo MainPage.xaml.cs e implemente a interface IMainPage. Os eventos do Click dos botões também devem ser alterados para chamar o método MostrarTela passando a string do contrato desejado como parâmetro. O conteúdo da classe inteira ficará dessa forma:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl, IMainPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Tela1_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela1");
    }

    private void Tela2_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela2");
    }

    #region Implementation of IMainPage
    public void MostrarTela(string contrato)
    {
        ITela tela = CatalogoMef.GetInstance<UserControl>(contrato) as ITela;
        if (tela != null)
        {
            mainPageContentControl.Content = tela;
            tela.Activate();
        }
    }
    #endregion
}

Repare na implementação do método MostrarTela que chamamos a tela com o contrato solicitado através do catálogo do MEF. Se o MEF encontrou a tela, a variável “tela” não será nula, então setamos o conteúdo do controle MainPageContentControl para o valor retornado pelo MEF e em seguida, chamamos o método Activate() da interface ITela. Esse método será implementado nos UserControl’s Tela1.xaml.cs e Tela2.xaml.cs. Veja como deve ficar:

[Export("Tela1", typeof(UserControl))]
public partial class Tela1 : UserControl, ITela
{
    public Tela1()
    {
        InitializeComponent();
    }

    #region Implementation of ITela

    public void Activate()
    {
        dataHoraCriacao.Text = DateTime.Now.ToString();
    }

    #endregion
}

Faça a mesma alteração no arquivo Tela2.xaml.cs:

[Export("Tela2", typeof(UserControl))]
public partial class Tela2 : UserControl, ITela
{
    public Tela2()
    {
        InitializeComponent();
    }

    #region Implementation of ITela

    public void Activate()
    {
        dataHoraCriacao.Text = DateTime.Now.ToString();
    }

    #endregion
}

Dessa forma, ao implementarmos a interface ITela que criamos na pasta “Contratos”, somos obrigados a implementar o método Activate. Nesse método Activate, será feita a atualização da Data e Hora do textblock.

Execute a aplicação e você verá que agora a data/hora sempre estarão atualizadas.

Neste artigo podemos concluir que através do auxílio de interfaces, podemos controlar e manter o estado das telas que foram instanciadas.

Para finalizar vamos implementar um método DeActivate() que será chamado quando a tela ficar invisível.
Para isto, altere a interface ITela e adicione um método DeActivate() da mesma forma como declarado o método Activate():

//interface ITela
namespace SilverlightApplication1.contracts
{
    public interface ITela
    {
        //método chamado quando a tela ficar vísivel
        void Activate();

        //método chamado quando a tela ficar invisível
        void DeActivate();
    }
}

Como adicionamos um método novo na interface ITela, agora precisamos implementar esse método na Tela1 e Tela2:

public void DeActivate()
{
    dataHoraCriacao.Text = "";
}

Pra concluir essa funcionalidade, vamos adicionar uma propriedade na interface IMainPage, que será uma referência à tela que está Ativa no momento:

//interface IMainPage
namespace SilverlightApplication1.contracts
{
    public interface IMainPage
    {
        //propriedade que vai guardar a referência à tela que estiver ativa
        ITela TelaAtiva { get; set; }
        void MostrarTela(string contrato);
    }
}

Em MainPage.xaml.cs, a classe toda ficará dessa maneira:

[Export("MainPageExport", typeof(UserControl))]
public partial class MainPage : UserControl, IMainPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void Tela1_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela1");
    }

    private void Tela2_Click(object sender, RoutedEventArgs e)
    {
        MostrarTela("Tela2");
    }

    #region Implementation of IMainPage

    public ITela TelaAtiva { get; set; }

    public void MostrarTela(string contrato)
    {
        if (TelaAtiva != null) TelaAtiva.DeActivate();
        TelaAtiva = CatalogoMef.GetInstance<UserControl>(contrato) as ITela;
        if (TelaAtiva == null) return;
        mainPageContentControl.Content = TelaAtiva;
        TelaAtiva.Activate();
    }
    #endregion
}

Toda vez que uma nova tela for chamada, a tela ativa atual irá executar o método DeActivate(), nesse método poderemos fazer o que acharmos necessário quando uma tela ficar invisível. Em seguida, a nova tela passa a ser ativa e então o método Activate() é chamado.


Código fonte do projeto aqui