.NET - O padrão Unit of Work (EF 4.1)


Neste artigo abordarei os conceitos relacionados com o padrão de projeto Unit Of Work e sua implementação com o Entity Framework 4.1.

De acordo com Martin Fowler, que definiu o padrão no seu livro - Patterns of Enterprise Application Architecture , o padrão Unit Of Work (unidade de trabalho) : "mantém uma lista de objetos afetados por uma transação comercial e coordena a gravação de alterações e a resolução de problemas de concorrência." (numa tradução livre by Macoratti)

Para poder reproduzir o código usado neste artigo é recomendável que você utilize o Visual Studio 2010 ou o Visual Studio 2008.

O que é o padrão Unit of Work ?

O padrão Unit of Work esta presente em quase todas as ferramentas OR/M atuais (digo quase pois não conheço todas) e geralmente você não terá que criar a sua implementação personalizada a menos que decida realmente fazer isso por uma questão de força maior.

Dessa forma a interface ITransaction no NHibernate, a classe DataContext no LINQ to SQL e a classe ObjectContext no Entity Framwork são exemplos de implementações do padrão Unit of Work. (Até o famigerado DataSet  pode ser usado como uma Unit of Work.)

Então o padrão Unit of Work pode ser visto como um contexto, sessão ou objeto que acompanha as alterações das entidades de negócio durante uma transação sendo também responsável pelo gerenciamento dos problemas de concorrência que podem ocorrer oriundos dessa transação.

Como usar o padrão Unit of Work ?

Uma das melhores maneiras de usar o padrão Unit of Work é permitir que classes e serviços diferentes façam parte em uma única transação lógica sem se conhecerem mutuamente.

Em uma das implementações do padrão repositório podemos começar definindo uma interface (da mesma forma que no padrão repository) conforme mostrado abaixo:

public interface IUnitOfWork<T>

{

  void RegisterNew(T entity);

  void RegisterDirty(T entity);

  void RegisterClean(T entity);

  void RegisterDeleted(T entity);

  void Commit();

}

Public Interface IUnitOfWork(Of T)

   Sub RegisterNew(entity As T)

   Sub RegisterDirty(entity As T)

   Sub RegisterClean(entity As T)

   Sub RegisterDeleted(entity As T)

   Sub Commit()

End Interface

C#

VB .NET

Observe que no exemplo estamos registrando as alterações das entidades dentro da unidade de trabalho (unit of work) e no final de cada transação efetivamos a gravação usando o método Commit.

Como eu já mencionei em quase todas as ferramentas OR/M disponíveis atualmente temos esta funcionalidade definida dentro da sessão ou contexto dos objetos/entidades.

È a unidade de trabalho que gerencia os detalhes de cada alteração que realizamos sobre uma entidade. No exemplo foram definidos quatro métodos para tratamento dos registros além do método Commit mas você pode utilizar uma interface mais simples conforme vemos abaixo:

public interface IUnitOfWork<T>

{

  void RegisterNew(T entity);

  void Commit();

}

Public Interface IUnitOfWork(Of T)

   Sub RegisterNew(entity As T)

   Sub Commit()

End Interface

C# VB .NET

Você também pode optar por implementar a funcionalidade para desfazer as alterações feitas antes de efetivá-las definindo um método Rollback .(Ao usar Entity Framework, a abordagem recomendada para desfazer é a de descartar o seu contexto com as mudanças que você está interessado em desfazer.)

Como o padrão Repository e o padrão Unit Of Work caminham lado a lado eu vou usar o exemplo do repositório definido no artigo: .NET - Apresentando o padrão de projeto Repository

Abaixo vemos o código da implementação do padrão Repository a classe ClienteRepository que eu simplifiquei mais ainda removendo os métodos Save, Delete e Query para tornar mais claro o exemplo:

public class ClienteRepository : IRepository<Cliente>, IDisposable

{

private _entidades _context;
 

 public ClienteRepository()

 {

  _context = new _entidades();

 }

 
 public
Cliente GetById(int
id)

 {

   return _context.Clientes.

   Where(s => s.ClienteID == id).

   FirstOrDefault();

 }


 
public Cliente[] GetAll()

 {

   return _context.Clientes.ToArray();

 }


 
public void Dispose()

 {

   if (_context != null)

   {

     _context.Dispose();

   }

   GC.SuppressFinalize(this);

 }

}


 

Public Class ClienteRepository

   Implements IRepository(Of Cliente)

   Implements IDisposable


Private
_context As _entidades


Public
Sub New
()

_context = New _entidades()

End Sub


Public Function GetAll() As Cliente() Implements IRepository(Of Cliente).GetAll

  Return _context.Clientes.ToArray()

End Function


Public
Function GetById(id As Integer) As Cliente Implements IRepository(Of Cliente).GetById

   Return _context.Clientes.Where(Function(s) s.ClienteID = id).FirstOrDefault()

End Function


Public
Sub Dispose() Implements System.IDisposable.Dispose

  If _context IsNot Nothing Then

   _context.Dispose()

  End If

  GC.SuppressFinalize(Me)

End Sub
 

End Class


 

Vamos transformar a classe ClienteRepository em uma unidade de trabalho para gerenciar clientes incluindo na classe o seguinte trecho de código:

public void RegisterNew(Cliente entity)

{

   _context.AddToClientes(entity);

}


public
void Commit()

{

   _context.SaveChanges();

}

Public Sub RegisterNew(entity As Cliente)

   _context.AddToClientes(entity)

End Sub
 

Public Sub Commit()

   _context.SaveChanges()

End Sub

C#

VB .NET

O código completo da implementação ficaria assim:

public class ClienteRepository : IRepository<Cliente>,IUnitOfWork<Cliente>, IDisposable

{

private _entidades _context;

public ClienteRepository()

{

   _context = new _entidades();

}

public Cliente GetById(int id)

{

   return _context.Clientes.

   Where(s => s.ClienteID == id).

   FirstOrDefault();

}

public Cliente[] GetAll()

{

   return _context.Clientes.ToArray();

}

public void RegisterNew(Cliente entity)

{

   _context.AddToClientes(entity);

}

public void Commit()

{

   _context.SaveChanges();

}

public void Dispose()

{

   if (_context != null)

  {

     _context.Dispose();

  }

   GC.SuppressFinalize(this);

}

}

Public Class ClienteRepository

Implements IRepository(Of Cliente)

Implements IUnitOfWork(Of Cliente)

Implements IDisposable
 

Private _context As _entidades


Public
Sub New()

   _context = New _entidades()

End Sub
 

Public Sub Dispose1() Implements System.IDisposable.Dispose

   If _context IsNot Nothing Then

      _context.Dispose()

   End If

   GC.SuppressFinalize(Me)

End Sub

 

Public Function GetById(id As Integer) As Cliente Implements IRepository(Of Cliente).GetById

   Return _context.Clientes.Where(Function(s) s.ClienteID = id).FirstOrDefault()

End Function
 

Public Function GetAll(id As Integer) As Cliente Implements IRepository(Of Cliente).GetAll

   Return _context.Clientes.ToArray()

End Function
 

Public Sub RegisterNew(entity As Cliente) Implements IUnitOfWork(Of Cliente).RegisterNew

   _context.AddToClientes(entity)

End Sub
 

Public Sub Commit() Implements IUnitOfWork(Of Cliente).Commit

   _context.SaveChanges()

End Sub

End Class

C#

VB .NET

Lições aprendidas

Seguindo a definição e  orientações de Martin Fowler definimos uma interface bem simples para nossa unidade de trabalho e usando o Entity Framework fizemos nossa própria implementação gerenciando apenas o registro de novas entidades a sua persistência.

Existem implementações bem mais complexas e mais robustas do que foi mostrado aqui. O nosso exemplo serviu apenas para introduzir o assunto.

Aguarde em breve novos artigos sobre o padrão Unit of Work.

"Eu sou o Alfa e o Ômega, o princípio e o fim, o primeiro e o derradeiro.  Bem-aventurados aqueles que lavam as suas vestiduras no sangue do Cordeiro, para que tenha direito à árvore da vida, e possam entrar na cidade pelas portas." Apocalipse 22:13-14

Referências:


José Carlos Macoratti