Npgsql C# - 将参数作为复合类型数组传递到存储过程中

Npgsql C# - pass parameters as array of composite type into stored procedure

也许这个主题与这个 Array of composite type as stored procedure input passed by C# Npgsql 重复。但那是 2017 年的旧版本,一些 API、属性已被弃用。

目前,我正在尝试将一组复合类型传递给存储过程。我确实映射了一个全局复合类型。但是抛出了异常 Can't write CLR type Web.API.Models.UdtSpParameter[] with handler type MappedCompositeHandler`1

我尝试 google 似乎没有找到解决该问题的任何结果。下面下面是我创建、映射、调用命令存储过程。

Postgresql

/* Create composite type */
CREATE TYPE udt_sp_parameter AS (
    field VARCHAR(50),
    value VARCHAR
);


/* Create stored procedure */
CREATE OR REPLACE PROCEDURE stored_example(
    parameters udt_sp_parameter[])
LANGUAGE 'plpgsql'

AS $BODY$
DECLARE 
    _refCursor CURSOR FOR SELECT field, value FROM UNNEST(parameters::udt_sp_parameter[]);

    _record udt_sp_parameter;
BEGIN 
    OPEN _refCursor;

    LOOP
        FETCH _refCursor INTO _record;
        IF NOT FOUND THEN 
            EXIT;
        END IF;
  
        RAISE NOTICE 'Field: %', _record.field;
        RAISE NOTICE 'Value: %', _record.value IS NULL;
    END LOOP;

    CLOSE _refCursor;
END;
$BODY$;

并且我尝试用plpgsql语言调用存储并且工作正常。

DO
$$
DECLARE
parameters udtt_input_param[] := ARRAY[
     ROW('YED','Yeti')
    ,ROW('INTELLIGENT','NOOB')
    ,ROW('ZXC',NULL)
    ,ROW('CXX','1')];

BEGIN

CALL stored_example(parameters);
END
$$
LANGUAGE plpgsql

C# Npgsql (nuget Npgsql 4.1.4)

// mapping global type on Startup.cs
NpgsqlConnection.GlobalTypeMapper.MapComposite<UdtSpParameter>("udt_sp_parameter");

// class model UdtSpParameter
public class UdtSpParameter
{
    [PgName("field")]
    public string Field { get; set; }
    [PgName("value")]
    public string Value { get; set; }

    public UdtSpParameter() { }
}

// call stored procedure at data access layer for example StudentDAL.cs
public IEnumerable<T> CallStoredResultSet<T>(UdtSpParameter[] inputParameters ) where T : class
{
    var conn = _GetOpenConnection();
    var tran = _BeginTransaction(conn);

    NpgsqlCommand command = new NpgsqlCommand("stored_example", conn);
    command.CommandType = CommandType.StoredProcedure;
    var cmdParam = command.CreateParameter();
    cmdParam.ParameterName = "parameters";
    cmdParam.DbType = DbType.Object;  
    cmdParam.Value = inputParameters;
    cmdParam.DataTypeName = "udt_sp_parameter";

    command.Parameters.Add(cmdParam);

    // throw exception here
    // Can't write CLR type Web.API.Models.UdtSpParameter[] with handler type MappedCompositeHandler`1
    NpgsqlDataReader dr = command.ExecuteReader(); 


    var result = new List<T>();

    while (dr.Read())
    {
        Console.WriteLine(dr[0].ToString(), dr[1].ToString());
    }


    _CommitTransaction(tran);
    _CloseConnection(conn);

    return result;
}

如果我做错了什么,请找些东西告诉我改正。提前致谢。

您出现此错误是因为您为参数指定了类型名称,该参数是复合类型而不是复合数组。因此,您应该将 udt_sp_parameter[] 指定为 DataTypeName 的值,而不是 udt_sp_parameter:

var cmdParam = command.CreateParameter();
cmdParam.ParameterName = "parameters";
cmdParam.Value = inputParameters;
cmdParam.DataTypeName = "udt_sp_parameter[]";

由于您的类型已注册并且驱动程序已经知道其 PostgreSQL 名称,因此无需在参数设置期间指定它。请随意删除上面代码的最后一行:

var cmdParam = command.CreateParameter();
cmdParam.ParameterName = "parameters";
cmdParam.Value = inputParameters;

在大多数情况下,驱动程序足够智能,可以自动检测类型,但是当有歧义时,应该指定它。例如,您有一个应作为 JSON 传递的字符串,或者应将 CLR 类型序列化为 JSON。在其他情况下,只依赖于驱动程序的内部结构并让他完成他的工作。

Npgsql的官方文档说:

The only way to call a stored procedure is to write your own CALL my_proc(...) command, without setting CommandBehavior.StoredProcedure.

在您的特定情况下,您应该像这样修改代码:

NpgsqlCommand command = new NpgsqlCommand("call stored_example(:parameters)", conn);
// comment this line command.CommandType = CommandType.StoredProcedure;

希望对兄弟有帮助