简体   繁体   English

如何使用 Dapper 从 PostgreSQL function 读取多个结果集?

[英]How do I read multiple results sets from a PostgreSQL function using Dapper?

I am trying to understand how to use Dapper to make a call to a PostgreSQL function that returns multiple result sets.我试图了解如何使用 Dapper 调用返回多个结果集的 PostgreSQL function。 My understanding is that in PostgreSQL, the best (only?) way to currently achieve this is to declare that the function RETURNS SETOF REFCURSOR .我的理解是,在 PostgreSQL 中,目前实现此目的的最佳(唯一?)方法是声明 function RETURNS SETOF REFCURSOR

Example PostgreSQL Function that Returns Multiple REFCURSOR s返回多个REFCURSOR的示例 PostgreSQL Function

CREATE OR REPLACE FUNCTION public.testmultiplerefcursorfunc()
    RETURNS SETOF REFCURSOR
    LANGUAGE 'plpgsql'

    STABLE 
AS $BODY$
DECLARE 
    ref1    REFCURSOR;
    ref2    REFCURSOR;
BEGIN

    OPEN ref1 FOR
    SELECT      *
    FROM        characters;
    RETURN NEXT ref1;

    OPEN ref2 FOR
    SELECT      *
    FROM        planets;
    RETURN NEXT ref2;

END;
$BODY$;

Broken Dapper+PostgreSQL with Multiple REFCURSOR s Example带有多个REFCURSOR的损坏的 Dapper+PostgreSQL 示例

[Test]
public void UsingDapper_QueryMultiple_CallFunctionThatReturnsMultipleRefCursors_ReadsMultipleResultSetsViaMultipleRefCursors()
{
    // Arrange
    using (var conn = new NpgsqlConnection(_getConnectionStringToDatabase()))
    {
        var funcName = "testmultiplerefcursorfunc";
        var expect1 = CharacterTestData;
        var expect2 = PlanetTestData;
        conn.Open();

        using (var transaction = conn.BeginTransaction())
        {
            // Act
            using (var results = conn.QueryMultiple(
                funcName, 
                commandType: CommandType.StoredProcedure, 
                transaction: transaction))
            {
                var result1 = results.Read<Character>().AsList();
                var result2 = results.Read<Planet>().AsList();

                // Assert 
                CollectionAssert.AreEquivalent(expect1, result1);
                CollectionAssert.AreEquivalent(expect2, result2);
            }
        }
    }
}

The problem that I'm having with the code above is that when I make the first results.Read<T>() call, it attempts to return both REFCURSOR s cast as T .我在上面的代码中遇到的问题是,当我进行第一个results.Read<T>()调用时,它会尝试将两个REFCURSOR都转换为T This cast then results in a T with null values for all of the properties.然后,此转换会生成一个T ,其中所有属性的值为null Then the next call to results.Read<T>() throws the following exception:然后对results.Read<T>()的下一次调用会抛出以下异常:

System.ObjectDisposedException: 'The reader has been disposed; this can happen after all data has been consumed
Object name: 'Dapper.SqlMapper+GridReader'.'

So, how does Dapper work with multiple PostgreSQL REFCURSOR s?那么, Dapper 是如何处理多个REFCURSOR的呢? Is there a way to read the results without manually dereferencing the cursors?有没有一种方法可以在不手动解除对游标的引用的情况下读取结果?

I've got a vanilla example that returns multiple REFCURSOR s without using Dapper that works where I manually dereference the cursors and read the results and I've also got examples that work against a SQL Server stored procedure that return multiple results.我有一个香草示例,它返回多个REFCURSOR s 而不使用 Dapper,它可以在我手动取消引用游标并读取结果的地方工作,我还有一些示例可以针对 SQL 服务器存储过程返回多个结果。

I haven't (yet) found any particular documentation that points to a specific difference of how QueryMultiple should be called for PostgreSQL vs SQL Server, but such documentation would be greatly appreciated.我还没有(还)找到任何特定的文档来指出 PostgreSQL 和 SQL 服务器应该如何调用QueryMultiple的具体区别,但是这样的文档将不胜感激。

Even when calling a PostgreSQL function that returns single REFCURSOR using Dapper, I've found it necessary to manually handle the cursor dereferencing like the example below.即使在使用 Dapper 调用返回单个REFCURSOR的 PostgreSQL function 时,我发现有必要手动处理 cursor 取消引用,如下例所示。

But from what I've read so far, this doesn't seem like it's supposed to be necessary, although I've had trouble finding specific documentation/examples for Dapper+PostgreSQL that show how this should otherwise work.但是从我到目前为止所读的内容来看,这似乎不是必需的,尽管我很难找到 Dapper+PostgreSQL 的特定文档/示例来说明它应该如何工作。

Working Dapper+PostgreSQL with Single REFCURSOR Example使用单个REFCURSOR示例使用 Dapper+PostgreSQL

[Test]
public void UsingDapper_Query_CallFunctionThatReturnsRefCursor_ReadsRowsViaRefCursor()
{
    // Arrange
    using (var conn = new NpgsqlConnection(_getConnectionStringToDatabase()))
    {
        var procName = "testrefcursorfunc";
        var expect = CharacterTestData;
        conn.Open();

        using (var transaction = conn.BeginTransaction())
        {
            // Act
            var cursorResult = (IDictionary<string, object>)conn
                .Query<dynamic>(procName, commandType: CommandType.StoredProcedure, transaction: transaction)
                .Single();
            var cursorSql = $@"FETCH ALL FROM ""{(string)cursorResult[procName]}""";
            var result = conn.Query<Character>(
                cursorSql, 
                commandType: CommandType.Text, 
                transaction: transaction);

            // Assert 
            CollectionAssert.AreEquivalent(expect, result);
        }
    }
}

So, with Dapper + PostgreSQL + REFCURSOR , is it always necessary to manually deference the cursor to read the results?那么,使用 Dapper + PostgreSQL + REFCURSOR ,是否总是需要手动引用 cursor 来读取结果? Or can Dapper handle that for you?或者 Dapper 可以为您处理吗?

I've ran into this problem lately, and to imitate the multiple result set behavior of SQL Server, I used a function that returns SETOF REFCURSOR .我最近遇到了这个问题,为了模仿SQL 服务器的多结果集行为,我使用了返回SETOF REFCURSOR 的 function I made my own simple library to resolve this problem, so I can reuse it to my other projects.我制作了自己的简单库来解决这个问题,因此我可以将它重新用于我的其他项目。 See my GitHub Repository查看我的GitHub 存储库

Assuming you have 5 tables in the database假设数据库中有 5 个表

  • User用户
    • Id ID
    • Username用户名
  • Role角色
    • Id ID
    • Name姓名
  • Permission允许
    • Id ID
    • Name姓名
  • UserRole用户角色
    • UserId |用户名| FK: User.Id FK:用户.Id
    • RoleId |角色编号| FK: Role.Id FK:角色.Id
  • RolePermission角色权限
    • RoleId |角色编号| FK: Role.Id FK:角色.Id
    • PermissionId |权限编号| FK: Permission.Id FK: Permission.Id

PostgreSQL (PL/PGSQL) PostgreSQL (PL/PGSQL)

CREATE OR REPLACE FUNCTION "get_user_with_roles_and_permissions"("user__id" INTEGER)
RETURNS SETOF REFCURSOR AS
$BODY$
DECLARE
    -- Refcursor declarations
    "ref__user" REFCURSOR;
    "ref__roles" REFCURSOR;
    "ref__permissions" REFCURSOR;
BEGIN
    -- Select User
    -- NOTE: this only query for exactly 1 row
    OPEN "ref__user" FOR
    SELECT "User"."Id", "User"."Username"
    FROM "User"
    WHERE "User"."Id" = "user__id"
    LIMIT 1;
    RETURN NEXT "ref__user";

    -- Select Roles
    OPEN "ref__roles" FOR
    SELECT "Role"."Id", "Role"."Name"
    FROM "Role"
    INNER JOIN "UserRole" ON "Role"."Id" = "UserRole"."RoleId"
    WHERE "UserRole"."UserId" = "user__id";
    RETURN NEXT "ref__roles";

    -- Select Permissions
    -- NOTE: There's a chance that user has many roles which have same permission, we use DISTINCT to eliminate duplicates
    OPEN "ref__permissions" FOR
    SELECT DISTINCT "Permission"."Id", "Permission"."Name"
    FROM "Permission"
    INNER JOIN "RolePermission" ON "Permission"."Id" = "RolePermission"."PermissionId"
    INNER JOIN "UserRole" ON "RolePermission"."RoleId" = "UserRole"."RoleId"
    WHERE "UserRole"."UserId" = "user__id";
    RETURN NEXT "ref__permissions";
END;
$BODY$

C# C#

/* Entity models */
record User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public IEnumerable<Role> Roles { get; set; }
    public IEnumerable<Permission> Permissions { get; set; }
}

record Role
{
    public int Id { get; set; }
    public string Name { get; set; }
}

record Permission
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// Calling the database function in C# code

// Create an instance of NpgsqlConnection
using var connection = new NpgsqlConnection(connectionString);

// Try to open a database connection
connection.Open();

// Begin a database transaction
using var transaction = connection.BeginTransaction();

// Query refcursors
// Call a function with name 'get_user_with_roles_and_permissions' with parameter 'user_id' = 1
var refcursors = connection.QueryRefcursor("get_user_with_roles_and_permissions", transaction, new { user__id = 1 });

// we use ReadSingleOrDefault because we're sure that there is only one user that has an id of 1 (or none if the user with id = 1 doesn't exists)
var user = refcursors.ReadSingleOrDefault<User>();

// Check if user with id = 1 exists
if (user is not null)
{
    // Query for roles
    user.Roles = refcursors.Read<Role>();

    // Query for permissions
    user.Permissions = refcursors.Read<Permission>(); 
}

Try with conn.QueryMultipleAsync In Current source you are using conn.QueryMultiple.尝试使用 conn.QueryMultipleAsync 在当前源中,您正在使用 conn.QueryMultiple。 Here is the complete guide for it .这是它的完整指南。 Dapper Multiple 小巧玲珑

You Can Use Like this.你可以这样使用。 Sure It Will Work..!当然它会工作..!

public DataSet Manage_user_profiledata(string _prof_code)
        {
            string query = string.Format(@"select * from  Function_Name(@prof_code, @first_tbl, @second_tbl)");
            NpgsqlParameter[] sqlParameters = new NpgsqlParameter[3];
            sqlParameters[0] = new NpgsqlParameter("@prof_code", NpgsqlDbType.Varchar);
            sqlParameters[0].Value = Convert.ToString(_prof_code);

            //
            sqlParameters[1] = new NpgsqlParameter("@first_tbl", NpgsqlTypes.NpgsqlDbType.Refcursor);
            sqlParameters[1].Value = Convert.ToString("Avilable");
            sqlParameters[1].Direction = ParameterDirection.InputOutput;
            sqlParameters[1].NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Refcursor;
            //
            sqlParameters[2] = new NpgsqlParameter("@second_tbl", NpgsqlTypes.NpgsqlDbType.Refcursor);
            sqlParameters[2].Value = Convert.ToString("Assigned");
            sqlParameters[2].Direction = ParameterDirection.InputOutput;
            sqlParameters[2].NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Refcursor;
            return conn.executeMultipleSelectQuery(query, sqlParameters);

        }





public DataSet executeMultipleSelectQuery(string _query, NpgsqlParameter[] sqlParameter)
        {
            // NgpSql Init //
            npg_connection = new NpgsqlConnection(connstr);
            npg_command = new NpgsqlCommand(_query, npg_connection);
            // NgpSql Init //
            i = 0;
            try
            {
                ds = new DataSet();
                npg_connection.Open();
                NpgsqlTransaction tran = npg_connection.BeginTransaction();
                npg_command.CommandType = CommandType.Text;
                npg_command.Parameters.AddRange(sqlParameter);
                npg_command.ExecuteNonQuery();
                foreach (NpgsqlParameter parm in sqlParameter)
                {
                    if (parm.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Refcursor)
                    {
                        if (parm.Value.ToString() != "null" || parm.Value.ToString() != "NULL" || parm.Value.ToString() != "")
                        {
                            string parm_val = string.Format("FETCH ALL IN \"{0}\"", parm.Value.ToString());
                            npg_adapter = new NpgsqlDataAdapter(parm_val.Trim().ToString(), npg_connection);
                            ds.Tables.Add(parm.Value.ToString());
                            npg_adapter.Fill(ds.Tables[i]);
                            i++;
                        }
                    }
                }
                tran.Commit();
                return ds;
            }
            catch (Exception ex)
            {
                ds_ERROR.Tables[0].Rows.Add(ex.ToString(), ex.Message.ToString());
                return ds_ERROR;
            }
            finally
            {
                npg_connection.Close();
            }
        }

Coming from an sql server background where its just a question of a list of select statements in your stored proc and your "good to go";来自 sql server 背景,其中只是存储过程中的 select 语句列表和“准备就绪”的问题; using postgresql and the requirement to use refcursors and "fetch " those refcusors on the other side can be quite painful.使用 postgresql 以及使用 refcursors 和“获取”另一侧的那些 refcusors 的要求可能非常痛苦。

What i can suggest is:我可以建议的是:

1.) Use a postgresql Procedure with refcursors as INOUT parameters. 1.) 使用带有 refcursors 作为 INOUT 参数的 postgresql 过程。

CREATE OR REPLACE PROCEDURE public.proc_testmultiplerefcursor(INOUT ref1 refcursor, INOUT ref2 refcursor)

2.) Call the procedure and then fetch the refcursors for the returned data using "FETCH ALL". 2.) 调用该过程,然后使用“FETCH ALL”获取返回数据的引用。

Fill the INOUT parameters with names for refcursors so they are recoverable in this case i have used 'ref1' & 'ref2'.用 refcursors 的名称填充 INOUT 参数,以便在这种情况下它们可以恢复,我使用了“ref1”和“ref2”。

var sql = "BEGIN;CALL public.proc_testmultiplerefcursor(@pentity_id,'ref1','ref2');" +
                  "FETCH ALL FROM  ref1; " +
                  "FETCH ALL FROM  ref2;" +
                  "COMMIT;";

3.) Then your usual Dapper QueryMutliple and Reads. 3.) 然后是你通常的 Dapper QueryMutliple 和 Reads。

var multi = await conn.QueryMultipleAsync(sql);
var result1 = (await multi.ReadAsync<Character>()).AsList();
var result2 =(await multi.ReadAsync<Planet>()).AsList();

This is untested but i hope it can be of help.这是未经测试的,但我希望它可以有所帮助。 Postgresql is painfull but brilliant. Postgresql 很痛苦但很精彩。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM