在 Entity Framework Core 中使用 SQL 视图

Posted

技术标签:

【中文标题】在 Entity Framework Core 中使用 SQL 视图【英文标题】:Working with SQL views in Entity Framework Core 【发布时间】:2016-06-30 23:19:09 【问题描述】:

例如,我有这样的模型:

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; 
 

我想在 ImageView 视图中返回 UrlImage

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

【问题讨论】:

我认为您可能需要提供更多资源。这与 MVC 有关吗? @Willwsharp 是的,它与 asp.net 核心有关 我的意思是这是一个 MVC 应用程序吗?如,您是指创建 MVC 视图吗?另外,您到底想返回什么?我认为您应该编辑您的问题以更好地反映您正在尝试做的事情。 @Willwsharp 不,这是 SQL 视图,我认为在哪个应用程序中使用它并不重要。 【参考方案1】:

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

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

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

【讨论】:

我尝试使用 [NotMapped] 属性来查看类,但 EF 仍在尝试使用视图名称创建表。 就像我说的...视图不是真正支持的。您很可能会遇到这类问题。 我不喜欢将 FromSql 绑定到 DbSet 并强制用户将类映射到他们的 DbContext。我认为以前版本的 EF 中的传统 SqlQuery 是必要的,以使人们能够迁移到 EF Core 以获得尚不支持的功能以及灵活性。我在这里创建了一个用户语音:data.uservoice.com/forums/… @MattSanders EF Core 支持context.Set<T>().FromSql()。但是,EF Core 将要求在 EF 使用的 IModel 中配置类型 T。如果您想要微型 ORM 的功能,请使用微型 ORM。 Dapper 非常适合这个。 现在是否支持 SQL 视图?还是不支持?【参考方案2】:

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; 
    

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

【讨论】:

jessedotnet.com/2016/04/13/…对我来说也是很好的参考书 它在抱怨主键。不会让我用 builder 设置它,不会让我在没有 :/ 的情况下运行 这个答案让我非常接近,我所要做的就是从模型构建器中删除 entity.Property,因为我的视图类的所有成员都是属性,因此会自动映射。【参考方案3】:

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

【讨论】:

【参考方案4】:

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

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

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

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; 

2.我们有一个名为 AuthorArticleCounts 的视图,定义为返回作者撰写的文章的名称和数量

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

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

public class AuthorArticleCount

  public string AuthorName  get; private set; 
  public int ArticleCount  get; private set; 

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

public DbQuery<AuthorArticleCount> AuthorArticleCountsget;set;

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

protected override void OnModelCreating(ModelBuilder modelBuilder)

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

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

var results=_context.AuthorArticleCounts.ToList();

更新 根据ssougnez的评论

值得注意的是,EF 不再支持/不再支持 DbQuery 核心 3.0。 见here

【讨论】:

这给了我一个例外.... System.ArgumentNullException: '值不能为空。参数名称:source' @Jackal 好吧,听起来你在数据库中有一个 Null 值,而你不应该:)。 嗨,Anastasios,我有同样的问题,但还有一个问题,我需要在视图类中包含导航属性。但它给我带来了错误:***.com/questions/56137189/… @AnastasiosSelmanis 嗯,这种方法会弹出找不到 sql 异常对象。在连接字符串中,我们应该只需要指定服务器和数据库正确吗? 值得注意的是,EF Core 3.0 不再支持/不再支持 DbQuery。【参考方案5】:

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

定义模型以匹配视图列(匹配模型类名称以匹配视图名称或使用 Table 属性。确保 [Key] 属性应用于至少一列,否则数据提取将失败 在您的上下文中添加 DbSet 添加迁移(Add-Migration) 删除或注释掉用于创建/删除要根据提供的模型创建/删除的“表”的代码 更新数据库(Update-Database)

【讨论】:

我真的不想碰自动生成的迁移代码,这是唯一让我感到困惑的地方。 @YuriiN。 - 一般来说,你不去接触生成的代码是对的。但是,迁移代码非常容易理解,风险也很低(创建表意味着创建 + 删除两个长语句)。【参考方案6】:

EF Core 支持视图,here 是细节。

此功能是在 EF Core 2.1 中以查询类型的名称添加的。在 EF Core 3.0 中,该概念被重命名为无键实体类型。 [Keyless] 数据注释在 EFCore 5.0 中可用。

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

无法定义键。 永远不会跟踪 DbContext 中的更改,因此永远不会在数据库中插入、更新或删除。 按照惯例从未被发现。 仅支持导航映射功能的子集,具体而言: 他们可能永远不会充当关系的主要目的。 他们可能没有导航到拥有的实体 它们只能包含指向常规实体的参考导航属性。 实体不能包含无键实体类型的导航属性。 需要配置 [Keyless] 数据注解或 .HasNoKey() 方法调用。 可以映射到定义查询。定义查询是在模型中声明的查询,充当无键实体类型的数据源

它的工作原理如下:

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

你应该有一个类来保存数据库视图的结果:

  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; 

【讨论】:

【参考方案7】:

可以搭建视图。只需像搭建表格一样使用 -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“创建视图”语句必须是批处理中的唯一语句,因此您需要在存在检查语句中将创建视图作为动态 Sql 运行。或者,您可以运行单独的“if exists drop view...”,然后是“create view”语句,但是如果多个测试同时运行,您不希望在另一个测试正在使用该视图时删除该视图。 示例:

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

【讨论】:

以上是关于在 Entity Framework Core 中使用 SQL 视图的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Entity Framework Core 中运行存储过程?

如何在 Entity Framework Core 中运行存储过程?

什么是自有实体?何时以及为什么在 Entity Framework Core 中使用 Owned Entity?

Entity Framework Core 中 TimeSpan 的总和

Entity Framework优化一:引发了“System.Data.Entity.Core.EntityCommandExecutionException”类型的异常

如何在 Entity Framework Core 2 中播种?