Utilizando o EF em Ambiente Parcialmente Confiável

Estava utilizando a template de projeto Web Site para a criação de um site com uma vida curta, e que se destina a realizar uma tarefa temporária. Como ele deve acessar uma fonte de dados SQL Server para manipular alguns dados, optei por utilizar o Entity Framework como forma de acesso, ao invés do LINQ To SQL ou até mesmo do ADO.NET.

Em pouco tempo essa aplicação ficou pronta e tudo funcionava tranquilamente na máquina de desenvolvimento, até que decidi – erroneamente – configurar o Web.Config localmente. Entre essas configurações, uma delas foi ajustar o nível de segurança que a aplicação deverá rodar, que – teoricamente – não preciso nada mais do que o nível “Medium”. Sendo assim, o meu arquivo Web.Config passou a ficar com a seguinte entrada:

<trust level=”Medium” />

Ao tentar recompilar a aplicação no Visual Studio .NET, me deparo com o seguinte erro listado na IDE:

Type ‘System.Data.Entity.EntityDesignerBuildProvider’ cannot be instantiated under a partially trusted security policy (AllowPartiallyTrustedCallersAttribute is not present on the target assembly).

Ao abrir o arquivo Web.Config da aplicação, você notará que no elemento connectionStrings possuirá as referências para os arquivos que possuem as descrições das entidades e mapeamentos (CSDL, MSL e SSDL), acrescentado o prefixo “res://”, que indica que eles serão adicionados ao assembly como Resource.

Além disso, você verá que existe um builder provider do tipo EntityDesignerBuildProvider, vinculado à aplicação. Essa classe é responsável por extrair as informações dos arquivos mencionados acima, e modificar os assemblies que estão sendo gerados, embutindo-as como Resources. Esse processo não pode ser executado em ambiente parcialmente confiável, já que a permissão necessária não será concedida à aplicação. Veja que a mensagem de erro informa que o Assembly onde está declarado este builder provider, não pode ser chamado por aplicações parcialmente confiáveis, pois a ausência do atributo AllowPartiallyTrustedCallersAttribute evita isso.

A transformação do arquivo EDMX (CSDL, MSL e SSDL) ocorre somente na primeira vez que se compila a aplicação (%windir%Microsoft.NETFrameworkVersaoTemporary ASP.NET Files), e se nesse momento estiver com ela configurada como parcialmente confiável, assim como eu fiz acima, você irá obter o erro em questão. Se você optar por pré-compilar a aplicação em ambiente “FullTrust”, e fazer o deployment em ambiente parcialmente confiável, você não terá este problema. Isso é perfeitamente possível, já que as configurações de uma aplicação (Web.Config) não são compiladas.

Uma outra alternativa é utilizar o conceito de sandboxing, onde você isola o EDMX em uma Class Library, e faz referencia para ela no projeto Web. Como o arquivo EDMX estará embutido na DLL gerada, você não precisa mais das referências aos arquivos CSDL, MSL e SSDL na aplicação Web, e muito menos do build provider EntityDesignerBuildProvider no Web.Config. Neste caso, o ponto negativo é ter que gerenciar dois projetos e Assemblies.

Ainda há uma última alternativa neste caso, onde você extrai os arquivos CSDL, MSL e SSDL a partir do EDMX, e modifica a connectionString para apontar fisicamente para estes arquivos, que devem estar na mesma aplicação (talvez no diretório bin). Particularmente prefiro a opção da geração do Assembly a parte, que facilita a reutilização por várias aplicações e não corremos o risco de alguém, acidentalmente, excluir estes arquivos que são necessários para o Entity Framework trabalhar.

Uma alternativa aos cursores

Uma funcionalidade que existe no SQL Server é a capacidade de iterar pelos resultados de uma consulta através de cursores. Não sou especialista em SQL Server, mas sei que a sua utilização degrada consideravelmente a performance. Talvez se utilizá-lo em uma quantidade pequena de informações, ele pode executar bem, mas não sei ao o impacto que isso causa, devido aos recursos do sistema que ele utiliza para fazer funcionar.

Como havia a necessidade de melhorar o resultado de um relatório extremamente complexo, e as alternativas em T-SQL já estavam esgotadas, a solução foi recriar a Stored Procedure utilizando o .NET. Sustituimos os cursores por SqlDataReaders, e a diferença foi bastante significativa. E ainda nem precisei abrir mão da segurança, já que o Assembly com a Stored Procedure gerenciada, foi catalogado com o nível de segurança definido como Safe.

Queries compiladas

Em várias aplicações é muito comum você ter que executar uma determinada query diversas vezes. Essas queries são utilizadas por diversos pontos do sistema e por diversos usuários. Quando estamos utilizando o LINQ To SQL ou o Entity Framework, a cada query que fazemos em C#/VB.NET, há uma série de procedimentos internos que são realizados para transformar/traduzir a query em T-SQL (ASTs).

Para evitar que esses procedimentos aconteçam a todo o momento, podemos recorrer a uma classe chamada CompiledQuery. Basicamente, a finalidade desta classe é otimizar a execução, que apenas efetua todos os procedimentos uma única vez, e faz o caching do “plano de execução” (em termos de entidades). E para fazer tudo isso funcionar, basta utilizar o método Compile, que esta classe disponibiliza. Esse método retorna um delegate do tipo Func<,,>, representando a query compilada, que por sua vez, especificará os tipos dos parâmetros necessários para ela, tais como, a fonte de dados (DataContext ou ObjectContext), os parâmetros de entrada e o resultado.

Vale lembrar que essa classe não é comum para ambas as tecnologias, ou seja, há uma versão dela para o Linq To SQL e outra para o Entity Framework, contidas nos namespaces System.Data.Linq e System.Data.Objects, respectivamente. O exemplo abaixo ilustra uma classe chamada DBQueries, que possui um membro estático chamado AreasDeUmaEmpresa, que representa uma query frequentemente utilizada em um determinada aplicação, e que faz uso do Linq To SQL como acesso a dados:

using System.Linq;
using System.Data.Linq;

internal static class DBQueries
{
    public static Func<DBContext, int, IEnumerable<Area>> AreasDeUmaEmpresa;

    static DBQueries()
    {
        AreasDeUmaEmpresa =
            CompiledQuery.Compile<DBContext, int, IEnumerable<Area>>(
                (db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
    }
}

Abaixo está a mesma query, mas com uma ligeira modificação (não muito vísivel) para que funcione com o Entity Framework:

using System.Linq;
using System.Data.Objects;

internal static class DBQueries
{
    public static Func<DBEntities, int, IEnumerable<Area>> AreasDeUmaEmpresa;

    static DBQueries()
    {
        AreasDeUmaEmpresa =
            CompiledQuery.Compile<DBEntities, int, IEnumerable<Area>>(
                (db, empresaId) => from a in db.Areas where a.EmpresaID == empresaId select a);
    }
}

Como já sabemos, apesar de terem o mesmo nome, as classes CompiledQuery são tipos diferentes. O método Compile de cada uma delas possui uma constraint para o primeiro tipo genérico (TArg0), que identifica a fonte de dados, ou melhor, o contexto. No caso do LINQ To SQL, ele nos obrigará a definir esse primeiro parâmetro com um tipo que herde direta ou indiretamente da classe DataContext, enquanto a classe CompiledQuery utilizada pelo Entity Framework, determina que este mesmo parâmetro deve ser uma classe do tipo ObjectContext.

Em ambos os casos, armazenamos o resultado gerado pelo método Compile das respectivas classes CompiledQuery, em campos estáticos. Esse campo foi inicializado no construtor (também estático), mas talvez você possa fazer isso sob demanda. A idéia é criar um membro estático para armazenar cada query compilada, mantendo o resultado durante a execução da aplicação, reutilizando-a quando necessário.

Abaixo consta um exemplo de utilização da query compilada com LINQ To SQL. O Entity Framework seguirá a mesma idéia, apenas criando o contexto correspondente.

using (DBContext db = new DBContext())
    foreach (var item in DBQueries.AreasDeUmaEmpresa(db, 12)) { }

Queries compiladas tem uma performance consideravelmente melhor em relação as queries não compiladas, e usá-las poderá trazer bons ganhos de performance, podendo inclusive, utilizá-la em conjunto com o ConnectedDataContext. Mas também há um ponto negativo: você não conseguirá, de forma simples, projetar possíveis tipos anônimos que possam ser gerados através do select. Como tipos anônimos não podem ser propagados entre chamadas de métodos, então a única opção que nos resta é a criação de uma classe para armazenar e servir como wrapper para esse resultado.

Serviços CRUD

Muitas pessoas utilizam ou já utilizaram o WCF (ou até mesmo os antigos ASP.NET Web Services (ASMX)), para servir como um wrapper de uma base de dados. Basicamente era criado um serviço para cada entidade desta base, onde cada um deles apenas define em sua interface as operações de CRUD, que nada mais são do que as operações básicas com uma determinada tabela relacional.

Se você ainda precisa criar algum tipo que serviço que exponha essas funcionalidades, então acredito que seria uma boa alternativa considerar o uso do ADO.NET Data Services. Este framework é construído em cima do próprio WCF, fornecendo a possibilidade de efetuar as operações CRUD em cima de contexto de dados do Entity Framework ou qualquer outra fonte de dados que implemente a interface IQueryable. Todas as funcionalidades são baseadas no padrão REST, que utiliza URIs predefinidas em conjunto com os verbos HTTP, para executar cada uma dessas operações. Já a serialização do resultado pode ser emitida em ATOM (Xml) ou até mesmo JSON, permitindo assim, que qualquer cliente HTTP (como um navegador) consuma o serviço.

Um detalhe importante é que a Microsoft incluiu no .NET Client Library, Silverlight e no AJAX tudo o que é necessário para efetuar a comunicação com serviços baseados no ADO.NET Data Services.

É importante dizer que o ADO.NET Data Services não é ideal para todos os cenários. Há muitas ocasiões onde a customização pode ser muito grande, e utilizando-o pode tornar o processo muito mais trabalhoso do que produtivo e, sendo assim, será mais viável utilizar o WCF, podendo inclusive, expor as funcionalidades do serviço utilizando o padrão REST, se assim desejar. Agora, se tudo o que precisa são as simples operações de CRUD, então criar serviços baseados no ADO.NET Data Services será uma opção boa e bastante produtiva.

Table-Valued Parameter

Suponha que você tenha uma lista na sua aplicação, onde cada um dos elementos desta lista representa  um Id na base de dados. Você precisa submeter esta lista para uma Stored Procedure para atualizar os respectivos registros. Aqui entra o problema: como você irá fazer isso?

Provavelmente você já deve ter passado por esse problema, e as possíveis soluções para isso seria percorrer a lista, elemento à elemento, configurar o(s) parâmetro(s) e, finalmente, invocar a Stored Procedure. Outra possibilidade seria formatar todos os Ids em uma única String, separados por uma vírgula, e na Stored Procedure criar um parâmetro do tipo Varchar(X) para receber tais Ids. O problema da primeira técnica é a quantidade de comandos que você executa, ou seja, se tiver 200 Ids, serão duzentas execuções da Stored Procedure; já a segunda nos obrigará a criar uma query dinâmica, o que poderá tornar o nosso código vulnerável à SQL Injection e, além disso, se a concatenação dos Ids na String resultar em uma String maior do que o comprimento do parâmetro, a query não executará como o esperado.

Uma forma elegante de resolver isso no SQL Server 2008, é utilizando um novo tipo de dado, chamado de Table-Valued Parameter. Com este tipo especial, é possível determinarmos uma estrutura de dados e, definí-lo como parâmetro de entrada em uma Stored Procedure. O código abaixo ilustra a criação do Table-Valued Parameter e, logo em seguida, a Stored Procedure que utiliza o mesmo:

CREATE TYPE ColecaoDeIds AS TABLE
(
    Id INT NOT NULL
)
GO

CREATE PROCEDURE AtualizarUsuarios
    @ColecaoDeIds As ColecaoDeIds READONLY,
    @Ativo As Bit
AS
BEGIN
    UPDATE
Usuarios SET
        Ativo = @Ativo
    WHERE Id IN
    (
        SELECT Id FROM @ColecaoDeIds
    )
END
GO

Note que o tipo “ColecaoDeIds” contém apenas uma coluna chamada “Id”, mas você poderia adicionar quantas forem necessárias. Um detalhe importante é que, quando utilizamos um Table-Valued Parameter como parâmetro em uma Stored Procedure, ele deverá obrigatoriamente ser definido como ReadOnly; se precisar inserir, atualizar ou excluir registros dele, então você deve inserir os dados em uma tabela temporária e, depois disso, fazer a manipulação desejada. O interessante é que o Table-Valued Parameter nada mais é que uma espécie de “tabela”, permitindo efetuar queries em cima dela.

Já no código .NET, poucas mudanças são necessárias. Basicamente o que você precisa fazer é, criar uma instância da classe SqlParameter e definir a propriedade SqlDbType para SqlDbType.Structured. Há uma limitação na definição do tipo de objeto que você pode passar como valor para este parâmetro: deverá utilizar DataTable, DbDataReader ou IEnumerable<SqlDataRecord>. Independentemente de qual destes repositórios você irá utilizar, obrigatoriamente eles devem seguir a mesma estrutura do Table-Valued Parameter que, no nosso caso, possui apenas uma coluna chamada “Id” do tipo INT. O código abaixo mostra como podemos proceder para a configuração deste parâmetro:

using (SqlCommand cmd = new SqlCommand(“AtualizarUsuarios”, conn))
{
    cmd.Parameters.Add(
        new SqlParameter(“@ColecaoDeIds”, tuaDataTableAqui) { SqlDbType = SqlDbType.Structured });
}

Se você já utiliza DataTables para armazenamento e/ou manipulação dos dados, então tudo o que precisa fazer é criar uma nova DataTable com a mesma estrutura do Table-Valued Parameter e populá-la. Mas e se estivermos utilizando algum array ou coleção? Para facilitar,, eu criei um extension method chamado ToDataTable<T> que, como o próprio nome diz, transforma a coleção/array em uma DataTable. Como a estrutura da DataTable precisa seguir a mesma estrutura do parâmetro, eu criei um atributo chamado GenerateColumnAttribute, para que você decore as propriedades de sua entidade, para que elas sejam criadas como DataColumns no DataTable. Com essa estrutura, poderíamos fazer algo como:

List<Usuario> list = CarregarUsuarios();

using (SqlCommand cmd = new SqlCommand(“AtualizarUsuarios”, conn))
{
    cmd.Parameters.Add(
        new SqlParameter(
            “@ColecaoDeIds”,
            list.ToDataTable(typeof(GenerateColumnAttribute))) { SqlDbType = SqlDbType.Structured });

    Console.WriteLine(cmd.ExecuteNonQuery());
}

O método ExecuteNonQuery retornará a quantidade de registros afetados pela query. O código .NET de exemplo pode ser encontrado aqui.

Model-First Development

A versão atual do Entity Framework trabalha com o conceito conhecido como “database-first”. Neste caso, você precisa primeiramente criar a base de dados, definir as tabelas, as colunas e suas características, constraints e, depois disso, a IDE analisará todo o schema da mesma e irá construir o modelo de classes olhando para ele, inclusive levando em consideração os possíveis relcionamentos definidos, para tentar criar as associações entre as classes que farão parte do sistema. Quando você tiver tem uma base de dados que está bem definida e funcional, esse modelo é bem interessante, já que ele (re)utiliza todo o trabalho que foi realizado na construção da mesma, tendo apenas que acertar alguns detalhes.

Mas o mapeamento entre o mundo relacional e orientado a objetos é bem mais complexo do que isso. Nem sempre temos uma entidade por registro/tabela. Algumas características que são salvas no banco de dados como uma coluna normal, utilizando um tipo de dado convencional, no mundo orientado à objetos, isso pode ser representado por uma especialização (herança) ou até mesmo um outro objeto mais complexo que possui, além de suas características (propriedades), um funcionamento próprio (métodos e eventos). Esses são alguns dos “problemas” encontrados neste modelo de desenvolvimento que o Entity Framework atualmente possui.

Eu coloquei a palavra problemas entre aspas que, ao meu ver, isso é uma característica deste modelo e não uma deficiência. Aqueles que muitas vezes optam por iniciar no desenvolvimento da estrutura de classes da aplicação, não irá ter benefício nenhum do uso desta técnica. A partir da versão 2.0 do Entity Framework, que será lançada em conjunto com o .NET Framework 4.0 e o Visual Studio .NET 2010, irá incorporar o modelo de desenvolvimento conhecido como “model-first” que, como o próprio nome diz, baseando na estrutura de classes e suas associações, conseguirá extrair o modelo relacional, gerando os scripts necessários para a criação das tabelas, colunas e possíveis relacionamentos em uma base de dados.

Paginação utilizando LINQ

Podemos facilmente vincular uma query LINQ em um controle DataBound do ASP.NET para exibir os dados. A query pode ser feita utilizando LINQ To SQL, LINQ To Xml, etc. Com isso, podemos simplesmente fazer:

this.GridView1.DataSource = from cliente in colecaoDeClientes select cliente;
this.GridView1.DataBind();

Os problemas aparecem quando voce habilita a paginação do controle e, ao rodar o mesmo exemplo, uma exceção do tipo NotSupportedException será disparada, informando a seguinte mensagem: The data source does not support server-side data paging. Quando efetuamos uma query LINQ (em cima de qualquer fonte de informação), o retorno é sempre uma classe que implementa direta ou indiretamente a Interface IEnumerable<T>, e para efetuar a paginação, é necessário que o controle conheça a quantidade de registros retornados pela query para conseguir dimensionar a quantidade necessária de páginas a serem exibidas.

A Interface referida acima não possui uma propriedade que informe a quantidade de elementos da coleção e, como alternativa, podemos alterar a query para que ela retorne uma instancia da classe List<T>, modificando ligeiramente a query:

this.GridView1.DataSource = (from cliente in colecaoDeClientes select cliente).ToList();
this.GridView1.DataBind();

Acessando tipos anônimos no evento RowDataBound

Há algum tempo eu mostrei como acessar o item que está sendo adicionado em um controle DataBound para fazer algum tipo de verificação durante a inserção dos dados no controle. Como é mostrado no post, a conversão deve ser feita de acordo com o tipo de fonte de dados que está sendo inserida no controle (DataTable, DataReader, objetos, etc). Mas e quando precisamos acessar um tipo anônimo que é projetado a partir de uma query LINQ?

this.GridView1.DataSource = from cliente in colecaoDeClientes
                            select new
                            {
                                Nome = cliente.Nome,
                                QualquerOutraInformacao = true
                            };
this.GridView1.DataBind();

No exemplo acima, não há um tipo efetivo em que podemos converter o DataItem que é passado como parametro para o evento, impossibilitando extrair o valor de uma determinada propriedade. Para contornar esse problema, podemos recorrer ao método estático Eval da classe DataBinder, passando o DataItem como parametro e o nome da propriedade a ser recuperada. O exemplo abaixo mostra como efetuar o acesso ao tipo anônimo no evento RowDataBound do controle GridView:

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        ((Button)e.Row.FindControl(“btn”)).Enabled =
            Convert.ToBoolean(DataBinder.Eval(e.Row.DataItem, “QualquerOutraInformacao”));
    }
}

Além de ser uma técnica late-bound e que usa Reflection em runtime, ainda há o problema de ser fracamente tipado, o que nos obriga a sempre efetuar a conversão explícita para garantir a compilação da aplicação.

Múltiplas condições no JOIN com LINQ

Em minhas deambulações com o LINQ, me surgiu uma dúvida de como proceder para efetuar um join com duas condições na cláusula on. Inicialmente tentei da forma mais fácil, ou seja, utilizando o operador &&, mas sem muito sucesso. Com um pequena busca, encontrei um post no MSDN em que o próprio Anders Hejlsberg explica como proceder. Simplesmente basta criarmos um tipo anônimo e combinarmos os campos que desejamos comparar, assim como é mostrado no exemplo abaixo:

from sala in cadastroDeSalas
join curso in Cursos on
    new
    {
        cadastroDeSalas.Campo1,
        cadastroDeSalas.Campo2
    }
    equals 
    new 
    {
        curso.Campo1,
        curso.Campo2 
    }

Utilização de entidades LINQ To SQL em serviços

Estive lendo um artigo na MSDN Magazine chamado Flexible Data Access With LINQ To SQL And The Entity Framework, escrito pelo Anthony Sneed. O artigo aborda como expor entidades criadas pelos ORMs da Microsoft (LINQ To SQL e Entity Framework) através de serviços WCF.

O autor criou DTOs (que são baseados em POCO) semelhantes as entidades criadas pelo designer do ORM, fazendo um trabalho árduo que é o mapeamento entre os DTOs e as entidades geradas. Como o LINQ To SQL tem um suporte legal para mapeamento entre objetos POCOs e o banco de dados (via arquivos Xml), o Anthony mostra neste post como podemos fazer uso dos templates T4, criando classes em formato POCO ao invés de utilizar as próprias entidades geradas pelo LINQ To SQL.