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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s