2018 年 7 月

第 33 卷,第 7 期

数据点 - EF Core 2.1 查询类型

通过Julie Lerman

Julie LermanEF Core 2.1 是此处 !并且有许多出色的新功能和改进。而不是接管整个杂志将向您介绍所有这些模板,我将与你共享新的查询类型功能,这使您能够更轻松地查询数据库而无需具有键属性来使用结果,则返回 true 的实体。

查询类型之前,可能会对数据库视图编写查询和执行存储的过程使用 EF Core,但有限制。对于视图,您必须依赖于这一事实,EF Core 不会知道您的数据库中的视图和表之间的差异。您可以创建实体的是 DbContext 模型的一部分,这些实体创建 Dbset,然后编写针对这些 Dbset 的查询。但需要注意的问题的大量附带该工作流,例如无需注意不要编辑生成的对象和意外地导致 SaveChanges 以尝试执行 update 命令,除非您的视图是可更新数据库中将失败。当执行存储的过程使用 FromSql 方法,你再次需要将绑定到数据模型,这意味着将额外的类型添加到您实际上不需要在那里的数据模型的一部分,则返回 true 实体结果。

新的查询类型可让使用视图、 存储的过程和查询您的数据库的其他方式更轻松的路径。这是因为查询类型,您可以让 EF Core 与没有键属性和映射的类型进行交互到没有主键的数据库对象。EF 一直都是依赖键,因此这是一个很大的 EF Core。此外,查询类型将帮助你避免与更改跟踪器,进行任何交互,因此无需添加代码以从意外的运行时异常不是可更新的实体与相关保护应用程序中。您甚至可以使用查询类型将映射到表,强制它们是只读的。

在本文中我探索由查询类型启用的三个功能:

  • 查询数据库视图
  • 另一个名为"定义查询"的新功能
  • 捕获与非实体类型从 Sql 查询的结果

查询类型的重要让 DbContext 生成器知道应作为一种查询类型识别类型。通过创建 DbQuery 属性的上下文中或使用 ModelBuilder.Query 方法执行该操作。这两个新。

如果已在所有使用 EF 或 EF Core,您应熟悉 DbSet,允许您查询和更新的 DbContext 通过一个特定类型的实体的 EF 类。DbQuery 是与 DbSet,包装非实体类型并使你可以执行针对视图和表的只读查询。而在 DbQuery 中包装这些类型是查询类型。

DbQuery 的 EF Core 约定的类似于 DbSet 在于 EF Core 需要 DbQuery 属性以匹配名称映射到的数据库对象的名称。

两个点应注意的是,迁移的基于映射,并且 EF Core 不能反向工程处理视图 (尚) 不能生成视图。

映射到和查询数据库视图

我将在第一个示例使用 DbQuery — 映射到数据库视图,并从该查询。此 DbQuery 假定没有已定义,一个类,以及在数据库中名为 AuthorArticleCounts 的视图:

public DbQuery<AuthorArticleCount>
  AuthorArticleCounts{get;set;}

这仅将允许你查询数据库视图。下面我们退回,不过,若要查看模型中所示图 1

图 1 示例模型的实体类

public class Magazine
{
  public int MagazineId { get; set; }
  public string Name { get; set; }
  public string Publisher { get; set; }
  public List<Article> Articles { get; set; }
}
public class Article
{
  public int ArticleId { get; set; }
  public string Title { get; set; }
  public int MagazineId { get; set; }
  public DateTime PublishDate { get;  set; }
  public Author Author { get; set; }
  public int AuthorId { get; set; }
}
public class Author
{
  public int AuthorId { get; set; }
  public string Name { get; set; }
  public List<Article> Articles { get; set; }
}

我使用一个简单的模型具有三个实体来管理发布内容:杂志、 文章和作者。

在我的数据库,除了杂志、 文章和作者表中,我有一个名为 AuthorArticleCounts,定义为返回的名称和大量文章作者已编写视图:

SELECT
  a.AuthorName,
  Count(r.ArticleId) as ArticleCount
from Authors a
  JOIN Articles r on r.AuthorId = a.AuthorId
GROUP BY a.AuthorName

我还创建了 AuthorArticleCount 类查看结果的架构相匹配。在类中,我属性 setter 私有的以使用户清楚地知道,对此类是只读的尽管 EF Core 永远不会尝试跟踪或保存的查询类型的数据。

public class AuthorArticleCount
{
  public string AuthorName { get; private set; }
  public int ArticleCount { get; private set; }
}

与位置与旨在使用其结果的类中的数据库视图,只需将它们映射在一起是 DbQuery 属性在我的 DbContext,我在上文的同一示例:

public DbQuery<AuthorArticleCount> AuthorArticleCounts{get;set;}

现在 EF Core 会高兴地使用 AuthorArticleCount 类,即使它具有没有键属性,由于 EF Core 能够识别这是一种查询类型。可以使用它来编写和执行针对数据库视图的查询。

例如,此简单的 LINQ 查询:

var results=_context.AuthorArticleCounts.ToList();

将导致以下 SQL 来发送到我的 SQLite 数据库:

SELECT "v"."ArticleCount", "v"."AuthorName"
  FROM "AuthorArticleCounts" AS "v"

结果是一组 AuthorArticleCount 对象,如中所示图 2

一对一的查询的结果
图 2 一对一的查询结果

并且用于执行查询的上下文的 ChangeTracker 完全不能识别这些对象。

这是更好的体验比过去 EF Core 和 Entity Framework 实现其中像表一样处理数据库视图,其结果必须是实体,您必须注意不要意外地使用更改跟踪器跟踪它们。

就可以执行查询,而预定义 DbContext 类中的 DbQuery。DbSet 允许此操作,以及使用 DbContext 实例的设置方法。DbQuery,可以编写使用的查询:

var results=_context.Query<AuthorArticleCount>().ToList();

配置查询类型映射

因为所有内容遵循约定,此 DbQuery 轻松地工作。Dbset 和其实体不遵循 EF Core 约定,当使用 Fluent API 或数据注释在 OnModelCreating 方法中指定正确的映射。并开始通过标识想要影响使用 ModelBuilder 实体方法的模型中的实体。就像 DbSet 获得了 DbQuery 中的同级,实体方法还具有新的同级:查询。下面是名称的使用查询方法为 AuthorArticleCounts DbQuery 指向不同,使用新 ToView 方法 (类似于 ToTable 方法) 的视图的示例:

modelBuilder.Query<AuthorArticleCount>().ToView(
  "View_AuthorArticleCounts");

查询 < T > 方法返回 QueryTypeBuilder 对象。ToView 是一个扩展方法。有多种方法可以使用优化的查询类型时。QueryTypeBuilder 有一部分 EntityTypeBuilder 方法:HasAnnotation、 HasBaseType、 HasOne、 HasQueryFilter、 IgnoreProperty 和 UsePropertyAccessMode。没有 ToView 和 ToTable 突出显示为建议的查询类型文档中的提示的很好的说明 (bit.ly/2kmQhV8)。

在关系中的查询类型

请注意 HasOne 方法。很可能要依赖 (也称为"子") 与某个实体的一对一或一对多的关系中的查询类型虽然不与另一种查询类型。另请注意,查询类型并不几乎灵活性不如实体中的关系,这是在我看来合理。并且必须以特定方式设置的关系。

我将开始作者实体和 AuthorArticleCount 之间的一对一关系。用于实现这样的最重要规则是:

  • 查询类型必须具有返回到此关系的另一端的导航属性。
  • 该实体不能为查询类型的导航属性。

在后一种情况下,如果您打算将 AuthorArticleCount 属性添加到作者,上下文会认为 AuthorArticleCount 是一个实体,并且模型生成器将失败。

我增强了具有两个更改的模型:

首先,我修改 AuthorArticleCount 包括 Author 属性:

public Author Author { get; private set; }

然后,我添加了作者和 AuthorArticleCount 之间的一对一映射:

modelBuilder.Query<AuthorArticleCount>()
            .HasOne<Author>()
            .WithOne();

现在可以执行 LINQ 查询预先加载创作导航属性,例如:

var results =_context.AuthorArticleCounts.Include("Author").ToList();

结果显示在图 3

预先加载查询类型的实体之间的一对一关系的结果
图 3 个结果的预先加载查询类型的实体之间的一对一关系

一个对多关系中的查询类型

一个对多关系还要求查询类型必须依赖结束时,永远不会的主体 (也称为父项)。若要浏览此,我创建名为 ArticleView 数据库中的文章表上的新视图:

CREATE VIEW ArticleView as select Title, PublishDate, MagazineId from Articles;

和我创建了 ArticleView 类:

public class ArticleView
{
  public string Title { get; set; }
  public Magazine Magazine { get; set; }
  public int MagazineId { get; set; }
  public DateTime PublishDate { get; set; }
}

最后,我指定 ArticleView 是一种查询类型和定义使用其中一本杂志可拥有许多 ArticleViews 的杂志实体及其关系:

modelBuilder.Query<ArticleView>().HasOne(a => a.Magazine).WithMany();

现在我可以执行检索数据的图形的查询。我将再次使用 Include 方法。请记住,有没有引用的查询类型在杂志类中,因此不能使用其 ArticleViews 一本杂志的图形查询并查看这些图形。您可以仅从导航 ArticleView 杂志,因此,这是可以执行的查询的类型:

var articles=_context.Query<ArticleView>().Include(m=>m.Magazine).ToList();

请注意,我没有创建 DbQuery,因此我在我的查询中使用查询方法。

HasOne,您可以在找到 API 文档bit.ly/2Im8UqR,提供了有关使用此方法的更多详细信息。

新定义的查询功能

除了 ToView,永远不会存在于 EntityTypeBuilder,QueryTypeBuilder 上没有其他的一种新方法和是 ToQuery。ToQuery,可直接在 DbContext 中定义的查询,此类查询称为"定义查询"。 可以编写 LINQ 查询,并撰写定义查询时,甚至使用 FromSql。EF 团队的 Andrew Peters 介绍的"ToQuery 的用途之一是使用内存中提供程序进行测试。如果我的应用程序使用数据库视图,我还可以定义仅当我以内存中目标将使用 ToQuery。这样我可以模拟用于测试的数据库视图。"

若要开始,我创建了 MagazineStatsView 类来使用查询的结果:

public class MagazineStatsView
{
  public MagazineStatsView(string name, int articleCount, int authorCount)
  {
    Name=name;
    ArticleCount=articleCount;
    AuthorCount=authorCount;
  }
  public string Name { get; private set; }
  public int ArticleCount { get; private set; }
  public int AuthorCount{get; private set;}
}

然后,在 OnModelCreating 中杂志实体,该查询创建一个定义查询并生成从结果 MagazineStatsView 对象:

modelBuilder.Query<MagazineStatsView>().ToQuery(
      () => Magazines.Select(  m => new MagazineStatsView(
                     m.Name,
                     m.Articles.Count,
                     m.Articles.Select(a => a.AuthorId).Distinct().Count()
                    )
                )
  );

我还可以创建 DbQuery 以便更容易被发现,我定义的新查询,但我希望您可以看到,我仍然可以使用此不显式 DbQuery 的情况下。  下面是 MagazineStatsView LINQ 查询。它始终将由定义的查询处理:

var results=_context.Query<MagazineStatsView>().ToList();

根据我使用植入到数据库中所示的查询的结果的数据图 4,错误显示两个项目和一个唯一 MSDN 杂志 》,作者和使用两个唯一作者为新建 Yorker 两篇文章。

使用定义查询进行查询的结果
图 4: 使用定义查询进行查询的结果

捕获非实体类型中的 FromSql 结果

在以前版本的实体框架,这是可以执行原始 SQL 并捕获这些结果中随机的类型。我们会更接近于能够执行此类型的查询,由于查询类型。EF Core 2.1,你想要使用捕获的原始 SQL 查询结果的类型不一定是一个实体,但它仍必须将模型作为一种查询类型已知。

是一个例外,即,它是 (使用大量的限制) 可能返回匿名类型。即使此有限的支持仍可能很有用,因此值得注意。下面是返回匿名类型使用 FromSql 和原始 SQL 查询的查询:

context.Authors.FromSql("select authorid,authorname from authors").ToList();

通过查询实体返回匿名类型只适用于投影包括 DbSet 表示的类型的主键。如果我没有包括作者 Id,运行时错误会抱怨作者 Id 不可在投影中。或者,如果我开始与上下文。与我刚才介绍的相同查询 Magazines.FromSql,运行时错误会抱怨 MagazineId 不可用。

更好地利用此功能是预定义类型,并确保在 DbContext 都知道该类型,通过定义 DbQuery 或在 OnModelCreating 中指定的类型的 modelBuilder.Query。然后可以使用从 Sql 来查询和捕获结果。正如有点不太自然的示例中,或可能是我应该说更多精心设计的比一些我已使用的示例,下面是一个新类,发布服务器上,不是某一实体或我 PublicationsContext 的一部分:

public class Publisher
{  
  public string Name { get; private set; }
  public int YearIncorporated { get; private set; }
}

也是只读类,因为有另一个应用程序,我会保留发布服务器数据。

我创建了我的上下文中名为发布者 DbQuery < 发布服务器 > 并现在我可以使用它来执行原始 SQL 查询:

var publishers=_context.Publishers
                  .FromSql("select name, yearfounded from publishers")
                  .ToList();

原始 SQL 还可以执行存储的过程的调用。只要结果的架构匹配的类型 (在此情况下,发布服务器),您可以这样做,甚至在参数中传递。

EF core 将波兰语

如果你从未使用使用 EF Core,直到它已生产就绪,最后到的时间。EF Core 2.0 中特性和功能,所做的很好的突破,版本 2.1 现在包括实际波兰语置于该产品的功能。从 EF6 才会出现在 EF Core 功能等待已部分进行这一事实,EF 团队已不只会复制旧实现但找到更智能、 更实用的实现。查询类型是一个很好的示例,相比,在早期版本的实体框架支持视图和原始 SQL 的方式。请务必阅读上的 EF Core 文档的"EF Core 2.1 中新功能"部分查看 EF Core 2.1 中的其他新功能bit.ly/2IhyHQR


Julie Lerman住在佛蒙特州的丘陵地区,担任 Microsoft 区域主管、Microsoft MVP、软件团队导师和顾问。可以在全球的用户组和会议中看到她对数据访问和其他主题的介绍。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。通过 Twitter 关注她:@julielerman 并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Andrew Peters
Andrew Peters 是实体框架团队的首席工程师。在团队他 9 年,Andrew 参与过,在其他操作、 LINQ、 Code First 和迁移,并且是 EF Core 的体系结构潜在客户之一。在他的业余时间 Andrew 喜欢吉他,游戏,烹饪和与家人年轻投入的时间。


在 MSDN 杂志论坛讨论这篇文章