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.