【无标题】

EF Core 原生 SQL 执行方法对比(ExecuteSqlRawAsync vs FromSqlRaw)

场景:C# 全栈(EF Core + ABP)开发中,需要直接执行原生 SQL 的两种方法。

一、源码与结论速览

csharp 复制代码
// 方法一:RunSql ------ 执行 DML,返回受影响行数
public async Task<int> RunSql(string sql)
{
    var dbContext = await this.GetDbContextAsync();
    try
    {
        int affectRows = await dbContext.Database.ExecuteSqlRawAsync(sql);
        if (affectRows < 0)
        {
            return -1;
        }
        return affectRows;
    }
    catch (Exception ex)
    {
        return -1; // ⚠️ 吞掉异常,UoW 不会回滚
    }
}

// 方法二:GetAllSolutionTrains ------ 执行 SELECT,返回实体列表
public async Task<List<AllSolutionTrains>> GetAllSolutionTrains(string sql)
{
    var dbContext = await GetDbContextAsync();
    try
    {
        var query = dbContext.AllSolutionTrains.FromSqlRaw(sql);
        var viewList = await query.ToListAsync();
        return viewList;
    }
    catch (Exception ex)
    {
        throw new Exception(ex.Message); // 包装后抛出
    }
}

一句话区别:

RunSql GetAllSolutionTrains
底层 API ExecuteSqlRawAsync FromSqlRaw
适用 SQL INSERT / UPDATE / DELETE / DDL SELECT 查询
返回值 int(受影响行数) List<AllSolutionTrains>(结果集)
实体映射 不需要实体 必须匹配 DbSet 实体,字段对齐

二、逐点对比

2.1 用途 & EF Core API 不同

RunSql → ExecuteSqlRawAsync

适用:INSERT / UPDATE / DELETE / DDL(create table 等)

返回值:受影响行数

不能用来做 SELECT,该 API 不返回数据行,只返回被修改的行数。

执行语句,不追踪实体,也不会加载数据到内存。

GetAllSolutionTrains → FromSqlRaw

适用:SELECT 查询

返回:数据库返回的行映射成 C# 实体 List<AllSolutionTrains>

不能用于增删改,FromSqlRaw 只能跟 SELECT 语句。

限制:

SQL 返回的列名必须与 AllSolutionTrains 实体属性一一对应;

必须返回该实体所有映射字段,少字段会报错;

只能从对应实体的 DbSet 调用(dbContext.AllSolutionTrains)。

2.2 异常处理逻辑差异(重点)

RunSql

csharp 复制代码
catch (Exception ex)
{
    return -1; // 吞掉异常,返回 -1,调用方靠返回值判断失败
}

⚠️ 坑:ABP 工作单元 UoW 下,如果只是 return -1 而没有抛出异常,事务不会自动回滚。SQL 报错后异常被吃掉,UoW 认为代码正常执行,事务会继续提交 ------ 数据一致性隐患很大。

GetAllSolutionTrains

csharp 复制代码
catch (Exception ex)
{
    throw new Exception(ex.Message); // 包装后重新抛出
}

捕获异常后重新抛出,上层可捕获。

⚠️ 小问题:new Exception(ex.Message) 会丢失原始异常类型(如 DbUpdateException、SqlException),建议直接 throw; 保留原始堆栈。

2.3 实体追踪差异

ExecuteSqlRawAsync:完全不追踪实体,只是执行 SQL 命令。

FromSqlRaw 查询出的实体:默认被 EF ChangeTracker 追踪,后续修改实体属性后 SaveChanges 会更新数据库。若不想追踪可加 .AsNoTracking()。

2.4 SQL 注入风险(两者一致)

两个方法都是 Raw 原生 SQL,若直接拼接字符串传参,都存在 SQL 注入风险。

正确做法:使用参数化,不要字符串拼接。

csharp 复制代码
// 参数化示例
dbContext.Database.ExecuteSqlRawAsync("UPDATE Table SET Name={0}", name);

三、完整对比表

对比项 RunSql(ExecuteSqlRawAsync) GetAllSolutionTrains(FromSqlRaw)
SQL 类型 INSERT / UPDATE / DELETE / DDL SELECT 查询
返回值 int:受影响行数 List<实体>:查询结果集
实体映射 不需要实体 必须匹配 DbSet 实体,字段对齐
实体追踪 无 默认开启追踪
异常处理 吞异常返回 -1,⚠️ 事务不会回滚 重新抛出异常,UoW 可回滚
能否读数据 ❌ 不行 ✅ 可以

四、你现有代码的两个严重坑

坑 1:RunSql 捕获异常 return -1
数据库报错(主键冲突、语法错误)时异常被吞,ABP UoW 感知不到失败,事务继续提交,造成数据不一致。
建议:异常直接往上抛,交由 UoW 自动回滚;调用方自行 try-catch 处理。
坑 2:FromSqlRaw 实体字段必须完全匹配
若 SELECT 返回列少了、列名与实体不一致,直接报错。若想返回自定义字段,不能用此方法,应改用 dbContext.Database.SqlQuery<T>(EF Core 7+)返回自定义 DTO。

五、优化后的建议代码

csharp 复制代码
// RunSql 优化:不吞异常,交由上层 UoW 处理
public async Task<int> RunSql(string sql)
{
    var dbContext = await this.GetDbContextAsync();
    int affectRows = await dbContext.Database.ExecuteSqlRawAsync(sql);
    return affectRows;
}

// 查询优化:保留原始异常堆栈 + 只读不追踪
public async Task<List<AllSolutionTrains>> GetAllSolutionTrains(string sql)
{
    var dbContext = await GetDbContextAsync();
    try
    {
        var query = dbContext.AllSolutionTrains.FromSqlRaw(sql);
        var viewList = await query.AsNoTracking().ToListAsync();
        return viewList;
    }
    catch
    {
        throw; // 保留原始异常堆栈,不要 new Exception
    }
}

六、延伸建议

若需执行原生 SQL 返回自定义 DTO(非 DbSet 实体),建议增加通用方法:

csharp 复制代码
// EF Core 7+ 支持 SqlQuery<T> 返回自定义 DTO
public async Task<List<T>> RunSqlQuery<T>(string sql) where T : class
{
    var dbContext = await GetDbContextAsync();
    return await dbContext.Database.SqlQuery<T>(sql).ToListAsync();
}

说明:SqlQuery<T> 需 Microsoft.EntityFrameworkCore.Relational 7.0+ 支持;T 为自定义 DTO,列名需与属性对应。

相关推荐
几何心凉1 小时前
嵌入式Linux系统开发21天速成:从基础开发到综合项目实战
linux·运维·服务器
Tairitsu_H1 小时前
[Linux系统] 一切皆文件 | 缓冲区机制 | FILE 结构
linux·运维·服务器·文件·缓冲区
雨落在了我的手上1 小时前
MySQL数据库基础(1):MySQL的安装-Windows
数据库·mysql
涉密IT资质笔记2 小时前
涉密人员脱密期管理规范:期限分级模型、就业限制边界与违规认定标准
java·服务器·前端·网络·数据库
honsor2 小时前
PoE温湿度传感器:一根网线供电+通信,即插即用,机房/配电室/仓库温湿度监测首选
运维·服务器·网络·数据库·人工智能·安全
自强的小白2 小时前
jvm面试(Gc)
服务器·jvm·面试
XM_jhxx2 小时前
简会AI图纸识别系统功能上新:模板、公差、量具个性化配置上线!
数据库·人工智能·软件工程
Qwier2 小时前
Win Srv 2019 安装补丁后重启
运维·服务器·windows
一叶飘零晋2 小时前
GPT-6 Luna 批量调用实战指南
服务器·gpt·php