zoukankan      html  css  js  c++  java
  • 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之后,看一下数据库:

    UTOOLS1593166699745.png

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

    无主键的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();
    

    UTOOLS1593168338482.png

    这种查询也可以加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();
    

    UTOOLS1593169259412.png

    也可以加查询条件和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();
    

    UTOOLS1593169799606.png

    如果数据库中有对应的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注入。

    UTOOLS1593180643043.png

  • 相关阅读:
    预处理、const、static与sizeof-为什么不把所有的函数都定义成内联函数
    预处理、const、static、sizeof-说明内联函数使用的场合
    预处理、const、static、sizeof-为什么inline能很好地取代表达式形式的预定义
    预处理、const、static与sizeof-为什么要引入内联函数
    预处理、const、static与sizeof-#pragma pack的作用
    预处理、const、static与sizeof-sizeof与strlen有哪些区别
    预处理、const、static与sizeof-static全局变量与普通的全局变量有什么区别
    预处理、const、static与sizeof-static有什么作用(至少说出2个)
    预处理、const、static与sizeof-C++中const有什么作用(至少说出3个)
    预处理、const、static与sizeof-使用const与#define的特点及区别
  • 原文地址:https://www.cnblogs.com/Kit-L/p/13196594.html
Copyright © 2011-2022 走看看