Este artigo foi traduzido por máquina.

Entity Framework

Novos recursos do CTP de junho do Entity Framework

Srikanth Mandadi

 

O recém-lançado Microsoft Entity Framework (FE) junho de 2011 CTP inclui suporte para inúmeros recursos solicitados com freqüência, como enums, espaciais tipos e funções com valor de tabela (TVFs). Vamos dar uma olhada esses recursos usando simple instruções passo a passo. Eu presumo que você está familiarizado com o EF (http://bit.ly/oLbjp0) e com o código primeiro padrão de desenvolvimento (http://bit.ly/oQ77Hm) apresentados na versão 4.1 EF.

Eis o que você precisa ser capaz de experimentar esses exemplos neste artigo:

  • 2010 Express Visual Studio e SQL Server 2008 R2 Express ou superior. Você pode baixar as edições Express do Visual Studio e SQL Server Express de http://bit.ly/rsFvxJ.
  • A Microsoft EF e EF CTP de junho de 2011 de ferramentas. Você pode fazer o download de http://bit.ly/mZgQIS.
  • O banco de dados Northwind. Você pode baixá-lo do http://bit.ly/pwbDoQ.

Agora, vamos começar.

Enums

Vamos começar com um dos recursos mais solicitados no EF — enums. Muitas linguagens de programação, inclusive.NET linguagens como C# e Visual Basic, têm suporte para enumerações. O EF, o objetivo é permitir que os usuários têm enums em seus tipos CLR e mapeá-los para o subjacente entidade dados EDM (modelo) e permitir persisting esses valores no banco de dados. Antes de entrar em detalhes, vamos examinar um exemplo simples. Enumerações são compatíveis com as abordagens de código, o primeiro banco de dados e modelo primeiro. Vou começar com a abordagem de banco de dados primeiro e, em seguida, mostrar um exemplo que usa a abordagem de código primeiro.

No exemplo de banco de dados primeiro, você usará a tabela Produtos no banco de dados Northwind. Você precisa certificar-se de que você objetiva o EF junho de 2011 CTP antes de adicionar o modelo, portanto, certifique-se para:

  1. Inicie 2010 de Visual Studio e crie um novo projeto de aplicativo de Console do C#.
  2. Clique com o botão direito em seu projeto no Solution Explorer e selecione Propriedades.
  3. Selecione Microsoft Entity Framework junho de 2011 CTP da lista suspensa de estrutura de destino (consulte a Figura 1).
  4. Pressione Ctrl + S para salvar o projeto. Visual Studio solicita permissão para fechar e reabrir o projeto; Clique em Sim.
  5. Adicionar um novo modelo para o projeto clicando com o projeto | Adicionar Novo Item (ou Ctrl + Shift + A) e, em seguida, selecionando o ADO.NET modelo de entidades de dados do Visual C# itens (chamamos nossa "CTP1EnumsModel.edmx"), em seguida, clique em Adicionar.
  6. Siga as etapas do Assistente para apontar para o banco de dados Northwind. Selecione a tabela Produtos e clique em Concluir.
  7. O modelo de entidade resultantes dessas opções tem uma única entidade, como mostrado na a Figura 2.

Targeting the Entity Framework June 2011 CTP
Figura 1 direcionamento a Entity Framework CTP de junho de 2011

Entity Model for Product Entity
Figura 2 modelo de entidade para a entidade do produto

Aqui está uma consulta LINQ para obter todos os produtos que pertencem à categoria bebidas. Observe que o CategoryID para bebidas 1:

var ctx = new NorthwindEntities();
var beverageProducts = from p in ctx.Products
                       where p.CategoryID == 1

Claro, escrever essa consulta você teria que saber que o CategoryID para bebidas é 1, que seria possível apenas se você lembrou-lo ou entrou no banco de dados para pesquisar o valor. O outro problema com esta consulta é que não está claro que está consultando o código de categoria. Ele precisa de alguma documentação em cada local que CódigoDaCategoria é usada, ou algum conhecimento por parte da pessoa que está lendo o código sobre os valores de CategoryID para várias categorias.

Outro problema surge quando você tentar inserir um novo produto. Use o código a seguir para fazer uma inserção na tabela de produtos:

var ctx = new NorthwindEntities();
var product = new Product() { ProductName = "place holder",
  Discontinued = false, CategoryID = 13 };
ctx.AddToProducts(product);
ctx.SaveChanges();

Quando você executa esse código, você obterá uma exceção de restrição de chave estrangeira do banco de dados. A exceção é lançada porque não há nenhuma categoria com um valor de ID de 13. Seria bem mais gentil se o programador tivesse uma maneira de saber a lista de categorias e atribuir uma correta em vez de ter de lembrar o conjunto de valores inteiros válidos.

Vamos apresentar enums no modelo e ver como isso melhora os cenários.

Aqui estão as etapas para alterar o CategoryID em um enum:

  1. Abra o modelo no designer clicando duas vezes no arquivo CTP1EnumsModel.edmx.
  2. A propriedade CategoryID na entidade do produto com o botão direito e escolha Converter em Enum.
  3. Criar um tipo enum e insira os valores de membros de enums na nova caixa de diálogo abre (consulte a Figura 3). Nome do categoria do tipo enum e selecione o tipo subjacente como Byte. O tipo subjacente é o tipo integral que representa o espaço de valor para enums. Você pode escolher a que ela com base no número de membros em enum. Essa enumeração específica, há oito membros, que se encaixam em um byte. Insira os membros na ordem crescente dos valores CategoryID. Digite o valor para a primeira categoria (Bebidas) como 1 e deixar o campo de valor vazio para outros membros porque os valores para os outros membros incrementam automaticamente por 1 no banco de dados. Este é o padrão escolhido pelo EF também. Mas se os valores forem diferentes no banco de dados, você digitaria-los no campo valor para todas as categorias. Se o valor de bebidas era 0 em vez de 1, você poderia deixar que esvaziar também porque o EF escolhe 0 como padrão para o primeiro membro de um enum.
  4. Ao criar um enum, você pode escolher para designá-lo como um sinalizador usando a opção "É o sinalizador?". Isso só é usado durante a geração de código; Se ela estiver marcada, o tipo enum será gerado com um atributo de sinalizadores (consulte http://bit.ly/oPqiMp para obter mais informações sobre as enumerações de sinalizadores). Neste exemplo, deixe a opção desmarcada.
  5. Recriar o aplicativo para regenerar o código e o código resultante inclui agora enums.

The Enum Type Creation Window
Figura 3 A janela de criação do tipo Enum

Você pode reescrever a consulta para obter todos os produtos que são bebidas da seguinte maneira:

var ctx = new NorthwindEntities();
var beverageProducts = from p in ctx.Products
                       where p.Category == Category.Beverages
                       select p;

Agora IntelliSense ajuda você a escrever a consulta, em vez de você ter que ir ao banco de dados para encontrar o valor de bebidas. Da mesma forma, as atualizações, IntelliSense Mostrar-me os valores corretos para a categoria.

Simplesmente examinamos enums usando uma abordagem de banco de dados primeiro. Agora, utilizarei a abordagem de código primeiro escrever a consulta para obter todas as bebidas usando enums. Para fazer isso, crie outro aplicativo de Console e adicionar um arquivo de C# com os tipos mostrados na a Figura 4.

Figura 4 usando Enums com uma abordagem de código primeira

public enum Category : byte
{
  Beverages = 1,
  Condiments,
  Confections,
  Dairy,
  Grains,
  Meat,
  Produce,
  Seafood
}
 
public class Product
{
  public int ProductID { get; set; }
  public string ProductName { get; set; }
  public int?
SupplierID { get; set; }
  [Column("CategoryID", TypeName = "int")]
  public Category Category { get; set; }
  public string QuantityPerUnit { get; set; }
  public decimal?
UnitPrice { get; set; }
  public short?
UnitsInStock { get; set; }
  public short?
UnitsOnOrder { get; set; }
  public short?
ReorderLevel { get; set; }
  public bool Discontinued { get; set; }
}
public class EnumsCodeFirstContext : DbContext
{
  public EnumsCodeFirstContext() : base(
    "data source=<server name>; initial catalog=Northwind;
    integrated security=True;multipleactiveresultsets=True;")
  {
  }
  public DbSet<Product> Products { get; set; }
}

A classe EnumsCodeFirstContext herda de DbContext. DbContext é um novo tipo que é fornecida na 4.1 a EF e é semelhante ao ObjectContext — mas é muito mais simples e mais simples de usar. (Para obter mais informações sobre como usar a API DbContext, consulte http://bit.ly/eeEsyt.)

Algumas coisas observar no código da a Figura 4:

  • O atributo de coluna acima a propriedade Category: isso é usado para mapear entre colunas e propriedades do CLR, quando os dois têm nomes diferentes ou tipos.
  • O construtor para EnumsCodeFirstContext, que chama o construtor da classe base, passando uma seqüência de conexão: por padrão, DbContext cria um banco de dados do SqlExpress local com um nome totalmente qualificado da classe que deriva do DbContext. Neste exemplo, nós simplesmente usamos o banco de dados Northwind.

Agora você pode escrever código semelhante ao contexto primeiro banco de dados para obter todos os produtos que pertencem à categoria bebidas:

EnumsCodeFirstContext ctx = new EnumsCodeFirstContext();
var beverageProducts = from p in ctx.Products
                       where p.Category == Category.Beverages
                       select p;

Funções com valor de tabela

Outro recurso significativo adicionado neste CTP é suporte para TVFs. TVFs são muito semelhantes aos procedimentos armazenados com uma diferença importante: o resultado de um TVF é Channels. Isso significa que os resultados de um TVF podem ser usados em uma consulta externa. Portanto, uma implicação importante para desenvolvedores que usam o EF é que um TVF pode ser usado em uma consulta LINQ enquanto não é possível usar um procedimento armazenado. Apresentarei um exemplo que mostra como o TVFs podem ser usados em um aplicativo EF. No processo, você verá como aproveitar a funcionalidade de pesquisa de texto completo (FTS) em SQL Server (consulte http://bit.ly/qZXG9X para obter mais informações).

Funcionalidade FTS é exposta por meio de alguns predicados e TVFs. Nas versões anteriores do EF, a única maneira de usar os TVFs de texto completo seria invocá-los em um script T-SQL usando ExecuteStoreCommand ou usar um procedimento armazenado. Mas ambos desses mecanismos não podem ser compostos e não podem ser usados em LINQ to Entities. Meu exemplo mostrará a você como usar essas funções como Channels funções com suporte para TVFs neste CTP. Para fazer isso, eu tiradas de uma consulta de documentação do MSDN para ContainsTable (http://bit.ly/q8FFws). A consulta procura por todos os nomes de produtos contendo as palavras "Pães, biscoitos," "pescar" ou "chá", e diferentes pesos são fornecidos para cada palavra. Para cada linha retornada correspondentes a estes critérios de pesquisa, é mostrada a proximidade relativa (valor de classificação) da correspondência:

    SELECT FT_TBL.CategoryName, FT_TBL.Description, KEY_TBL.RANK
      FROM Categories AS FT_TBL
        INNER JOIN CONTAINSTABLE(Categories, Description,
        'ISABOUT (breads weight (.8),
        fish weight (.4), beers weight (.2) )' ) AS KEY_TBL
          ON FT_TBL.CategoryID = KEY_TBL.[KEY]
    ORDER BY KEY_TBL.RANK DESC;

Vamos tentar escrever a mesma consulta em LINQ to Entities. Infelizmente, você não pode expor ContainsTable diretamente para o EF porque ele espera que os dois primeiros parâmetros (nome da tabela e o nome da coluna) como identificadores sem aspas — isto é, como categorias em vez de 'Categorias', e não há nenhuma maneira de saber o EF tratar esses parâmetros especialmente. Para contornar essa limitação, encapsule ContainsTable em outro TVF definida pelo usuário. Execute o seguinte SQL para criar um TVF chamado ContainsTableWrapper (o TVF executa a função ContainsTable na coluna Descrição na tabela Categorias):

    Use Northwind;
    Create Function ContainsTableWrapper(@searchstring nvarchar(4000))
    returns table
    as
    return (select [rank], [key] from ContainsTable(Categories, Description,
      @searchstring))

Agora, crie um aplicativo EF e usar esse TVF. Siga as mesmas etapas, conforme descrito no exemplo de enums criar um aplicativo de console e adicionar um modelo de entidade, apontando para o Northwind. Inclua categorias, produtos e o TVF recém-criado. O modelo será a aparência semelhante à mostrada na a Figura 5.

Entity Model with Products and Categories from Northwind
Figura 5 modelo de entidades com produtos e categorias do Northwind

O TVF não é exibido na superfície de design, mas você pode exibi-la no navegador modelo expandindo as armazenados procedimentos/funções na seção Store.

Para usar essa função em LINQ, adicione um stub de função (conforme descrito em http://bit.ly/qhIYe2). Adicionei o stub de função em uma classe parcial para a classe ObjectContext, na NorthwindEntities neste caso:

public partial class NorthwindEntities
  {
    [EdmFunction("NorthwindModel.Store", "ContainsTableWrapper")]
    public IQueryable<DbDataRecord> ContainsTableWrapper(string searchString)
    {
      return this.CreateQuery<DbDataRecord>(
        "[NorthwindModel.Store].[ContainsTableWrapper](@searchstring)",
        new ObjectParameter[] {
        new ObjectParameter("searchString", searchString)});
    }
  }

Agora você pode começar a usar essa função em suas consultas. Impressão simplesmente a chave — isto é, CategoryId e classificação para a consulta de texto completo, mencionada anteriormente:

var ctx = new NorthwindEntities();
var fulltextResults = from r in ctx.ContainsTableWrapper("ISABOUT (breads weight (.8),
  fish weight (.4), beers weight (.2) )")
                    select r;
foreach (var result in fulltextResults)
{
  Console.WriteLine("Category ID:" +  result["Key"] + "   Rank :" + result["Rank"]);
}

A saída no console quando você executa este trecho de código é como segue:

Category ID:1   Rank :15
Category ID:3   Rank :47
Category ID:5   Rank :47
Category ID:8   Rank :31

Mas isso não é a consulta que estamos tentando gravar. Aquele que desejamos realmente faz um pouco mais. Ele fornece o nome de categoria e descrição juntamente com a classificação, que é mais interessante do que apenas a identificação de categoria. Aqui está a consulta original:

    SELECT FT_TBL.CategoryName, FT_TBL.Description, KEY_TBL.RANK
      FROM Categories AS FT_TBL
        INNER JOIN CONTAINSTABLE(Categories, Description,
        'ISABOUT (breads weight (.8),
        fish weight (.4), beers weight (.2) )' ) AS KEY_TBL
          ON FT_TBL.CategoryID = KEY_TBL.[KEY]
    ORDER BY KEY_TBL.RANK DESC;

Para usar o LINQ para esta consulta, você precisará mapear o TVF para uma função de importação no EDM com um tipo complexo ou uma entidade retornar tipo, porque as importações de função que retornam tipos de linha não podem ser compostas.

Para fazer o mapeamento, eis o que você precisa fazer:

  1. Clique duas vezes a função de armazenamento no navegador para abrir a caixa de diálogo Adicionar importação de função mostrada no modelo a Figura 6.
  2. Insira o nome da função Import como ContainsTableWrapperModelFunction.
  3. Verifique a importação de função é Composable? caixa.
  4. Selecione a função ContainsTableWrapper na lista suspensa nome de função/procedimento armazenado.
  5. Clique no botão GetColumnInformation para preencher a tabela abaixo do botão com informações sobre o tipo de resultado retornado da função.
  6. Clique no botão Criar novo tipo complexo. Ele resulta na alteração da seleção de "Retorna uma coleção de" botão de rádio para complexas com um nome gerado para o tipo complexo.
  7. Clique em OK.

Mapping a TVF to a Function Import
Figura 6 o mapeamento de um TVF para uma importação de função

Para os TVFs que são mapeados para importações de função no modelo, você não precisa adicionar uma função correspondente no código porque a função é gerada para você.

Agora você pode escrever a consulta de T-SQL que estava usando a função FTS ContainsTable em LINQ, como segue:

var ctx = new NorthwindEntities();
var fulltextResults = from r in ctx.ContainsTableWrapperModelFunction("ISABOUT
  (breads weight (.8), fish weight (.4), beers weight (.2) )")
                     join c in ctx.Categories
                     on r.key equals c.CategoryID
                     select new { c.CategoryName, c.Description, Rank = r.rank };
 
foreach (var result in fulltextResults)
{
  Console.WriteLine("Category Name:" + result.CategoryName + "   Description:" +
    result.Description + "   Rank:" + result.Rank);
}

Quando você executa esse código, a saída no console é:

Category Name:Beverages   Description:Soft drinks, coffees, teas, beers, and ales   Rank:15
Category Name:Confections   Description:Desserts, candies, and sweet breads   Rank:47
Category Name:Grains/Cereals   Description:Breads, crackers, pasta, and cereal Rank:47
Category Name:Seafood   Description:Seaweed and fish   Rank:31

Suporte a tipos espacial

Agora vamos examinar um novo recurso que causou muita empolgação: suporte para tipos espaciais. Dois novos tipos foram adicionados ao EDM — DbGeometry e DbGeography. O próximo exemplo mostra como é simples para usar tipos espaciais no EF usando código primeiro.

Crie um projeto de ConsoleApplication chamado EFSpatialSample, em seguida adicione um arquivo C# para este projeto com os seguintes tipos:

namespace EFCTPSpatial
{
  public class Customer
  {
    public int CustomerID { get; set; }
    public string Name { get; set; }
    public DbGeography Location { get; set; }
  }
 
  public class SpatialExampleContext : DbContext
  {
    public DbSet<Customer> People { get; set; }
  }
}

A propriedade Location no cliente é do tipo DbGeography, que foi adicionado ao namespace System.Data.Spatial neste CTP. DbGeography é mapeado para SqlGeography no caso de SQL Server. Inserir alguns dados espaciais usando esses tipos e, em seguida, usar o LINQ para consulta dos dados (consulte a Figura 7).

Figura 7, trabalhando com dados espaciais

static void Main(string[] args)
  {
    var ctx = new SpatialExampleContext();
    ctx.Customers.Add(new Customer() { CustomerID = 1, Name = "Customer1",
      Location = DbGeography.Parse(("POINT(-122.336106 47.605049)")) });
    ctx.Customers.Add(new Customer() { CustomerID = 2, Name = "Customer2",
      Location = DbGeography.Parse(("POINT(-122.31946 47.625112)")) });
    ctx.SaveChanges();
 
    var customer1 = ctx.Customers.Find(1);
    var distances = from c in ctx.Customers                           
                    select new { Name = c.Name, DistanceFromCustomer1 =
                    c.Location.Distance(customer1.Location)};
    foreach (var item in distances)
    {
      Console.WriteLine("Customer Name:" + item.Name + ",
        Distance from Customer 1:" + (item.DistanceFromCustomer1 / 1609.344 ));
    }               
  }

O que acontece no código é bem simple. Você cria dois clientes com dois locais diferentes e especificar suas localizações usando texto conhecido (você pode ler mais sobre esse protocolo em http://bit.ly/owIhfu). Essas alterações são persistentes no banco de dados. Em seguida, o LINQ to Entities a consulta obtém a distância de cada cliente na tabela de Customer1. A distância é dividida por 1609.344, que converte de metros em quilômetros. Aqui está a saída do programa:

Customer Name:Customer1,  Distance from Customer 1:0
Customer Name:Customer2,  Distance from Customer 1:1.58929160985881

Conforme o esperado, a distância de Customer1 para 1 cliente é zero. A distância de 1 de cliente do cliente 2 está em quilômetros. A operação de distância na consulta seja executada no banco de dados usando a função STDistance. Aqui está a consulta SQL que é enviada para o banco de dados:

    SELECT 1 AS [C1], [Extent1].[Name] AS [Name], [Extent1].[Location].STDistance(@p__linq__0)
      AS [C2]
    FROM [dbo].[Customers] AS [Extent1]

Auto-Compiled consultas do LINQ

Quando você escreve um LINQ to Entities de consulta de hoje, o EF orienta o expressãoárvore gerado pelo compilador C# ou Visual Basic e converte (ou compila) em SQL. Compilação de árvore de expressão em SQL envolve alguma sobrecarga, porém, particularmente para consultas mais complexas. Para evitar ter de pagar essa penalidade de desempenho de cada vez que a consulta do LINQ é executada, você pode compilar suas consultas e, em seguida, reutilizá-los. A classe CompiledQuery permite que você pague a sobrecarga de compilação apenas uma vez e oferece a você volta um delegado que aponta diretamente para a versão compilada da consulta no cache EF.

O suporta o CTP de junho um novo recurso chamado consultas LINQ Auto-Compiled, que permite que cada LINQ to Entities de consulta é executada automaticamente obter compilado e colocado no cache de consulta EF. Toda vez que você executa a consulta posteriormente, o EF acharão em seu cache de consulta e não precisará percorrer o processo de compilação inteira novamente. Esse recurso também fornece uma melhora a consultas emitidos usando serviços de dados do WCF, pois ela usa LINQ nos bastidores. Para obter mais informações sobre árvores de expressão, consulte http://bit.ly/o5X3rA.

Resumindo

Como você pode ver, há uma série de recursos interessantes que virão na próxima versão do EF — e há ainda mais do que aqueles fui capaz de tocar aqui, como melhorias de geração de SQL para a tabela por tipo, ou TPT, mapeamento e resultado vários conjuntos de procedimentos armazenados. O CTP oferece a oportunidade de experimentar os bits e forneça comentários para corrigir quaisquer erros ou melhorar a experiência dos novos recursos. Você pode relatar os erros usando o site de conexão de desenvolvedor de dados do Microsoft (http://connect.microsoft.com/data), ou sugerir recursos novos via voz usuário EF (http://ef.mswish.net) site da Web.

Srikanth Mandadi é líder de desenvolvimento da equipe do Entity Framework.

Graças aos seguintes especialistas técnicos para revisão deste artigo: A equipe do Entity Framework