Entity Framework

Entity Framework June CTP 中的新功能

Srikanth Mandadi

 

最近发行的 Microsoft 实体框架 (EF) 6 月 2011 CTP 包括支持频繁请求的功能,如枚举、 空间类型和表值函数 (TVFs) 的数量。 我们来看一看这些功能,使用简单的演练。 我我假定您熟悉 EF (http://bit.ly/oLbjp0) 和与第一个代码的开发模式 (http://bit.ly/oQ77Hm) EF 4.1 版中引入。

下面是您需要能够尝试使用本文中的示例:

  • Visual Studio 2010年速成版和 SQL Server 2008 R2 表达或更高版本。 您可以下载的 Visual Studio 和 SQL Server Express 从速成版 http://bit.ly/rsFvxJ
  • Microsoft EF 和 EF 工具 6 月 2011 CTP。 您可以下载这些从 http://bit.ly/mZgQIS
  • 在罗斯文数据库中。 您可以下载它从 http://bit.ly/pwbDoQ

现在,我们开始吧。

枚举

让我们开始在 EF 最迫切需要的功能之一 — 枚举。 许多编程语言,包括。NET C# 和 Visual Basic 等语言中,具有用于枚举的支持。 在 EF,目标是允许用户在他们的 CLR 类型中有枚举,并将它们映射到底层实体数据模型 (EDM),并进而允许保持这些值写入数据库。 之前进入明细数据,让我们看一个简单的示例。 枚举中的第一个代码、 第一个数据库和模型第一个方法支持。 我将开头的第一个数据库的方法,然后显示使用的第一个代码的方法的示例。

对于数据库第一个示例中,您将在罗斯文数据库中使用产品表。 您需要确保您目标 EF 6 月 2011 CTP 之前添加模型,所以应确保:

  1. 启动 Visual Studio 2010年并创建一个新的 C# 控制台应用程序项目。
  2. 您的项目在解决方案资源管理器中右击并选择属性。
  3. 从下拉列表中的目标框架中选择 Microsoft 实体框架 6 月 2011 CTP (请参阅图 1)。
  4. 按 Ctrl + S 保存项目。 Visual Studio 索要权关闭并重新打开该项目。 单击是。
  5. 向项目中添加一个新的模型,通过右击项目 |添加新项 (或 Ctrl + Shift + A),并选择 ADO。NET 数据实体模型从 Visual C# 项 (我们称为我们"CTP1EnumsModel.edmx"),然后单击添加。
  6. 指向罗斯文数据库向导的步骤。 选择产品表,然后单击完成。
  7. 实体模型,从而从这些选项中有一个实体,如中所示图 2

Targeting the Entity Framework June 2011 CTP
图 1 的目标实体框架 6 月 2011 CTP

Entity Model for Product Entity
图 2 为产品实体的的实体的模型

下面是一个 LINQ 查询以获得属于饮料类别的所有产品。 请注意类别 id 饮料 1:

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

当然,以编写该查询,您将不得不知道饮料的类别 Id 为 1,它仅可以如果记住它或进入到数据库以查找该值。 与此查询的另一个问题是它是无法清除代码查询用于哪些类别。 它需要使用类别 id,则每个位置中的某些文档,或者阅读有关类别 id 值的代码的各种类别的人员的一些知识。

当您试图插入新产品的另一个问题会弹出。 使用下面的代码到 Products 表中执行插入操作:

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

在运行此代码时,您将从数据库有一个外键约束的例外。 因为没有任何类别 ID 值为 13,则引发异常。 如果程序员必须知道的类别列表并分配一个正确的不必记住的一组有效的整数值 (而不是一种方法,它就会更好些。

让我们枚举引入模型,请参阅此过程如何提高方案。

类别 id 转化为枚举的步骤如下:

  1. 在设计器中打开模型,通过双击 CTP1EnumsModel.edmx 文件。
  2. 用鼠标右键单击产品实体中的类别 id 属性,然后选择转换为枚举。
  3. 创建一个枚举类型和枚举成员,可打开新建对话框中输入的值 (请参阅图 3)。 命名的枚举类型类别并选择作为字节的基础类型。 表示用于枚举的值空间的整数类型的基础类型。 您可以选择基于枚举中的成员数。 对于此特定的枚举,有八个成员,一个字节中容纳不下。 在类别 id 值的升序顺序输入成员。 输入第一个类别 (饮料) 的值为 1,并将 [值] 字段保留为空的其他成员,因为其它成员的值自动递增 1 在数据库中。 这是由 EF 以及选择默认值。 但是,如果值是不同的数据库中,应该输入它们在 [值] 字段中的所有类别。 如果饮料的值是 0,而不是 1,您可以保持清空也因为 EF 选择作为默认值 0 枚举的第一个成员的。
  4. 在创建枚举时,您可以选择将其指定为一个标志,使用"是标志?"选项。 代码生成; 期间才使用此选项 如果选中它,则将生成具有标记属性的枚举类型 (请参阅 http://bit.ly/oPqiMp 标志枚举上的详细信息)。 对于本例,请取消选中该选项。
  5. 重新生成应用程序以重新生成代码,并生成的代码现在包括枚举。

The Enum Type Creation Window
图 3 枚举类型创建窗口

您可以重写查询以获得如下饮料的所有产品:

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

现在 IntelliSense 可以帮助您编写查询,而不是无须转到该数据库所要查找的值的饮料。 同样,在更新 IntelliSense 将显示正确的值的类别。

我们刚才在使用数据库第一种方法的枚举。 现在我将使用代码第一种方法来编写查询以获得使用枚举所有饮料。 要这样做,请创建另一个控制台应用程序并添加一个 C# 文件中显示的类型与图 4

图 4 代码的第一种方法与使用枚举

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; }
}

类继承从 DbContext 的 EnumsCodeFirstContext。 DbContext 是一种新类型的发货 EF 4.1 中,它类似于 ObjectContext — — 但是要简单得多,并且要使用更简单。 (使用 DbContext API 的详细信息,请参阅 http://bit.ly/eeEsyt。)

几项事情需要注意的代码中图 4

  • 上面的类别属性将列属性: 这用于这两个具有不同名称或类型时,CLR 属性和列之间映射。
  • EnumsCodeFirstContext,在连接字符串中传递的方式来调用基类构造函数的构造函数: 默认情况下,由 DbContext 中创建一个数据库是从 DbContext 派生的完全限定的类名与本地 SqlExpress。 对于本示例,我们只需使用现有的罗斯文数据库。

现在,您可以编写代码类似于第一个数据库上下文获取属于饮料类别的所有产品:

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

表值函数

在此 CTP 中添加另一个重要的功能是对 TVFs 的支持。 TVFs 是非常类似于存储过程的不同在于: TVF 的结果是可组合。 这意味着从 TVF 结果可用于在外部查询中。 因此,开发人员使用 EF 主要含意是 TVF 可使用 LINQ 查询中同时存储的过程不能。 本文将简要介绍一个示例,演示如何在 EF 应用程序中使用 TVFs。 在此过程中,您将看到如何利用在 SQL Server 中的全文本搜索 (FTS) 功能 (请参阅 http://bit.ly/qZXG9X 的详细信息)。

通过几个谓词和 TVFs 被公开 FTS 功能。 在以前版本的 EF,若要使用全文 TVFs 的唯一方法是使用 ExecuteStoreCommand T SQL 脚本中调用这些或使用存储的过程。 但这两种机制是不可以组合和 LINQ to Entities 中不能使用。 我的示例将显示如何使用这些函数作为可组合函数与此 CTP 中 TVFs 的支持。 这样做,已经所采取的 MSDN 文档 ContainsTable 查询 (http://bit.ly/q8FFws)。 查询搜索所有产品名称中包含单词"breads,""钓取"或"beers,"并提供每个单词的不同重量。 对于这些搜索条件匹配的每个返回行,显示匹配的相关程度 (等级值):

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;

我们可以试着在 LINQ to Entities 中编写相同的查询。 遗憾的是,您不能公开直接向 EF ContainsTable,因为它期望为不带引号标识符的前两个参数 (表名和列名) — — 也就是说,作为类别,而不是类别,并没有告诉 EF 专门处理这些参数的方法。 若要解决此限制,请在另一个用户定义 TVF 环绕 ContainsTable。 执行下面的 SQL 来创建名为 ContainsTableWrapper (TVF 类别表中的说明列执行的 ContainsTable 函数) TVF:

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

现在,创建 EF 应用程序,并使用此 TVF。 如所述创建控制台应用程序并通过指向罗斯文添加实体模型枚举示例,请按照相同的步骤。 包括类别、 产品和新创建的 TVF。 模型看起来类似中所示图 5

Entity Model with Products and Categories from Northwind
图 5 实体模型与产品和类别,从罗斯文

TVF 时不显示设计器图面,但您可以通过扩展存储过程/函数的存储部分中的模型浏览器中查看。

若要使用此函数在 LINQ 中,添加函数存根 (stub) (如所述,在 http://bit.ly/qhIYe2)。 我为此案例 NorthwindEntities 中的 ObjectContext 类的分部类中添加函数存根 (stub):

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)});
    }
  }

您现在可以开始在查询中使用该函数。 只打印出密钥 — — 即类别 Id 和全文查询,正如前面提到的秩:

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"]);
}

当您运行这段代码时在控制台上的输出如下所示:

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

但这不是我们试图编写的查询。 我们希望在的一个实际作用稍多一些。 它提供了类别名称和说明,并连同其排位,这是更有趣比只是 id 类别。 以下是原始查询:

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;

若要为此查询使用 LINQ,您需要将 TVF EDM 中函数导入到映射具有复杂类型或实体返回类型,因为返回的行类型的函数导入不是可组合。

为映射,下面是您的需要:

  1. 双击要打开添加函数导入对话框中所示的模型浏览器中的存储函数图 6
  2. 输入函数导入名称为 ContainsTableWrapperModelFunction。
  3. 检查函数导入是 Composable 吗? 名称。
  4. 为存储过程/函数名称,请从下拉列表中选择 ContainsTableWrapper 函数。
  5. 单击 GetColumnInformation 按钮来填充下面的按钮从该函数返回的结果类型有关的信息的表。
  6. 单击创建新的复杂类型按钮。 这会导致更改为该复杂类型生成的名称与复杂的选择"返回集合的"单选按钮。
  7. 单击“确定”。

Mapping a TVF to a Function Import
图 6 将 TVF 映射函数导入到

对于 TVFs 映射函数导入到模型中,不需要在代码中添加相应的功能,因为该函数会为您生成。

现在,您可以编写了 FTS 函数 ContainsTable 在 LINQ 中,按如下方式使用 T SQL 查询:

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);
}

当您运行此代码时,请在控制台上的输出为:

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

空间类型支持

现在让我们看一下导致了大量刺激性的一种新功能: 支持空间的类型。 两个新类型已添加到 EDM — DbGeometry 和 DbGeography。 下一个示例将介绍在 EF 中使用空间类型是如何简单使用第一个代码。

创建名为 EFSpatialSample,ConsoleApplication 项目,然后将 C# 文件添加到此项目,与以下类型:

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; }
  }
}

在客户中的位置属性属于类型 DbGeography,已添加到此 CTP 中的 System.Data.Spatial 命名空间。 DbGeography 被映射到 SqlGeography SQL Server 的情况下。 插入使用这些类型的一些空间数据,然后使用查询 LINQ 的数据 (请参阅图 7)。

图 7 使用空间数据

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 ));
    }               
  }

怎样在代码中是相当简单。 您创建具有两个不同位置的两个客户,并指定它们使用众所周知的文本的位置 (可以阅读更多有关在此协议 http://bit.ly/owIhfu)。 这些更改将保存到数据库中。 接下来,查询 LINQ to Entities Customer1 从获取表中的每个客户的距离。 距离除以 1609.344,将其从转换米为英里。 下面是该程序的输出结果:

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

像预期的那样,从 Customer1 到客户 1 的距离为零。 从客户 2 客户 1 的距离正在英里。 在查询中的距离操作获取使用 STDistance 函数在数据库中执行。 以下是获取发送到数据库 SQL 查询:

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

自动编译 LINQ 查询

在今天编写查询时 LINQ to Entities,带领 EF 表达式树由 C# 或 Visual Basic 编译器生成和转换 (或编译) 到 SQL。 编译为 SQL 的表达式目录树涉及一些开销,但是,特别是对于更复杂的查询。 要避免支付这种性能下降,每次执行 LINQ 查询时,可以编译您的查询,然后重新使用它们。 CompiledQuery 类允许您支付的只是一次,编译系统开销,并提供回直接指向 EF 高速缓存中的查询的已编译版本的委托。

一种称为 Auto-Compiled LINQ 查询,它允许您查询的每个 LINQ to Entities 的新功能自动执行该 6 月 CTP 支持获取编译且放置 EF 查询缓存中。 每次您随后运行查询时,EF 其查询缓存中可以找到它,并就不必再次经过整个编译过程。 此功能还提供了提升发出使用 WCF 数据服务,如它在幕后使用 LINQ 查询。 表达式目录树的详细信息,请参阅 http://bit.ly/o5X3rA

总结

正如您所见,有许多令人兴奋的多功能 EF 的下一发行版中即将推出的 — — 并有更多比我已经能够在这里,如 SQL 代改进,为每种类型,表上触摸或从存储过程的 TPT、 映射和多个结果集。 CTP 您有机会试用位并提供修复任何错误或愉悦的新功能的反馈。 您可以报告使用 Microsoft 数据开发人员连接站点的任何错误 (http://connect.microsoft.com/data),或建议通过 EF 用户声音的新功能 (http://ef.mswish.net) Web 站点。

Srikanth Mandadi 是开发组长在实体框架团队中。

这要归功于以下的技术专家审阅这篇文章: 实体框架团队