在 Entity Framework 核心中使用 SQL 视图

Working with SQL views in Entity Framework Core

比如我有这样的模型:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
} 

我想return在ImageView中查看UrlImage

我需要在哪里创建和定义 SQL 视图?

Entity Framework 核心目前不支持视图。参见 https://github.com/aspnet/EntityFramework/issues/827

也就是说,您可以通过将您的实体映射到视图来欺骗 EF 使用视图,就好像它是一个 table。这种方法有局限性。例如您不能使用迁移,您需要手动指定一个键供 EF 使用,并且某些查询可能无法正常工作。要绕过这最后一部分,您可以手动编写 SQL 查询

context.Images.FromSql("SELECT * FROM dbo.ImageView")

EF Core 不会在上下文调用中自动为 SQL 视图创建 DBset,我们可以手动添加它们,如下所示。

public partial class LocalDBContext : DbContext
{ 

    public LocalDBContext(DbContextOptions<LocalDBContext> options) : base(options)
    {

    }

    public virtual DbSet<YourView> YourView { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<YourView>(entity => {
            entity.HasKey(e => e.ID);
            entity.ToTable("YourView");
            entity.Property(e => e.Name).HasMaxLength(50);
        });
    }

}

示例视图定义如下,属性很少

using System;
using System.Collections.Generic;

namespace Project.Entities
{
    public partial class YourView
    {
        public string Name { get; set; }
        public int ID { get; set; }
    }
}

在上下文 class 中为视图和数据库集添加 class 后,您可以通过控制器中的上下文对象使用视图对象。

这是在 EF Core 中使用 SQL 视图的新方法:Query Types

Entity Framework Core 2.1 中,我们可以按照 Yuriy N 的建议使用 Query Types

可以找到有关如何使用它们的更详细的文章 here

根据本文的示例,最直接的方法是:

1.We 例如有以下实体模型来管理发布

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

2.We 有一个名为 AuthorArticleCounts 的视图,定义为 return 作者所写文章的名称和数量

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

3.We 去创建一个用于视图的模型

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

4.We 之后在我的 DbContext 中创建一个 DbQuery 属性 以使用模型中的视图结果

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

4.1。您可能需要覆盖 OnModelCreating() 并设置视图,尤其是当您的视图名称与 Class.

不同时
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Query<AuthorArticleCount>().ToView("AuthorArticleCount");
}

5.Finally我们可以很容易的得到这样的View的结果

var results=_context.AuthorArticleCounts.ToList();

更新 根据 ssougnez 的评论

It's worth noting that DbQuery won't be/is not supported anymore in EF Core 3.0. See here

QueryTypes 是 EF Core 2.1 的规范答案,但在从数据库优先方法迁移时我使用了另一种方法(视图已在数据库中创建):

  • 定义模型以匹配视图列(匹配模型 class 名称以查看名称或使用 Table 属性。确保 [Key] 属性至少应用于一列,否则数据提取会失败
  • 在您的上下文中添加 DbSet
  • 添加迁移(Add-Migration)
  • 根据提供的模型
  • 删除或注释掉 "table" 的 creation/drop 的代码为 created/dropped
  • 更新数据库(Update-Database)

EF Core 支持视图,here 是详细信息。

This feature was added in EF Core 2.1 under the name of query types. In EF Core 3.0 the concept was renamed to keyless entity types. The [Keyless] Data Annotation became available in EFCore 5.0.

它的工作方式与普通实体没有太大区别;但有一些特别之处。根据文档:

  • Cannot have a key defined.
  • Are never tracked for changes in the DbContext and therefore are never inserted, updated or deleted on the database.
  • Are never discovered by convention.
  • Only support a subset of navigation mapping capabilities, specifically:
  • They may never act as the principal end of a relationship.
  • They may not have navigations to owned entities
  • They can only contain reference navigation properties pointing to regular entities.
  • Entities cannot contain navigation properties to keyless entity types.
  • Need to be configured with a [Keyless] data annotation or a .HasNoKey() method call.
  • May be mapped to a defining query. A defining query is a query declared in the model that acts as a data source for a keyless entity type

它的工作原理如下:

public class Blog
{
   public int BlogId { get; set; }
   public string Name { get; set; }
   public string Url { get; set; }
   public ICollection<Post> Posts { get; set; }
}

public class Post
{
   public int PostId { get; set; }
   public string Title { get; set; }
   public string Content { get; set; }
   public int BlogId { get; set; }
}

如果您在数据库中没有现成的视图,您应该像下面这样创建:

db.Database.ExecuteSqlRaw(
@"CREATE VIEW View_BlogPostCounts AS 
    SELECT b.Name, Count(p.PostId) as PostCount 
    FROM Blogs b
    JOIN Posts p on p.BlogId = b.BlogId
    GROUP BY b.Name");

而且你应该有一个 class 来保存来自数据库视图的结果:

  public class BlogPostsCount
  {
     public string BlogName { get; set; }
     public int PostCount { get; set; }
  }

然后使用 HasNoKey 在 OnModelCreating 中配置无键实体类型:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder
          .Entity<BlogPostsCount>(eb =>
          {
             eb.HasNoKey();
             eb.ToView("View_BlogPostCounts");
             eb.Property(v => v.BlogName).HasColumnName("Name");
          });
    }

只需配置 DbContext 以包含 DbSet:

public DbSet<BlogPostsCount> BlogPostCounts { get; set; }

可以搭建视图。只需像构建 table 的脚手架一样使用 -Tables,仅使用视图的名称。例如,如果您的视图名称是“vw_inventory”,那么 运行 程序包管理器控制台中的此命令(用您自己的信息替换“我的...”):

PM> Scaffold-DbContext "Server=MyServer;Database=MyDatabase;user id=MyUserId;password=MyPassword" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Temp -Tables vw_inventory

此命令将在项目的 Temp 目录中创建模型文件和上下文文件。您可以将模型文件移动到您的模型目录中(记得更改命名空间名称)。您可以从上下文文件中复制您需要的内容并将其粘贴到项目中适当的现有上下文文件中。

注意:如果您想在使用本地数据库的集成测试中使用您的视图,您需要创建视图作为数据库设置的一部分。如果您要在多个测试中使用该视图,请确保添加对视图是否存在的检查。在这种情况下,由于 SQL 'Create View' 语句需要成为批处理中的唯一语句,因此您需要 运行 create view as dynamic Sql within the existence检查语句。或者你可以 运行 分开'if exists drop view...',然后'create view' statements,但是如果多个测试同时运行ning,你不希望视图在另一个测试时被删除正在使用它。 示例:

  void setupDb() {
    ...
    SomeDb.Command(db => db.Database.ExecuteSqlRaw(CreateInventoryView()));
    ...
  }
  public string CreateInventoryView() => @"
  IF OBJECT_ID('[dbo].[vw_inventory]') IS NULL
    BEGIN EXEC('CREATE VIEW [dbo].[vw_inventory] AS
       SELECT ...')
    END";