domingo, 1 de maio de 2011

Acesso ao contexto em linha

Para quem utiliza Entity Framework, o acesso aos dados é fornecido pelo contexto (gerado pelo Entity Data Model Designer). Ora, o contexto gerado pela ferramenta deriva de System.Data.Objects.ObjectContext que por sua vez implementa IDisposable. Dizem as boas práticas que um objecto que seja IDisposable deve ser usado da seguinte forma:

        public IEnumerable<Product> Products { get; set; }
        void Sample1()
        {
            using (var ctx = new NorthwindEntities())
            {
                Products = ctx.Products.Where(p => p.UnitPrice < 10);
            }
        }

Simples e eficaz. Mas, agora imaginemos que quero carregar a lista de produtos num outro objecto e até a posso colocar no construtor ou na inicialização directa:

        class SomeObject { public IEnumerable<Product> Products { get; set; } }

                                                                                               
        void Sample2()
        {
            using (var ctx = new NorthwindEntities())
            {
                SomeObject someObject1 = new SomeObject()
                {
                    Products = ctx.Products.Where(p => p.UnitPrice < 10)
                };
            }
            //someObject1 not available here...
        }

Então temos um problema: é que a utilização do objecto criado fica restrita ao corpo da estrutura using. É certo que se pode declarar o objecto fora da estrutura ou simplesmente expandir a estrutura, mas isso nem sempre é desejável. Para contornar esta situação costumo utilizar um pequeno método inspirado na inversão de controlo, assim:

    public partial class NorthwindEntities
    {
        public static T Execute<T>(Func<NorthwindEntities, T> f)
        {
            using (var ctx = new NorthwindEntities())
            {
                return f(ctx);
            }
        }
    }

Neste caso, coloquei o método na própria classe de contexto, mas pode ser colocado numa qualquer classe estática. A razão de o ter colocado directamente no contexto é que é dependente do próprio contexto devido à inicialização local do mesmo. Com esta pequena ajuda, a mesma consulta pode ser escrita assim:

        void Sample3()
        {
            Products = NorthwindEntities.Execute(ctx => ctx.Products.Where(p => p.UnitPrice < 10));
        }

Ou assim:

        void Sample4()
        {
            SomeObject someObject1 = new SomeObject()
            {
                Products = NorthwindEntities.Execute(ctx => ctx.Products.Where(p => p.UnitPrice < 10))
            };
            //someObject1 available here...
        }

Claro que este exemplo apenas tem utilização quando o contexto apenas é necessário para uma única consulta. Para mais do que uma consulta deverá sempre ser utilizado o padrão standard.