EntityFramework Core使用原生SQL语句,执行存储过程和视图

参考资料:
微软MVP杨旭教程:https://www.bilibili.com/video/BV1xa4y1v7rR?p=10

在EF Core项目中,如果想在数据库中添加视图或者存储过程或者类似的东西,不可以直接操作数据库,而应该把生成视图或生成存储过程的脚本放在一个Migration里面,让它来执行生成视图或者创建存储过程。

创建视图和存储过程

直接Add一个空的Migration,然后再修改Migration的代码。空的Migration:

public partial class AddView : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {

    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {

    }
}

然后使用migrationBuilder.Sql(),把SQL脚本用@"xxxx"包裹起来当作参数放进去。创建视图和存储过程的脚本都应该放在Up方法中,先创建视图再创建存储过程:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql(
        @"CREATE VIEW [dbo].[PlayerClubView]
            AS SELECT p.Id as PlayerId, p.Name as PlayerName, c.Name as ClubName
            FROM [dbo].[Players] as p
            INNER JOIN [dbo].[Clubs] as c
            ON p.ClubId = c.Id");
    migrationBuilder.Sql(
        @"CREATE PROCEDURE [dbo].[RemoveGamePlayersProcedure] @playerId int = 0
            AS 
                DELETE FROM [dbo].[GamePlayers] WHERE [PlayerId] = @playerId
            RETURN 0");
}

同时Down方法中应该包含回滚的操作,如果更新失败就回滚,回滚时应该先删除存储过程再删除视图:

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.Sql(
        @"DROP PROCEDURE [dbo].[RemoveGamePlayersProcedure]");
    migrationBuilder.Sql(
        @"DROP VIEW [dbo].[PlayerClubView]");
}

Update-Database之后,看一下数据库:

EntityFramework Core使用原生SQL语句,执行存储过程和视图

可以看到视图和存储过程全都有了。

无主键的Entity

我们建立的实体Model基本都有主键,而且基本都是Id,但:

  • .NET Core 3.1 允许无主键的Entity
  • 它们不会被追踪
  • 映射到没有主键的Table或者View

我们根据刚才建立的视图建立一个没有主键的模型:

public class PlayerClub
{
    public int PlayerId { get; set; }
    public string PlayerName { get; set; }
    public string ClubName { get; set; }
}

然后把这个类添加到Context里的DbSet属性。但DbSet不识别没有主键的类,我们需要再OnModelCreating中设置一下,用HasNoKey()方法设置PlayerClub这个Entity。但只这样设置还不足够,如果后面再添加迁移,它会认为我们想要创建PlayerClubs这样一个Table,所以要用ToView()方法把它映射到我们前面创建的视图[dbo].[PlayerClubView]上。需要稍微修改一下视图名,去掉中括号和dbo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    ...
    modelBuilder.Entity<PlayerClub>()
        .HasNoKey()
        .ToView("PlayerClubView");
}

针对这种没有主键的Entity,查出来的结果都是无法追踪的。

然后我们像查表一样查一下视图:

var playerClubs = context.PlayerClubs.ToList();

EntityFramework Core使用原生SQL语句,执行存储过程和视图

这种查询也可以加Where查询条件:

var playerClubs = context.PlayerClubs
    .Where(x => x.PlayerId > 1)
    .ToList();

但如果使用DbSet的Find()方法,在编译的时候不会报错,在运行的时候会报错。因为Find后面跟的是主键,而这个视图没有主键。

原生SQL查询

一共有两种方法,还有它们各自的异步版本:

  • FromSQLRaw("SELECT" * ...")
  • FromSQLRawAsync("SELECT" * ...")
  • FromSQLInterpolated($"SELECT * WHERE x={var}")
  • FromSQLInterpolatedAsync($"SELECT * WHERE x={var}")

第二种方法支持C#6出现的字符串插值。这几种方法都是DbSet的方法,所以只能针对DbSet来执行这个方法。最后还要调用ToList()等方法,否则查询不会执行。

var leagues = context.Leagues
    .FromSqlRaw("SELECT * FROM dbo.Leagues")
    .ToList();

EntityFramework Core使用原生SQL语句,执行存储过程和视图

也可以加查询条件和Include()等。过滤条件可以在SQL语句里面写,在外面写没有太大意义:

var clubs = context.Clubs
    .FromSqlRaw("SELECT * FROM dbo.Clubs")
    .Include(x => x.League)
    .Include(x => x.Players)
        .ThenInclude(x => x.GamePlayers)
    .ToList();

原生SQL查询的要求

  • 必须返回Entity类型的所有(标量)属性

    也就是SELECT *,如果写列名必须一个不多一个不少,而且不包含导航属性

  • 字段名和Entity的属性名匹配
  • 无法包含关联的数据

    指在SQL语句中无法包含关联的数据

  • 只能查询已知的Entity

字符串插值

插值的部分在生成的SQL语句中也是SQL参数。

var id = 0;

var clubs = context.Clubs
    .FromSqlInterpolated($"SELECT * FROM dbo.Clubs WHERE Id > {id}")
    .ToList();

EntityFramework Core使用原生SQL语句,执行存储过程和视图

如果数据库中有对应的Club类的存储过程的话,Clubs.FromSqlInterpolated()Clubs.FromSqlRaw()也可以执行存储过程。前提是要求存储过程返回的字段必须与Club类匹配。

执行非查询类SQL

执行非查询类SQL,包括执行非查询类的存储过程,不能使用DbSet的方法,应当使用Context的Database属性,它有下面两种方法,各自还有一个异步方法。。

  • Context.Database.ExecuteSQLRaw()
  • Context.Database.ExecuteSQLRawAsync()
  • Context.Database.ExecuteSQLInterpolated()
  • Context.Database.ExecuteSQLInterpolatedAsync()
  • 无法用于查询
  • 只能返回影响的行数
var count = context.Database
    .ExecuteSqlRaw("EXEC dbo.RemoveGamePlayersProcedure {0}", 2);

count = context.Database
    .ExecuteSqlInterpolated($"EXEC dbo.RemoveGamePlayersProcedure {2}");

可以看到无论那种方法,都使用了参数形式,因为这是非查询类SQL语句,不使用参数的话很容易被SQL注入。

EntityFramework Core使用原生SQL语句,执行存储过程和视图

EntityFramework Core使用原生SQL语句,执行存储过程和视图

上一篇:MySQL 查询所有存储过程视图 等等


下一篇:adb链接时报错误10061解决方法