Back-End

10 jun, 2014

Dados geográficos no Entity Framework 5 e SQL Server 2012

Publicidade

Umas das grandes novidades do Entity Framework 5 foi a adição de um tipo de campo chamado DbGeography, o qual permite informar as coordenadas de latitude e longitude de um determinado ponto. Com isto, podemos ter uma série de pontos armazenados num banco de dados disponíveis para qualquer tipo de consulta.

Neste artigo vou explicar passo a passo como criar uma lista de lojas com algumas propriedades, armazená-las no banco de dados do SQL Server 2012 e ao final, como fazer pesquisas obtendo, por exemplo, qual é a loja mais próxima a partir da minha localização.

Para iniciar, abra o Visual Studio .NET 2012 e crie um projeto em Visual C# do tipo Console Application chamado Geolocalização, conforme a figura 1.

Figura 1 – Novo Projeto
Figura 1 – Novo Projeto

Por ser um projeto de Console, a primeira coisa a fazer é instalar o Entity Framework 5 através do pacote do NuGet. Para isto, selecione o menu Tools / Library Package Manager / Manage NuGet Packages for Solution e pesquise sobre “entity framework 5”, conforme a figura 2. Assim que mostrar o pacote chamado EntityFramework, clique em Install e pronto. Veja que a versão é a 5.0.0 e quando você instalar qualquer pacote via NuGet, tenha certeza que terá a última versão e não precisará ficar pesquisando na internet onde está, qual versão usar, como instalar, quais referências fazer etc. Tudo isto é o NuGet que faz por nós.

Figura 2 – Instalação via NuGet
Figura 2 – Instalação via NuGet

Nos bastidores, abra a pasta References e veja que foram referenciadas todas as classes, sendo a EntityFramework, System.Data.Entity e a System.ComponentModel.DataAnnotations.

Em seguida, no Solution Explorer, clique com o botão direito e selecione Add / Class. A figura três mostra a janela com o nome da classe chamada Loja. Clique no botão Add para criar o arquivo.

Figura 3 – Classe Loja
Figura 3 – Classe Loja

Na lista de using, adicione o “using System.Data.Spatial;” que é o responsável pelo tipo de dado DbGeography. Veja a seguir as quatro propriedades da classe Loja que você deverá digitar. Note que a propriedade Localização é do tipo DbGeography.

C#

public class Loja
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Telefone { get; set; }
    public DbGeography Localizacao { get; set; }
}

Eu sugiro que você explore um pouco mais o conhecimento com o Object Browser (menu View / Object Browser), onde o texto a ser pesquisado é o DbGeography. Sendo assim, você verá tudo o que é possível fazer com esta classe. Como iremos adicionar várias lojas à esta classe Loja, e ficarão armazenadas no banco de dados SQL Server 2012, o próximo passo é informar ao projeto qual é o banco de dados. Sendo assim, abra o arquivo App.Config, localize o nó “connectionStrings” – caso não exista, crie-o.

Neste nó é preciso especificar toda a string de conexão, então, adicione a chave chamada “ContextoDB”, que aponta o connectionString para o banco de dados chamado DadosGeograficos localizado no servidor EARTH (que é a minha máquina), que tem a segurança integrada. Veja que o providerName é o SqlClient.

Caso tenha ficado com dúvida nisto, tenha em mente que você precisa montar a string de conexão para acessar o seu SQL Server 2012, ou seja, se tiver usuário e senha, especifique-os, assim como o nome do servidor onde ele se encontra.

App.Config

<connectionStrings>
  <add name="ContextoDB" 
        connectionString="Data Source=EARTH;Initial Catalog=DadosGeograficos;Integrated Security=True;MultipleActiveResultSets=True" 
        providerName="System.Data.SqlClient" />
</connectionStrings>

Você deve estar pensando onde é que está o banco de dados? A resposta é: ainda não criamos o banco, pois estou usando o Code First do Entity Framework 5 para codificar antes mesmo de ter o banco criado.

O próximo passo é criar uma classe chamada Contexto, conforme a listagem a seguir. Na lista de using, adicione o using System.Data.Entity. Veja que há duas coisas super importantes que você jamais pode esquecer. Primeiro, que a classe herda DbContext, portanto, adicione o DbContext. Ele é responsável pelo gerenciamento do banco de dados, por criá-lo, exclui-lo, fazer as transações, efetivar as alterações, rastreamento, configurações, validação de entidade, enfim, dê um F12 sobre o DbContext para mais detalhes. Segundo, repare como criei o construtor desta classe. Note que há o “: base(“name=ContextoDB”)” o qual aponta para a chave criada no App.Config direcionando para o banco de dados descrito na connectionStrings.

Renato, o que acontece se eu não declarar a chave no App.Config e nem no construtor? O sistema cria o banco de dados apontando para “Data Source=(localdb)\v11.0”.

Logo em seguida existe a declaração da propriedade chamada Lojas que é do tipo DbSet<Loja>. Isto significa que DbSet é a coleção de dados do tipo Loja, é o responsável pelo CRUD que será 100% criado em tempo de execução. Novamente, pressione F12 sobre o DbSet para mais detalhes.

C#

public class Contexto : DbContext
{
    public Contexto() : base("name=ContextoDB") {}

    public DbSet<Loja> Lojas { get; set; }
}

Agora vem o melhor de tudo, incluir e pesquisar dados. Abra o arquivo Program.cs, adicione o “using System.Data.Spatial;” para que tenhamos acesso ao DbGeography. Dentro do bloco static void Main é onde iremos digitar o bloco de códigos descritos a seguir. As duas primeiras linhas são importantes porque há a referência da classe Contexto e a inicialização do banco de dados. Em seguida, criei uma lista de lojas usando Generics (List<Loja>) contendo cinco lojas do tipo Loja. Ou seja, para cada loja defini as devidas propriedades. Observe que a propriedade Localização contém o DbGeography, seguido do FromText com os devidos pontos de latitude e longitude da loja.

Eu usei o emulador do Windows Phone 8 para pegar estes pontos, mas você pode abrir qualquer software como o Bing mapas e o Google mapas para obter tais pontos. O mais importante é que quero que estas cinco lojas sejam relativamente próximas, afinal faremos uma pesquisa pelas coordenadas.

Outro ponto importante é que após a declaração das cinco lojas dentro do List<Loja>{}, usei o ForEach seguido de uma expressão Lambda para adicionar cada uma das lojas à entidade Loja no banco de dados. Para saber o que acontece nos bastidores, sugiro este link , pois é importante saber como montar uma expressão e como ela é executada.

Por fim, como estou usando o Entity Framework 5, após o looping para cadastrar as lojas, usei o SaveChanges para efetivar a inclusão na entidade. Pronto, temos as cinco lojas cadastradas no banco de dados.

Dica: quando você executar esta aplicação pela segunda vez, comente este bloco de inclusão de dados, senão teremos dados duplicados no banco de dados.

C#

class Program
{
    static void Main(string[] args)
    {
        Contexto ctx = new Contexto();
        ctx.Database.Initialize(true);

        // adicionar dados
        try
        {
            new List<Loja>{
                new Loja { Nome = "Loja roupas", Telefone="12312331", 
                            Localizacao = DbGeography.FromText("POINT(-27.583748 -48.545560)")},
                new Loja { Nome = "Loja eletrônicos", Telefone="49238443", 
                            Localizacao = DbGeography.FromText("POINT(-27.584433 -48.547148)")},
                new Loja { Nome = "Loja Bikes", Telefone="324234234", 
                            Localizacao = DbGeography.FromText("POINT(-27.585156 -48.549251)")},
                new Loja { Nome = "Loja Kitesurf", Telefone="98475474", 
                            Localizacao = DbGeography.FromText("POINT(-27.585764 -48.551053)")},
                new Loja { Nome = "Restaurante", Telefone="312345544", 
                            Localizacao = DbGeography.FromText("POINT(-27.586183 -48.551869)")}
            }.ForEach(l => ctx.Lojas.Add(l));
            ctx.SaveChanges();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.ReadLine();
        }
    }
}

Consultas ao banco

Agora que já temos dados no banco, vamos realizar algumas consultas. A primeira será para listar todas as lojas cadastradas e retornar o nome e o tipo de dados. Portanto, digite o bloco de código a seguir.

C#

Console.WriteLine("---- Lojas e tipos ----");
var tipos = ctx.Lojas.ToList();
foreach (var l in tipos)
{
    Console.WriteLine(l.Nome + " - " + l.Localizacao.SpatialTypeName);
}
Console.ReadLine();

Pressione F5 para executar e veja o resultado conforme a figura 4.

Figura 4 – Lista de lojas
Figura 4 – Lista de lojas

Até aqui foi um sucesso. Temos a lista de todas as lojas cadastradas. Mas, qual é a mágica se o banco de dados não existe? Como expliquei anteriormente, o uso do Code First permite que façamos todos os códigos e o banco é criado um tempo de execução. Para visualizar o banco, o VS 2012 tem um item chamado SQL Server Object Explorer, portanto, abra-o. Clique no nome do seu servidor (EARTH, no meu caso), abra o Databases e veja que o banco DadosGeograficos existe contendo a entidade chamada Lojas com os devidos campos, conforme a figura 5. Note que para o SQL Server 2012 o campo Localização é do tipo geography. Então, aqui fica a dica: se o seu banco de dados tiver este tipo, significa que o seu projeto pode usar o DbGeography (que é do .NET).

Figura 5 – Estrutura do banco gerado
Figura 5 – Estrutura do banco gerado

Vamos continuar com as consultas. Todo e qualquer código a partir deste ponto deve ser inserido antes do Console.Readline();. Obviamente, comente todo o bloco que adiciona dados no banco, senão teremos dados duplicados.

Já que temos a latitude e longitude de todas as lojas, vou definir uma coordenada para um ponto, digamos que seja o local onde estou. Sendo assim, a próxima consulta retorna todas as lojas com as devidas distâncias a partir do meu ponto, o qual chamei de meuLocal.

Observe como está a expressão Lambda da consulta, sendo que as lojas serão exibidas em ordem crescente (OrderBy) pela distância a partir do meu ponto. Ou seja, a propridade Localização (que é do tipo DbGeography) contém o Distance que computa a distância entre dois pontos, neste caso, o meuLocal e a coordenada de cada loja.

C#

Console.WriteLine("---- Lojas próximas + distancia ---");
var meuLocal = DbGeography.FromText("POINT (-27.5855 -48.5507)");

var lojas = ctx.Lojas
            .OrderBy(l => l.Localizacao.Distance(meuLocal));
foreach (var l in ctx.Lojas)
{
    Console.WriteLine("Lojas próximas: {0} - Distancia: {1}",
                        l.Nome,
                        meuLocal.Distance(l.Localizacao));
}

Pressione F5 para executar a aplicação e veja o resultado na figura 6.

Figura 6 – Distância das lojas a partir do meu ponto
Figura 6 – Distância das lojas a partir do meu ponto

Como localizar a loja mais próxima do meu ponto? Conforme o código a seguir, a partir da coleção de lojas retornadas da consulta, usei o método de extensão FirstOrDefault para saber qual é o ponto mais próximo em relação à variável meuLocal.

C#

Console.WriteLine("---- Localizar loja mais próxima ---");
var maisProxima = ctx.Lojas
                    .OrderBy(l => l.Localizacao.Distance(meuLocal))
                    .FirstOrDefault();
Console.WriteLine("Loja mais próxima é {0}",
                    maisProxima.Nome);

Pressione F5 para executar a aplicação e veja o resultado na figura 7, mostrando a Loja Kitesurf como a mais próxima.

Figura 7 – Loja mais próxima
Figura 7 – Loja mais próxima

Como localizar um ponto? Imagine que preciso saber se há alguma loja existente na coordenada informada, ou seja, quero que a consulta vá no banco de dados e pesquise em todas as lojas cadastradas se existe alguma loja numa coordenada informada. No código a seguir, chamei de alvo uma loja contendo a respectiva latitude e longitude. Em seguida, a consulta verifica através do Intersects se há algo contido no alvo. A variável buscaLoja é a pesquisa em si, então, como usei o SingleOrDefault na consulta, fiz um IF para saber se o buscaLoja é igual a nulo ou não. Neste caso, ele achou a loja chamada Loja Bikes, conforme a figura 8.

C#

Console.WriteLine("---- Localizar loja ---");
DbGeography alvo = DbGeography.FromText("POINT (-27.585156 -48.549251)");
var buscaLoja = ctx.Lojas
    .SingleOrDefault(l => l.Localizacao.Intersects(alvo));
if (buscaLoja == null)
    Console.WriteLine("Loja não encontrada");
else
    Console.WriteLine("Achou a loja {0}",
                    buscaLoja.Nome);

Pressione F5 para executar a aplicação e veja o resultado na figura 8, sendo que o alvo foi localizado, sendo a Loja Bikes.

Figura 8 – Localizar uma loja
Figura 8 – Localizar uma loja

Enfim, esta novidade do Entity Framework 5 (mais informações aqui), juntamente com o SQL Server 2012, nos faz pensar em diversos produtos de localização, que podemos fazer em qualquer tipo de aplicação que suporte isto (principalmente aplicações em Windows 8). O que você pode e deve fazer é unir os controles de mapas que vem nas Toolbox dos projetos do Visual Studio .NET 2012 e plotar os pontos oriundos das consultas, mostrando-os no mapa, a fim de dar mais realidade ao usuário. Tudo o que você tem que ter em mente é que um mapa sempre espera uma coleção de pontos, e isto nós já temos.

Bons estudos e sucesso nos projetos!