LINQ To SQL – Executando Stored Procedures com vários Result Sets

Recentemente me deparei com um problema nos fóruns do MSDN relacionado com stored procedures mapeadas com o LINQ To SQL. Após uma pesquisa em alguns blogs na internet encontrei uma solução e gostaria de compartilhar com vocês.

É comum existirem stored procedures que retornam mais de um result set tal como o exemplo abaixo.

CREATE PROCEDURE [dbo].[ListCustomersAndEmployees]
AS
BEGIN

    SET NOCOUNT ON;

    SELECT * FROM dbo.Customers
    SELECT * FROM dbo.Employees    

END

Obs: Estou considerando que todos conhecem o banco de dados Northwind da Microsoft. Para fazer o download do mesmo veja no final do post.

O LINQ To SQL realiza o mapeamento da stored procedure da seguinte maneira.

[Function(Name="dbo.ListCustomersAndEmployees")]
public ISingleResult<ListCustomersAndEmployeesResult> ListCustomersAndEmployees()
{
    IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));
    return ((ISingleResult<ListCustomersAndEmployeesResult>)(result.ReturnValue));
}

O problema é que no código acima só conseguimos adquirir os campos retornados pela primeira consulta, ou seja, os campos da tabela Customers.

Para resolver esse problema podemos utilizar o mecanismo de partial classes para criar um novo método que irá mapear a stored procedure. Esse mecanismo nos permite dividir a declaração de uma classe em múltiplos arquivos e isso é interessante, já que não é recomendado alterar o código gerado automaticamente pelo Visual Studio.

A idéia então é criar um novo arquivo que adiciona um novo método ao contexto. No exemplo abaixo o meu contexto possui o nome NorthwindDataContext.

public partial class NorthwindDataContext
{
    [Function(Name = "dbo.ListCustomersAndEmployees")]
    [ResultType(typeof(Customer))]
    [ResultType(typeof(Employee))]
    public IMultipleResults ListMultipleCustomersAndEmployees()
    {
        IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())));
        return (IMultipleResults)(result.ReturnValue);
    }
}

Assim para utilizarmos o método acima e executarmos a procedure o código seria assim:

// contexto
NorthwindDataContext dataContext = new NorthwindDataContext();

// usando a interface IMultipleResults
IMultipleResults results = dataContext.ListMultipleCustomersAndEmployees();

// result set Customers
IEnumerable<Customer> customers = results.GetResult<Customer>();

// result set Employees
IEnumerable<Employee> employees = results.GetResult<Employee>();

...

O Visual Studio provavelmente não consegue fazer esse mapeamento corretamente porque podem existir stored procedures que dependendo de uma condição retornam entidades diferentes. A própria definição da interface IMultipleResults diz o seguinte:

Represents the results of mapped functions or queries with variable return sequences.

Espero que isso ajude vocês.

Links:

Northwind and pubs Sample Databases for SQL Server 2000
http://bit.ly/cWQNiL

IMultipleResults Interface
http://bit.ly/cFzS27

Stored Procedures (LINQ To SQL)
http://bit.ly/9wnZZg

C# Tips & Tricks – Ordenação com LINQ To Objects

É comum a necessidade de realizarmos a ordenação de arrays, listas genéricas e dicionários. Apesar de ser um assunto antigo e já conhecido pela maioria dos desenvolvedores, vamos mostrar aqui como fazer a ordenação de uma maneira simples usando o LINQ To Objects.

Vamos demonstrar esse método de ordenação contrastando a maneira anterior à introdução do LINQ no Framework (antes da versão 3.0) e após o LINQ.

Vamos criar uma classe simples para utilizarmos posteriormente em nosso código.

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1}\nEmail: {2}", FirstName, LastName, EmailAddress);
    }
}

Essa classe representa um cliente que possui um nome, sobrenome e e-mail. Além disso, sobrescreve o método ToString de System.Object e utiliza propriedades automáticas (C# 3.0).

Vamos agora criar uma lista genérica de clientes.

var lista = new List<Customer>
{
    new Customer { FirstName = "Ari", LastName = "Raimundo", EmailAddress = "ari@mail.com"},
    new Customer { FirstName = "Anders", LastName = "Hejlsberg", EmailAddress = "anders@mail.com"},
    new Customer { FirstName = "Bill", LastName = "Gates", EmailAddress = "bill@mail.com"},
    new Customer { FirstName = "Jesse", LastName = "Liberty", EmailAddress = "jesse@mail.com"}
};

Esse post irá demonstrar como poderíamos ordenar essa lista de clientes pela propriedade FirstName (primeiro nome).

Antes da introdução do LINQ a ordenação era realizada com o método Sort da lista (ou Array.Sort em caso de arrays) que era sobrecarregado. Uma das assinaturas desse método solicitada uma classe que herdava de IComparer<T> e uma outra um delegate Comparison<T>.  Existem outras maneiras mas vou simplificar e demonstrar somente essas duas.

Vamos então criar uma classe que herda de IComparer<T> e que realiza a ordenação pela propriedade FirstName.

public class CustomerComparer : IComparer<Customer>
{
    public int Compare(Customer x, Customer y)
    {
        return x.FirstName.CompareTo(y.FirstName);
    }
}

Para ordenarmos a lista faríamos então:

lista.Sort(new CustomerComparer());

Vamos agora apresentar a maneira de ordenar usando o delegate Comparison<T>. Para isso, é necessário a criação do método abaixo:

static int CompareCustomerByFirstName(Customer x, Customer y)
{
    return x.FirstName.CompareTo(y.FirstName);
}

Para ordenar a lista faríamos então:

lista.Sort(CompareCustomerByFirstName);

O delegate Comparison<T> poderia ser passado para o método usando anonymous delegates também, que já estava presente na versão 2.0. Para ordenar a lista ficaria assim:

lista.Sort(delegate(Customer x, Customer y)
{
    return x.FirstName.CompareTo(y.FirstName);
});

Mas agora vamos utilizar o LINQ to Objects para ordenar a nossa lista, ou melhor, vamos realizar uma consulta em nossa lista e retornar uma nova lista ordenada.

var l = (from c in lista
         orderby c.FirstName
         select c).ToList();

Para simplificar ainda mais vamos utilizar extension methods (method-based queries).

var l = lista.OrderBy(c => c.FirstName).ToList();

Vejam que se tivéssemos a necessidade de ordenar a lista usando 2 propriedades, por exemplo: FirstName de maneira ascendente e LastName de maneira descendente, seria um pouco complexo usando IComparer<T> ou Comparable<T>. Veja abaixo o código em LINQ para fazermos isso.

var l = (from c in lista
         orderby c.FirstName, c.LastName descending 
         select c).ToList();

Ah, se quisermos mostrar o conteúdo da lista na tela podemos usar o código abaixo.

l.ForEach(c => { Console.WriteLine(c.ToString()); });

Os métodos de ordenação acima demonstrados podem ser utilizados para qualquer tipo que implementa IEnumerable ou IEnumerable<T> (arrays, listas genéricas e dicionários).

É fato que o LINQ simplifica muito a nossa vida!

Alguns links de ajuda:

LINQ To Objects
http://bit.ly/8OHCr3

List<T>.Sort Method
http://bit.ly/71Fd9B

(Off-Topic) To-Do List: 2010

Seguindo a idéia do Diego Nogare (http://www.diegonogare.net), autor de um dos blogs que leio sobre tecnologia, também vou colocar uma lista de algumas coisas que pretendo conquistar em 2010.

1 - MCTS em WPF (até metade de fevereiro !).
2 - MCTS em Windows Forms (talvez).
3 - MCPD em Windows Development (será?).
4 - Estudar Silverlight.
5 - Tentar arrumar tempo e assuntos interessantes para escrever no blog.
6 - Tentar arrumar tempo para responder mais perguntas nos fóruns do MSDN.
7 - Fazer com que alguns colegas de trabalho consigam uma certificação.
8 - Retornar à academia (depois de 2 anos parado).
9 - Recomeçar a jogar basquete.
10 - Ensinar meu filho a parar de usar fraldas. rs

Feliz 2010 a todos.