LINQ to SQL: deferred loading e load options

 
Antes de entrar no assunto do post em concreto, vamos criar algum contexto: considere-se uma base de dados sobre a qual foi executado o seguinte script SQL de complexidade extrema:
create table Person(
number int primary key,
[name] varchar(100),
email varchar(50)
)
create table Car (
licensePlate char(9) primary key,
manufacturer varchar(20),
model varchar(20),
color varchar(20), [owner] int not null,
constraint fk_car foreign key ([owner]) references Person(number)
)
insert into Person values (1234, ‘Mr LINQ Demo’, ‘mr.ld@mail.com’)
insert into Car values (‘AA-88-99’,‘Fiat’,‘Uno’,‘Black’,1234)
insert into Car values (‘BB-11-22’,‘Fiat’,‘Uno’,‘Red’,1234)
insert into Car values (‘CC-33-44’,‘Fiat’,‘Uno’,‘Blue’,1234)
O Mr. Linq Demo, que para além de ser o único no seu mundo inda colecciona FIAT’s Uno, vai ser a cobaia. Nas tabelas não tive preocupação com a validação do formato de alguns campos porque para o objectivo do post não é relevante. Para quem não está familiarizado com o LINQ to SQL, vou mostrar como começar; quem já conhece, talvez queira saltar umas quantas linhas 😉
 
Depois de termos as tabelas criadas e os dados inseridos vamos adicionar as classes que irão dar suporte ao nosso LINQ to SQL a um projecto do Visual Studio:
 

 
Depois de adicionar o novo item podemos então adicionar as nossas tabelas, arrastando-as do Server Explorer para o Designer que entretanto foi aberto.
 

 
Num dos ficheiros adicionados ao projecto (LindDemo.designer.cs, neste caso) podemos ver as classes geradas pelo designer. A classe LinqDemoDataContext representa a nossa BD e disponibiliza duas propriedades que representam as tabelas Person e Car.
 
Entrando no assunto do post: deferred loading é uma característica do LINQ to SQL que consiste em carregar relações de um para um ou um para muitos apenas quando são acedidas. Assim, considere-se o seguinte código:
static void Main(string[] args)
{
    LinqDemoDataContext dc = new LinqDemoDataContext();

    Person person = dc.Persons.Single(p => p.number == 1234);
    Console.WriteLine("Name: {0}; Number: {1}; Email: {2}",
        person.name, person.number, person.email);
    Console.WriteLine("Cars:");

    Car[] cars = person.Cars.ToArray();
    foreach (Car c in cars)
    {
        Console.WriteLine("\tManufacturer: {0}; Model: {1}; Color: {2}; Owner: {3}",
            c.manufacturer, c.model, c.color, c.owner);
    }

    cars[0].color = "Yellow";
    dc.SubmitChanges();
}
Executando-o temos o seguinte resultado e estado na tabela Car:

 
À partida, este é o resultado esperado.  Mas vejamos o que acontece na BD; no SQL Server Profiler obtemos o seguinte trace:
 

 

(carregar para aumentar)

 

Os resultados apresentados são o reflexo do deferred loading utilizado pelo LINQ to SQL. Como podemos ver (linhas sublinhadas) são feitos dois acessos em momentos distintos: o primeiro para obter a Person, e o segundo para obter os Cars. A classe DataContext disponibiliza a propriedade DeferredLoadingEnabled que permite controlar esse comportamento. Por omissão o seu valor vem a true já que os dados podem não ser necessários, mas caso sejam tem de ser possível obtê-los. Se colocarmos o valor da propriedade a false obtemos o seguinte comportamento:
 

(carregar para aumentar)

 

Como se pode ver, a colecção Cars está vazia e apenas foi realizado um comando sobre a BD, para obter a Person. Quando o valor da propriedade DeferredLoadingEnabled está a true num cenário como o anterior são feitos dois acessos à BD quando à partida já sabemos que vamos precisar dos dados. Para fazer face a este "problema" a classe DataContext disponibiliza a propriedade LoadOptions do tipo DataLoadOptions, que permite a seguinte utilização:
dc.DeferredLoadingEnabled = false;
DataLoadOptions opts = new DataLoadOptions();
opts.LoadWith<Person>(p => p.Cars);
dc.LoadOptions = opts;

Indicamos que não queremos deferred loading mas, para evitar que a colecção Cars não seja preenchida, indicamos também que quando os dados de uma Person são carregados, os Cars associados também o são (utilizei uma lambda expression; o tipo do parâmetro p é inferido pelo tipo indicado para o método genérico). Executando novamente o programa com estas alterações podemos ver que apenas é realizado um acesso de leitura (Person + Cars) e o acesso final de escrita (para guardar as alterações).

(carregar para aumentar)

Para finalizar: as DataLoadOptions permitem opimizar o carregamento de dados minimizando os acessos à BD. Apesar de ter exemplificado apenas com um LoadWith<>, podemos indicar várias opções, para carregar todas as relações que envolvem uma entidade, por exemplo.

 
 
 
 
 

Advertisements

Verificar se um tipo é uma concretização de um genérico em .NET

 
Há uns dias estava a trabalhar com um colega meu e algures no meio de uns genéricos surgiu-nos a necessidade de saber se um dado objecto é instância de um tipo que é concretização de um dado genérico. Fomos um pouco preguiçosos e, em vez de procurar na documentação, perguntámos ao Eng.º Miguel Carvalho como resolver o problema. E a solução é muito simples! O que se pretende é algo deste género:
static void Main(string[] args)
{
    int i = 0;
    List<int> list = new List<int>();
    Dictionary<int,string> dict = new Dictionary<int,string>();

    Console.WriteLine(IsSpecificationOfGenericType(i, typeof(List<>)));             // False
    Console.WriteLine(IsSpecificationOfGenericType(list, typeof(List<>)));          // True
    Console.WriteLine(IsSpecificationOfGenericType(dict, typeof(List<>)));          // False
    Console.WriteLine(IsSpecificationOfGenericType(dict, typeof(Dictionary<,>)));   // True
}
A primeira coisa a reparar é como indicar os tipos "base" dos genéricos: utiliza-se a sintaxe <,>; por exemplo, para List<T> utiliza-se typeof(List<>). Antes de passar à implementação do método auxiliar IsSpecificationOfGenericType vejamos três membros do tipo "Type":
 
– IsGenericType: Gets a value indicating whether the current type is a generic type.
– GetGenericTypeDefinition: Returns a Type object that represents a generic type definition from which the current generic type can be constructed.
– IsGenericTypeDefinition: Indicates whether the current Type represents a generic type definition, from which other generic types can be constructed.
Com estas propriedades e sabendo que a instância de Type que representa um tipo é única, a implementação do método auxiliar fica muito simples:
static bool IsSpecificationOfGenericType(object o, Type genericType)
{
    if (!genericType.IsGenericTypeDefinition) 
        throw new InvalidOperationException("The 'genericType' parameter must be a generic type definition");
    Type t = o.GetType();
    return t.IsGenericType && t.GetGenericTypeDefinition() == genericType;
}

Ciclo Application Lifecycle Management – Evento nº 1

Decorreu ontem o primeiro evento do ciclo Application Lifecycle Management, promovido pela Microsoft Portugal. Este primeiro evento, dividido em três sessões, focou principalmente o Visual Studio Team System/Team Foundation Server, quer a nível de funcionalidades actuais, quer novas features a serem incluídas na próxima versão (codename "Rosario").
 
O que há de novo no VSTS "Rosario" – Pedro Rosa
 
Nesta sessão o Pedro Rosa começou por apresentar alguns números que levaram a Microsoft a desenvolver uma ferramenta como o VSTS/TFS.
 
– Apenas 30% da generalidade dos projectos são concluídos com sucesso
– Apenas 25% do investimento é realizado na fase de desenvolvimento; os restantes 75% são gastos na manutenção
– Se um software não tem qualidade suficiente, vai ter mais custos para o cliente (tempo gasto a "aprender" a trabalhar com o software, por exemplo) do que para a equipa de desenvolvimento
O VSTS/TFS é uma ferramenta que tem por objectivo aumentar a qualidade final do software através da integração das várias fases e intervenientes de um projecto permitindo realizar desde o controlo de versões dos developers até aos relatórios de custos/progresso de um gestor de projecto, passando pelas testes ao software.
 
A partir daqui a sessão foi um showcase das funcionalidades que se esperam vir a pertencer à nova versão do VSTS, melhorada a partir do feedback dado pelos clientes/utilizadores. Um dos caminhos de evolução é a maior ligação com o "negócio", integrando ainda mais os vários intervenientes e envolvendo mais o gestor de projectos. O portal web do TFS está bastante melhorado permitindo ao PM uma visualização/comparação global de projectos, relatórios personalizados e integração com o Project/Project Server. Para os restantes intervenientes passam a ser incluídas umas "mini-ferramenta" de notificação através da qual as pessoas recebem notificações de novos work items e podem fazer o reporting de horas, mesmo sem ter o VS aberto (team-tracker). Para os testers existe outra ferramenta semelhantes que permite gerir quais os testes realizados/por realizar, bem como colocá-los em execução no TFS. Os work items também foram melhorados, sendo possível anexar conteúdos. Passa a ser possível algo deste género:
 
Developer termina um componente -> Tester fica com um teste pendente -> Teste utiliza a sua ferramenta para dar prioridade ao teste e o colocar em execução -> o teste falha e o Tester identifica a situação de erro -> O Tester grava um vídeo ou screen shots da situação de erro -> Tester cria um novo work item para o developer com o vídeo associado e faz report do bug -> O Developer recebe a notificação no team tracker, e com a ajuda do vídeo, ideintifica o bug, resolve-o, e volta a submeter o código. Ao mesmo tempo o PM pode ver quantos testes passaram/falharam e quais as implicações em tempo/custos que o bug pode ter.
 
Fiquei com a ideia que a complexidade (inicial) de utilização pode ser um pouco grande e como tal a implementação da ferramenta pode ter alguma inércia. Por outro lado, parece-me que depois de aprender a utilizá-la os benefícios em termos de gestão de informação, eficiência e qualidade final são bastante grandes!
 
Qualidade no desenvolvimento de software – José Almeida
 
Esta sessão abordou algumas funcionalidades do VSTS para ajudar a elevar a qualidade do software; destacam-se:
 
– Testes unitários: geração automática, utilização de valores de testes em base de dados ou ficheiros.
– Code coverage: determina qual a percentagem de código coberta pelos testes unitários.
– Code metrics: conjunto de indicadores de qualidade, como por exemplo "cyclomatic coverage" que indica o número de "caminhos" possíveis na execução de um método. Permite saber o nível de complexidade do método e ter uma ideia de quantos test-cases são necessários.
Foi bastante interesante enquanto introdução, porque eu desconhecia alguns dos pontos; espero fazer um post sobre isto em breve.
 
Testes de carga no VSTS
 
Para finalizar, foram apresentados os testes de carga em aplicações web através do VSTS. Nesta sessão a quantidade de informação foi um pouco exagerada, porque é possível configurar milimétricamente os testes de carga! Uma das funcionalidadesa que achei mais interessante é o facto de existir um recorder que permite gravar no IE os passos de navegação que vão compor o teste. Os vários pedidos e dados dos formulários são armazenados e podem depois ser replicados. Naturalmente, os dados de teste podem ser obtidos a partir de uma base de dados. Para além disso os vários testes podem ser reutilizados dentro de outros, podemos definir o número de utilizadores que se pretende simular, a que ritmo chegam à página, designar outra máquinas para simular utilizadores, etc. Os resultados dos testes são apresentados de forma gráfica (um pouco confuso, para quem não está habituado) e podem ser armazenados automaticamente numa BD.
 
Aqui fiquei com a ideia que não é qualquer pessoa que configura um teste de carga como deve ser, nem analisa os resultados de forma correcta. Trata-se de uma funcionalidade que deverá ser explorada por pessoas que têm apenas essa função (tester).
 

Summer Supercomputing Institute

 
O Alcides Fonseca indicou-me um evento na área da computação distribuída e parallel computing que vai realizar-se na Universidade de Coimbra entre 14 e 18 de Julho. Trata-se de um workshop intensivo organizado pelo Texas Advanced Computing Center e que vai acontecer pela primeira vez em Portugal. Apesar de ter tido apenas algum contacto com a área, parece-me que vão ser abordados os principais temas, tais como a utilização da Message Passing Interface (MPI) para o desenvolvimento de aplicações "paralelas" e a criação de clusters.
 
Mais informação e registo em http://cfc.fis.uc.pt/events/tacc2008/

LINQ to SQL: Bug no método DataContext.ExecuteCommand

 
A classe DataContext, elemento central do mapeamento OO/Relacional fornecido pelo LINQ to SQL, permite especificar quais os comandos SQL a utilizar nas operações insert,delete e update das várias entidades nele presentes, através de métodos parciais. Na definição do método parcial, o mais comum será invocar o método ExecuteCommand definido na classe DataContext. Este método permite executar um comando SQL sobre a BD correspondente, e tem a seguinte assinatura:
public int ExecuteCommand(
	string command,
	Object[] parameters     [params]
)
A ideia é evitar que o programador tenha de criar os elementos ADO.NET necessário para executar o comando (SqlCommand, SqlParameters, SqlConnection,…). A forma de utilização segue uma abordagem semelhante ao String.Format, por exemplo: os parâmetros são indicados com {n}, onde n é o número de ordem do parâmetro no array de objectos passado.
ExecuteCommand("update Cliente set email = {0} where numCliente = {1}", 
"cliente[at]loja.pt", 12345);
O cenário onde tive de utilizar este método envolvia alguns parâmetros a null. Ora, na documentação (MSDN) lê-se:
If any one of the parameters is null, it is converted to DBNull.Value
Mas, ao executar o método, obtive a seguinte excepção:
"NotSupportedException": A query parameter cannot be of the type System.Object.
Cannot be Object?! O problema só poderia vir dos "nulls" e não foi resolvido com conversão explícita para um tipo qualquer que não object. Depois de alguma pesquisa encontrei esta referência, que diz que se trata de um bug do método, que não aceita parâmetros a null. Como não encontrei muitas referências ao assunto, resolvi deixar aqui uma nota sobre isso. Existem duas soluções "rápidas": a primeira é voltar ao ADO.NET; a outra, é criar um procedimento armazenado que faça o comando e adicioná-lo ao DataContext, que o disponibilizará como um método vulgar.