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