1、前言
前一篇博客介绍了EFCore
中常见的一些查询操作,使用Linq
或Lambda
结合实体类的操作相当方便。但在某些特殊情况下,我们仍旧需要使用原生SQL
来获取数据。好在EFCore中提供了完整的方法支持原生SQL
,下面开始介绍。
2、构建测试数据库
与之前一样,还是使用Author
和Book
数据表,它们是一对多的关系,AuthorId
为Book
表中的外键。
Author
表数据如下所示:
Id | Name | Gender | Age | |
---|---|---|---|---|
1 | 张三 | 男 | 35 | 11111111@qq.com |
2 | 李四 | 女 | 40 | 22222222@qq.com |
3 | 王五 | 男 | 37 | 33333333@qq.com |
Book
表数据如下所示:
Id | Title | Press | PublicationTime | Price | AuthorId |
---|---|---|---|---|---|
1 | 《C程序设计》 | A出版社 | 2022-01-01 | 30 | 1 |
2 | 《C++程序设计》 | B出版社 | 2022-02-02 | 45 | 1 |
3 | 《Java程序设计》 | C出版社 | 2022-03-03 | 60 | 2 |
4 | 《C#程序设计》 | D出版社 | 2022-04-04 | 55 | 2 |
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
支持FromSqlInterpolated
与Lambda
一起使用,代码如下:
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
方法,下面代码执行了一个包含Insert
和Update
的事务:
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
。