ASP.NET Core 3.1系列(18)——EFCore中执行原生SQL语句

news/2024/11/8 16:42:49/

1、前言

前一篇博客介绍了EFCore中常见的一些查询操作,使用LinqLambda结合实体类的操作相当方便。但在某些特殊情况下,我们仍旧需要使用原生SQL来获取数据。好在EFCore中提供了完整的方法支持原生SQL,下面开始介绍。

2、构建测试数据库

与之前一样,还是使用AuthorBook数据表,它们是一对多的关系,AuthorIdBook表中的外键。

Author表数据如下所示:

IdNameGenderAgeEmail
1张三3511111111@qq.com
2李四4022222222@qq.com
3王五3733333333@qq.com

Book表数据如下所示:

IdTitlePressPublicationTimePriceAuthorId
1《C程序设计》A出版社2022-01-01301
2《C++程序设计》B出版社2022-02-02451
3《Java程序设计》C出版社2022-03-03602
4《C#程序设计》D出版社2022-04-04552

Author代码如下:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disablenamespace App.Models
{public partial class Author{public Author(){Book = new HashSet<Book>();}/// <summary>/// 主键/// </summary>[Key]public int Id { get; set; }/// <summary>/// 姓名/// </summary>[StringLength(20)]public string Name { get; set; }/// <summary>/// 性别/// </summary>[StringLength(2)]public string Gender { get; set; }/// <summary>/// 年龄/// </summary>public int? Age { get; set; }/// <summary>/// 邮箱/// </summary>[StringLength(30)]public string Email { get; set; }/// <summary>/// 导航属性/// </summary>[InverseProperty("Author")]public virtual ICollection<Book> Book { get; set; }}
}

Book代码如下:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disablenamespace App.Models
{public partial class Book{/// <summary>/// 主键/// </summary>[Key]public int Id { get; set; }/// <summary>/// 书名/// </summary>[StringLength(20)]public string Title { get; set; }/// <summary>/// 出版社/// </summary>[StringLength(20)]public string Press { get; set; }/// <summary>/// 出版时间/// </summary>[Column(TypeName = "datetime")]public DateTime? PublicationTime { get; set; }/// <summary>/// 价格/// </summary>[Column(TypeName = "money")]public decimal? Price { get; set; }/// <summary>/// 外键:AuthorId/// </summary>public int? AuthorId { get; set; }/// <summary>/// 导航属性/// </summary>[ForeignKey(nameof(AuthorId))][InverseProperty("Book")]public virtual Author Author { get; set; }}
}

DaoDbContext代码如下:

using App.Models;
using Microsoft.EntityFrameworkCore;// Code scaffolded by EF Core assumes nullable reference types (NRTs) are not used or disabled.
// If you have enabled NRTs for your project, then un-comment the following line:
// #nullable disablenamespace App.Context
{public partial class DaoDbContext : DbContext{public DaoDbContext(){}public DaoDbContext(DbContextOptions<DaoDbContext> options): base(options){}public virtual DbSet<Author> Author { get; set; }public virtual DbSet<Book> Book { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){if (!optionsBuilder.IsConfigured){optionsBuilder.UseSqlServer("Data Source=DSF-PC;Initial Catalog=Dao;User ID=sa;Password=123456;");}}protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Book>(entity =>{entity.HasOne(d => d.Author).WithMany(p => p.Book).HasForeignKey(d => d.AuthorId).OnDelete(DeleteBehavior.Cascade).HasConstraintName("FK_Book_Author");});OnModelCreatingPartial(modelBuilder);}partial void OnModelCreatingPartial(ModelBuilder modelBuilder);}
}

3、执行原生SQL查询操作

3.1、FromSqlInterpolated

如果是针对单表的查询操作,可以使用FromSqlInterpolated方法,但是该方法有以下局限性:

  • 只支持单表查询,不支持Join
  • 必须返回全部列
  • 结果集中的列名必须与数据库中的列名对应

下面将查询查询Author表中Name='张三'Age>30的记录,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<List<Author>> Get(){return GetAuthors("张三", 30);}private List<Author> GetAuthors(string name, int age){return _dbContext.Set<Author>().FromSqlInterpolated(@$"select * from Author where Name={name} and Age>{age}").ToList();}}
}

运行结果如下所示:

[{"id":1,"name":"张三","gender":"男","age":35,"email":"11111111@qq.com","book":[]}]

如果希望查询出Author对应的Book,也可以通过Include实现,EFCore支持FromSqlInterpolatedLambda一起使用,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<List<Author>> Get(){return GetAuthors("张三", 30);}private List<Author> GetAuthors(string name, int age){return _dbContext.Set<Author>().FromSqlInterpolated(@$"select * from Author where Name={name} and Age>{age}").Include(p => p.Book).ToList();}}
}

运行结果如下所示:

[{"id": 1,"name": "张三","gender": "男","age": 35,"email": "11111111@qq.com","book": [{"id": 1,"title": "《C程序设计》","press": "A出版社","publicationTime": "2021-01-01T00:00:00","price": 30.0000,"authorId": 1},{"id": 2,"title": "《C++程序设计》","press": "B出版社","publicationTime": "2021-02-02T00:00:00","price": 45.0000,"authorId": 1}]}
]

3.2、FromSqlRaw

针对单表的查询也可以使用FromSqlRaw方法,与FromSqlInterpolated类似,该方法也必须返回全部列,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<List<Author>> Get(){return GetAuthors("张三", 30);}private List<Author> GetAuthors(string name, int age){SqlParameter[] parameters ={new SqlParameter(@"Name", name),new SqlParameter(@"Age", age)};return _dbContext.Set<Author>().FromSqlRaw("select * from Author where Name=@Name and Age>@Age", parameters).ToList();}}
}

运行结果如下所示:

[{"id":1,"name":"张三","gender":"男","age":35,"email":"11111111@qq.com","book":[]}]

FromSqlRaw方法也可以与Lambda一起使用,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<List<Author>> Get(){return GetAuthors(1);}private List<Author> GetAuthors(int id){SqlParameter[] parameters ={new SqlParameter(@"Id", id),};return _dbContext.Set<Author>().FromSqlRaw("select * from Author where Id>@Id", parameters).OrderByDescending(p => p.Age).Include(p => p.Book).ToList();}}
}

运行结果如下所示:

[{"id": 2,"name": "李四","gender": "女","age": 40,"email": "22222222@qq.com","book": [{"id": 3,"title": "《Java程序设计》","press": "C出版社","publicationTime": "2021-03-03T00:00:00","price": 60.0000,"authorId": 2},{"id": 4,"title": "《C#程序设计》","press": "D出版社","publicationTime": "2021-04-04T00:00:00","price": 55.0000,"authorId": 2}]},{"id": 3,"name": "王五","gender": "男","age": 37,"email": "33333333@qq.com","book": []}
]

4、执行原生SQL非查询操作

4.1、ExecuteSqlInterpolated

如果希望执行增删改等非查询操作,可以使用ExecuteSqlInterpolated方法,下面给Author表添加一条记录,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<string> Get(){int result = AddAuthor(new Author{Name = "AAA",Gender = "男",Age = 33,Email = "44444444@qq.com"});return result > 0 ? "添加数据成功" : "添加数据失败";}private int AddAuthor(Author author){string name = author.Name;string gender = author.Gender;int age = author.Age.HasValue ? author.Age.Value : 0;string email = author.Email;return _dbContext.Database.ExecuteSqlInterpolated(@$"insert into Author(Name,Gender,Age,Email) values({name},{gender},{age},{email})");}}
}

运行结果如下所示:

添加数据成功

在这里插入图片描述

4.2、ExecuteSqlRaw

ExecuteSqlRaw方法也可以执行增删改等非查询操作,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<string> Get(){int result = AddAuthor(new Author{Name = "BBB",Gender = "女",Age = 42,Email = "55555555@qq.com"});return result > 0 ? "添加数据成功" : "添加数据失败";}private int AddAuthor(Author author){SqlParameter[] parameters ={new SqlParameter(@"Name", author.Name),new SqlParameter(@"Gender", author.Gender),new SqlParameter(@"Age", author.Age),new SqlParameter(@"Email", author.Email)};return _dbContext.Database.ExecuteSqlRaw("insert into Author(Name,Gender,Age,Email) values(@Name,@Gender,@Age,@Email)", parameters);}}
}

运行结果如下所示:

添加数据成功

在这里插入图片描述

5、执行数据库事务

如果希望执行数据库事务,可以使用BeginTransaction方法,下面代码执行了一个包含InsertUpdate的事务:

using App.Context;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<string> Get(){using (var transaction = _dbContext.Database.BeginTransaction()){try{_dbContext.Database.ExecuteSqlRaw("insert into Author(Name,Gender,Age,Email) values('CCC','男',45,'66666666@qq.com')");_dbContext.Database.ExecuteSqlRaw("update Author set Name='张三三' where Name='张三'");transaction.Commit();return "执行事务成功";}catch{transaction.Rollback();return "执行事务失败";}}}}
}

运行结果如下所示:

执行事务成功

在这里插入图片描述

6、封装SqlHelper

上面的查询方法都必须返回全部列,而在实际开发过程中往往是按需查询列,因此我们还是需要自行封装一个SqlHelper,代码如下:

SqlHelper.cs代码:

using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;namespace App
{public class SqlHelper{/// <summary>/// 执行查询操作,返回DataTable/// </summary>/// <param name="dbContext">数据库上下文</param>/// <param name="commandText">命令语句</param>/// <param name="commandType">命令类型</param>/// <param name="parameters">格式化参数集合</param>/// <returns>DataTable</returns>public static DataTable ExecuteQuery(DbContext dbContext, string commandText, CommandType commandType, params SqlParameter[] parameters){DbConnection connection = dbContext.Database.GetDbConnection();if (connection.State != ConnectionState.Open){connection.Open();}// 设置Commandusing DbCommand command = connection.CreateCommand();command.CommandText = commandText;command.CommandType = commandType;if (parameters != null && parameters.Length > 0){command.Parameters.AddRange(parameters);}// 查询数据using SqlDataAdapter adapter = new SqlDataAdapter(command as SqlCommand);try{DataTable dataTable = new DataTable();adapter.Fill(dataTable);return dataTable;}catch{return null;}finally{command.Parameters.Clear();}}/// <summary>/// 执行查询操作,返回DbDataReader/// </summary>/// <param name="dbContext">数据库上下文</param>/// <param name="commandText">命令语句</param>/// <param name="commandType">命令类型</param>/// <param name="parameters">格式化参数集合</param>/// <returns>DbDataReader</returns>public static DbDataReader ExecuteReader(DbContext dbContext, string commandText, CommandType commandType, params SqlParameter[] parameters){DbConnection connection = dbContext.Database.GetDbConnection();if (connection.State != ConnectionState.Open){connection.Open();}// 设置Commandusing DbCommand command = connection.CreateCommand();command.CommandText = commandText;command.CommandType = commandType;if (parameters != null && parameters.Length > 0){command.Parameters.AddRange(parameters);}// 返回DataReadertry{return command.ExecuteReader();}catch{return null;}finally{command.Parameters.Clear();}}/// <summary>/// 执行查询操作,返回第一行第一列/// </summary>/// <param name="dbContext">数据库上下文</param>/// <param name="commandText">命令语句</param>/// <param name="commandType">命令类型</param>/// <param name="parameters">格式化参数集合</param>/// <returns>第一行第一列</returns>public static object ExecuteScalar(DbContext dbContext, string commandText, CommandType commandType, params SqlParameter[] parameters){DbConnection connection = dbContext.Database.GetDbConnection();if (connection.State != ConnectionState.Open){connection.Open();}// 设置Commandusing DbCommand command = connection.CreateCommand();command.CommandText = commandText;command.CommandType = commandType;if (parameters != null && parameters.Length > 0){command.Parameters.AddRange(parameters);}// 返回第一行第一列try{return command.ExecuteScalar();}catch{return null;}finally{command.Parameters.Clear();}}/// <summary>/// 执行非查询操作,返回受影响的行数/// </summary>/// <param name="dbContext">数据库上下文</param>/// <param name="commandText">命令语句</param>/// <param name="commandType">命令类型</param>/// <param name="parameters">格式化参数集合</param>/// <returns>受影响的行数</returns>public static int ExecuteNonQuery(DbContext dbContext, string commandText, CommandType commandType, params SqlParameter[] parameters){DbConnection connection = dbContext.Database.GetDbConnection();if (connection.State != ConnectionState.Open){connection.Open();}// 设置Commandusing DbCommand command = connection.CreateCommand();command.CommandText = commandText;command.CommandType = commandType;if (parameters != null && parameters.Length > 0){command.Parameters.AddRange(parameters);}// 返回受影响的行数try{return command.ExecuteNonQuery();}catch{return 0;}finally{command.Parameters.Clear();}}/// <summary>/// 执行数据库事务,返回受影响的行数/// </summary>/// <param name="dbContext">数据库上下文</param>/// <param name="commands">命令集合</param>/// <returns>受影响的行数</returns>public static int ExecuteTransaction(DbContext dbContext, List<SingleCommand> commands){DbConnection connection = dbContext.Database.GetDbConnection();if (connection.State != ConnectionState.Open){connection.Open();}// 开启事务using DbTransaction transaction = connection.BeginTransaction();try{foreach (var item in commands){DbCommand command = connection.CreateCommand();command.CommandText = item.CommandText;command.CommandType = CommandType.Text;command.Transaction = transaction;if (item.Parameters.Count > 0){command.Parameters.AddRange(item.Parameters.ToArray());}command.ExecuteNonQuery();}// 提交事务transaction.Commit();return 1;}catch{// 回滚事务transaction.Rollback();return 0;}}}
}

SingleCommand.cs代码:

using Microsoft.Data.SqlClient;
using System.Collections.Generic;namespace App
{public class SingleCommand{/// <summary>/// 命令语句/// </summary>public string CommandText { get; set; }/// <summary>/// 格式化参数集合/// </summary>public List<SqlParameter> Parameters { get; set; }}
}

最后在Controller调用即可,代码如下:

using App.Context;
using App.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;namespace App.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class AuthorController : ControllerBase{protected readonly DaoDbContext _dbContext;public AuthorController(DaoDbContext dbContext){_dbContext = dbContext;}[HttpGet]public ActionResult<DataTable> GetAuthorsById(){SqlParameter parameter = new SqlParameter(@"Id", 1);return SqlHelper.ExecuteQuery(_dbContext, "select Id,Name,Age from Author where Id>@Id", CommandType.Text, parameter);}[HttpGet]public ActionResult<List<Author>> GetAuthorsByAge(){List<Author> list = new List<Author>();SqlParameter parameter = new SqlParameter(@"Age", 35);DbDataReader reader = SqlHelper.ExecuteReader(_dbContext, "select Id,Name,Age from Author where Age>@Age", CommandType.Text, parameter);while (reader.Read()){list.Add(new Author{Id = Convert.ToInt32(reader["Id"]),Name = reader["Name"] == DBNull.Value ? null : Convert.ToString(reader["Name"]),Age = reader["Id"] == DBNull.Value ? new Nullable<int>() : Convert.ToInt32(reader["Age"])});}return list;}[HttpGet]public ActionResult<int> GetAuthorsCount(){object obj = SqlHelper.ExecuteScalar(_dbContext, "select count(*) from Author", CommandType.Text);return Convert.ToInt32(obj);}[HttpGet]public ActionResult<string> UpdateAuthorById(){SqlParameter[] parameters ={new SqlParameter(@"Id", 1),new SqlParameter(@"Email", "12345678@163.com")};int result = SqlHelper.ExecuteNonQuery(_dbContext, "update Author set Email=@Email where Id=@Id", CommandType.Text, parameters);return result > 0 ? "修改邮箱成功" : "修改邮箱失败";}[HttpGet]public ActionResult<string> GetTransactionResult(){List<SingleCommand> commands = new List<SingleCommand>{new SingleCommand(){CommandText = "insert into Author values(@Name,@Gender,@Age,@Email)",Parameters = new List<SqlParameter>{new SqlParameter(@"Name", "赵六"),new SqlParameter(@"Gender", "女"),new SqlParameter(@"Age", 39),new SqlParameter(@"Email", "12345678@163.com")}},new SingleCommand(){CommandText = "update Author set Age=@Age where Name=@Name",Parameters = new List<SqlParameter>{new SqlParameter(@"Name", "张三"),new SqlParameter(@"Age", 59)}},};int result = SqlHelper.ExecuteTransaction(_dbContext, commands);return result > 0 ? "事务执行成功" : "事务执行失败";}}
}

GetAuthorsById结果如下:

[{"id":2,"name":"李四","age":40},{"id":3,"name":"王五","age":37}
]

GetAuthorsByAge结果如下:

[{"id":2,"name":"李四","gender":null,"age":40,"email":null,"book":[]},{"id":3,"name":"王五","gender":null,"age":37,"email":null,"book":[]}
]

GetAuthorsCount结果如下:

3

UpdateAuthorById结果如下:

修改邮箱成功

在这里插入图片描述
GetTransactionResult结果如下:

事务执行成功

在这里插入图片描述

7、结语

本文主要介绍了如何在EFCore中执行原生SQL的方法。在某些特殊情况下,直接执行原生语句往往会更方便且更高效,因此EFCore虽好,也别忘了SQL


http://www.ppmy.cn/news/2059.html

相关文章

【Opencv实战】高手勿入,Python使用Opencv+Canny实现边缘检测以及轮廓检测(详细步骤+源码分享)

前言 有温度 有深度 有广度 就等你来关注哦~ 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 在这次的案例实战中&#xff0c;我们将使用Python 3和OpenCV。我们将使用OpenCV&#xff0c;因为它是…

C# 11 中的新增功能

我们很高兴地宣布 C# 11 已经发布&#xff01;与往常一样&#xff0c;C# 开辟了一些全新的领域&#xff0c;同时推进了过去版本中一直在运行的几个主题。我们的文档页面上的 C# 11 的新增功能下有许多功能和详细信息&#xff0c;这些内容都得到了很好的介绍。 随着每个版本的发…

数据结构 树练习题

目录 判断 选择 判断 1.一棵有124个结点的完全二叉树&#xff0c;其 叶结点个数是确定的。 【答案】正确 【解析】完全二叉树 若设二叉树的深度为h 除第 h 层外 其它各层 1&#xff5e;(h-1) 的结点数都达到最大个数(即1~(h-1)层为一个满二叉树) 第 h 层所有的结点都连续集…

python 基础之垃圾回收机制

一、背景 之前能说个大概&#xff0c;python垃圾回收机制&#xff0c;设计到细节就不太清楚。 如同刚毕业的少年&#xff0c;出厂自带三年工作经验。做过啥啥.. 一问细节&#xff0c;阿西吧.. 不要问我怎么知道滴.. 哈哈!!!- 提高自己的计算机基础 - 重要的是面试(曾被问到三…

m基于遗传优化的不同等级电动汽车充电站的选址方案matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 作为电动汽车的普及与推广&#xff0c;必要的基础配套服务设施、充电站的建设位置和选址规划对整体行业的发展起着重要的意义&#xff0c;本文中提出了一个不同等级电动汽车充电站的选址与求解算…

博士论文答辩流程

2023年夏季毕业 &#xff08;一&#xff09;、总体时间安排 3月10日至3月20日&#xff1a;博士学位论文答辩资格预审 3月20日至3月25日&#xff1a;提交博士学位论文截止时间&#xff0c;评审时限为30天&#xff08;学院学籍的截止时间为3月20日&#xff09; 4月5日&#xf…

私人定制AI绘画——快速finetune stable diffusion教程

最近AI绘图非常火&#xff0c;只需要输入文本就能得到令人惊艳的图。 举个例子&#xff0c;输入 “very complex hyper-maximalist overdetailed cinematic tribal darkfantasy closeup portrait of a malignant beautiful young dragon queen goddess megan fox with long bl…

CSS盒子模型

网页布局过程&#xff1a; 1&#xff1a;准备好相关的网页元素&#xff0c;也就是大大小小的盒子2&#xff1a;利用CSS设置好盒子样式&#xff0c;将对不同的盒子摆放到对应的位置3&#xff1a;将内容填充到对应的盒子中盒子模型&#xff1a; 将html页面中的布局元素看作是一…