解决因对EFCore执行SQL方法不熟练而引起的问题

解决,efcore,执行,sql,方法,熟练,引起,问题 · 浏览次数 : 26

小编点评

# 模拟sql文件 **代码** ```csharp using var db = new OpenDbContext(); using var connection = db.Database.GetDbConnection(); using var tran = db.Database.BeginTransaction(); var cmd = connection.CreateCommand(); // 模拟sql语句 cmd.CommandText = "INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000)"; // 执行sql语句 cmd.Transaction = tran.GetDbTransaction(); cmd.Execute(); // 获取执行结果 var result = cmd.Transaction.CommitAsync(); result.Dump(); ``` **运行结果** ``` Hello, 张yszer1. The current time is 2023-10-08 17:26:45.000000. ``` **说明** * 代码模拟了数据库中两条数据,包括 id、name、create_time 和 modify_time。 * `$`符号用于创建可格式化字符串,例如 `FormattableString message = $\"Hello, {name}. The current time is {DateTime.Now}.\";` * `EFCore ExecuteSql方法使用该类型是用来防止SQL注入的。 * ``transaction.CommitAsync()``方法用于提交事务,并 ``transaction.CommitAsync()``方法用于获取执行结果。

正文

前言

本文测试环境:VS2022+.Net7+MySQL

因为我想要实现使用EFCore去执行sql文件,所以就用到了方法ExecuteSqlAsync,然后就产生了下面的问题,首先因为方法接收的参数是一个FormattableString,它又是一个抽象类,所以我就瞎测试使用下面方式构建

using var db = new OpenDbContext();
var mysqlSql2 = "INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');";
var result = await db.Database.ExecuteSqlAsync($"{mysqlSql2}");

编译没有报错,但是一个运行,结果居然报错了

Unhandled exception. MySqlConnector.MySqlException (0x80004005): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default' at line 1

看着这个错误我一直以为是哪个name列的值写的有问题,去数据库执行,没问题成功添加,代码中那个值换了好几次,就是不行,翻翻微软文档

using (var context = new BloggingContext())
{
    var rowsModified = context.Database.ExecuteSql($"UPDATE [Blogs] SET [Url] = NULL");
}

这不是和官方示例写的一样?难道是EFCore的bug?

寻找问题

抱着肯定不是EFCore bug的想法,查看源码吧

public static Task<int> ExecuteSqlAsync(
  this DatabaseFacade databaseFacade,
  FormattableString sql,
  CancellationToken cancellationToken = default (CancellationToken))
{
  return databaseFacade.ExecuteSqlRawAsync(sql.Format, (IEnumerable<object>) sql.GetArguments(), cancellationToken);
}

然后我就发现它源码里面还是从这个入参的sql中获取到对应的sql以及GetArguments,那么我就像提前构建一个FormattableString看下取到的值是多少

FormattableString mysqlSql = $"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');";

mysqlSql.Format.Dump();
mysqlSql.GetArguments().Dump();

这里的dump方法可以查看:此处

这不是也没问题吗,然后突然发现下面代码可以正常运行

using var db = new OpenDbContext();
FormattableString mysqlSql = $"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000')";

var result = await db.Database.ExecuteSqlAsync(mysqlSql);

那看来问题就出在ExecuteSqlAsync方法的入参上了,然后我这么测试

FormattableString mysqlSql = $"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');";

mysqlSql.Format.Dump();
mysqlSql.GetArguments().Dump();

FormattableString sql2 = $"{mysqlSql}";
sql2.Format.Dump();
sql2.GetArguments().Dump();

解决问题

到这里看来原因就出来了,是因为$的问题哦,那么解决方案就成先定义一个FormattableString类型直接传进入,或者

using var db = new OpenDbContext();
var name = "李四";
var result = await db.Database.ExecuteSqlAsync(
    $"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '{name}', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');");
result.Dump();

不过这里需要注意,ExecuteSqlAsync方法里面的sql在EFCore中并没有给你放到一个事务里面,所以如果有需要,那么就只好自己创建事务了

using var db = new OpenDbContext();
var name = "李四";
using var tran = db.Database.BeginTransaction();
var result = await db.Database.ExecuteSqlAsync(
    $"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '{name}', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');");
tran.Commit();
result.Dump();

未完

虽然解决了那个报错的问题,但是还是没解决我想执行sql文件,那只好换个方法去写了,自己去获取连接然后操作ADO.NET去执行吧(这里暂且先不用Dapper),我麻溜写下下面示例代码,顺带考虑到那个要裹在一个事务里面的情况(未封装,仅供参考)

// 模拟sql文件
var mysqlSql = @"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');
INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', 'error情况', '2023-10-08 17:26:47.000000');";

using var db = new OpenDbContext();
using var connection = db.Database.GetDbConnection();
using var tran = db.Database.BeginTransaction();
var cmd = connection.CreateCommand();
cmd.CommandText = mysqlSql;
int i = await cmd.ExecuteNonQueryAsync();
await tran.CommitAsync();
i.Dump();

运行居然报错:The transaction associated with this command is not the connection's active transaction ,还好报错中给了一个文档网站,网站中说我应该这么操作,将我开启的事务传递给cmd变量,也就是

// error 不能将源类型 'Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction' 转换为目标类型 'System.Data.Common.DbTransaction
cmd.Transaction = tran;

一脸懵逼这俩都对不上咋给,然后在看tran.的时候手滑点了一下,出来一个

cmd.Transaction = tran.GetDbTransaction();

源码如下

public static DbTransaction GetDbTransaction(this IDbContextTransaction dbContextTransaction) => dbContextTransaction is IInfrastructure<DbTransaction> accessor ? accessor.GetInfrastructure<DbTransaction>() : throw new InvalidOperationException(RelationalStrings.RelationalNotInUse);

这不是巧了,修改上面的代码如下

var mysqlSql = @"INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '2023-10-08 17:26:45.000000', '2023-10-08 17:26:47.000000');
INSERT INTO test1008.menus (id, name, create_time, modify_time) VALUES (default, '张飒1', '20xxcfdsfs000', '2023-10-08 17:26:47.000000');";

using var db = new OpenDbContext();
using var connection = db.Database.GetDbConnection();
using var tran = db.Database.BeginTransaction();
var cmd = connection.CreateCommand();
cmd.CommandText = mysqlSql;
cmd.Transaction = tran.GetDbTransaction();
int i = await cmd.ExecuteNonQueryAsync();
await tran.CommitAsync();
i.Dump();

因为我的sql第二条是错误的,所以运行成功报错,数据库中也不存在数据,这就是想要的效果。

再次修改sql后执行成功,数据库存在两条数据,实现了我的需求,完成。

FormattableString 介绍

以下内容来自chatgpt

FormattableString 是 C# 中的一个类,用于支持可格式化字符串的操作。它是在 .NET Framework 4.6 版本中引入的。

FormattableString 类的目的是提供一种方便的方式来创建可格式化的字符串。它可以使用类似于字符串插值的语法,但不会立即进行字符串插值操作,而是保留可格式化字符串的原始形式和参数的值。这使得开发人员可以在稍后的时间点或其他上下文中决定如何格式化字符串,以便满足特定的需求。

在使用 FormattableString 时,可以通过使用 $ 符号前缀来创建一个可格式化字符串,例如:

FormattableString message = $"Hello, {name}. The current time is {DateTime.Now}.";

在EFCore中ExecuteSql方法使用该类型是用来防止SQL注入的

与解决因对EFCore执行SQL方法不熟练而引起的问题相似的内容:

解决因对EFCore执行SQL方法不熟练而引起的问题

前言 本文测试环境:VS2022+.Net7+MySQL 因为我想要实现使用EFCore去执行sql文件,所以就用到了方法ExecuteSqlAsync,然后就产生了下面的问题,首先因为方法接收的参数是一个FormattableString,它又是一个抽象类,所以我就瞎测试使用下面方式构建 usin

如何让Java编译器帮你写代码

本文结合京东监控埋点场景,对解决样板代码的技术选型方案进行分析,给出最终解决方案后,结合理论和实践进一步展开。通过关注文中的技术分析过程和技术场景,读者可收获一种样板代码思想过程和解决思路,并对Java编译器底层有初步了解。

抖音验证签名和接口含中文签名,需要在发送端加上utf8编码

抖音验证签名和接口含中文签名,需要在发送端加上utf8编码 抖音验签和抖音异步通知回调验签解决:是对整个接收的字符串做验签,而不是部分数据做验签解决中文参数问题,否则中文乱码报验签错误 签名算法https://developer.open-douyin.com/docs/resource/zh-CN

盘点 Spring Boot 解决跨域请求的几种办法

熟悉 web 系统开发的同学,对下面这样的错误应该不会太陌生。 之所以会出现这个错误,是因为浏览器出于安全的考虑,采用同源策略的控制,防止当前站点恶意攻击 web 服务器盗取数据。 01、什么是跨域请求 同源策略,简单的说就是当浏览器访问 web 服务器资源时,只有源相同才能正常进行通信,即协议、域

[转帖]redis脑裂是什么?如何解决

这也是一个常见面试题,对redis集群部署不熟悉的同学,可能压根没听过这个名词qvq 什么是redis脑裂 下面我们解释一下什么是redis脑裂: 关于reids集群会由于网络等原因出现脑裂的情况,所谓的集群脑裂就是,由于redis master节点和redis salve节点和sentinel处于

01背包问题的js解决方式

如果你有兴趣看这个相信你已经对背包问题有所了解,所以关于背包问题的描述,我就不写了。只记录一下自己对这个问题的一些看法和思考,于我而言,这个东西现在困扰我的是如何确定最优解。实质上关于背包问题网上的东西我大体都有看过,对于这个问题,常见的就是使背包重量动态增长,然后遍历每个要装入的这些包裹,当包裹的

解决HtmlUnit执行JS报错提示ScriptException

问题描述 HtmlUnit作为一款比Selenium更轻量的HeadLess的Java版本浏览器模拟器,不需要在服务器上安装部署浏览器及其Driver程序。 但是,众所周知,HtmlUnit对JS脚本的支持并不是很友好,GitHub中大部分的issue都和JS执行错误有关,作者也一直在升级JS En

一文读懂责任分配矩阵,解决你80%的项目难题

成功的项目管理取决于整个团队对角色和职责的理解,使用责任分配矩阵分配和定义角色是使项目保持在正轨并为成功做好准备的好方法。

[转帖]谈谈对K8S CNI、CRI和CSI插件的理解

K8S的设计初衷就是支持可插拔架构,解决PaaS平台不好用、不能用、需要定制化等问题,K8S集成了插件、附加组件、服务和接口来扩展平台的核心功能。附加组件被定义为与环境的其他部分无缝集成的组件,提供类似本机的特性,并扩展集群管理员可用的组件,扩展还可以用于添加自定义软硬件的支持;服务和接口提供了看似

阿里云 龙蜥8.6系统镜像解决中文问号的方法

阿里云 龙蜥8.6系统镜像解决中文问号的方法 背景 计划测试环境上云 我这边作为先头不对开始搞 但是发现中文字体的显示存在问题,心中一片草泥马奔腾 搞国产OS, 连语言都不给我弄好. 问题现象 产品启动出现问题 [1]??????? [2]??????????????????????????????