如何在EF中使用命名参数调用PostgreSQL函数?
解决EF Core调用PostgreSQL带默认参数函数的问题
问题背景
调用PostgreSQL函数时,第一个参数sys_args有默认值无需传递,用NpgsqlConnection+NpgsqlCommand(设置CommandType.StoredProcedure并开启Npgsql.EnableStoredProcedureCompatMode)可以正常执行,但用EF Core的FromSqlRaw生成的SQL会报错:
42883: function frames.frm_tv_service_insert(integer, integer, integer, date) not found
EF生成的SQL语句为:
select result_err_out,result_text_out,result_text_body,data from frames.frm_tv_service_insert(@idnv,@idvid_rem,@place_idm_work,@datesv)
目标函数的定义签名:
CREATE OR REPLACE FUNCTION frames.frm_tv_service_insert( sys_args json DEFAULT '[{"Name":"idm_workdl","Value":[21,28,25,20,30]},{"Name":"userid","Value":"2790a07d-ccf5-4720-8e5c-59817743440c"}]'::json, idnv integer DEFAULT NULL::integer, idvid_rem integer DEFAULT NULL::integer, place_idm_work integer DEFAULT NULL::integer, datesv date DEFAULT NULL::date) RETURNS TABLE(result_err_out integer, result_text_out text, result_text_body text, data json) LANGUAGE plpgsql ...
报错原因
PostgreSQL通过参数的数量、类型、顺序匹配函数重载。EF生成的位置参数调用会把传入的4个参数对应到函数的前4个参数(第一个参数是sys_args,类型为json),但我们传入的是integer/date类型,PostgreSQL找不到匹配的函数签名,因此抛出错误。
解决方案
使用PostgreSQL的命名参数语法(参数名 := 参数值),明确指定每个参数对应的函数参数,跳过有默认值的sys_args,让PostgreSQL正确匹配函数。
方案1:直接构造带命名参数的SQL
// 构造带命名参数的SQL语句 var functionSql = @"select result_err_out,result_text_out,result_text_body,data from frames.frm_tv_service_insert( idnv := @idnv, idvid_rem := @idvid_rem, place_idm_work := @place_idm_work, datesv := @datesv)"; // 定义参数(确保参数名和函数参数名一致) var parameters = new[] { new NpgsqlParameter("@idnv", yourIdnvValue), new NpgsqlParameter("@idvid_rem", yourIdvidRemValue), new NpgsqlParameter("@place_idm_work", yourPlaceIdmWorkValue), new NpgsqlParameter("@datesv", yourDatesvValue) }; // 执行查询 var result = await new MyDataContext() .ResultMessageJsons .FromSqlRaw(functionSql, parameters) .FirstAsync();
方案2:动态生成命名参数SQL(适配任意函数和参数)
如果需要动态指定函数名和参数,可以封装方法自动拼接命名参数部分:
public static async Task<ResultMessageJson> ExecEFFunctionAsync( string functionName, MyDataContext context, NpgsqlParameter[] parameters) { // 拼接命名参数赋值语句,去掉参数名前的@符号 var paramAssignments = string.Join(", ", parameters.Select(p => $"{p.Name.TrimStart('@')} := {p.Name}")); // 构造完整SQL var sql = $"select result_err_out,result_text_out,result_text_body,data from {functionName}({paramAssignments})"; // 执行查询 return await context.ResultMessageJsons .FromSqlRaw(sql, parameters) .FirstAsync(); } // 调用示例 var paramsArray = new[] { new NpgsqlParameter("idnv", yourIdnvValue), // 参数名直接用函数参数名,无需@ new NpgsqlParameter("idvid_rem", yourIdvidRemValue), new NpgsqlParameter("place_idm_work", yourPlaceIdmWorkValue), new NpgsqlParameter("datesv", yourDatesvValue) }; var result = await ExecEFFunctionAsync("frames.frm_tv_service_insert", new MyDataContext(), paramsArray);
注意事项
- 无需设置
AppContext.SetSwitch("Npgsql.EnableStoredProcedureCompatMode", true),因为我们是直接用SELECT语法调用函数,而非存储过程调用方式。 - 确保NpgsqlParameter的Name属性和PostgreSQL函数的参数名完全一致(PostgreSQL默认大小写敏感)。
内容的提问来源于stack exchange,提问作者kocapb
相关产品推荐
相关产品推荐

