簡體   English   中英

如何使用 Dapper 從 PostgreSQL function 讀取多個結果集?

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

我試圖了解如何使用 Dapper 調用返回多個結果集的 PostgreSQL function。 我的理解是,在 PostgreSQL 中,目前實現此目的的最佳(唯一?)方法是聲明 function RETURNS SETOF REFCURSOR

返回多個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$;

帶有多個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);
            }
        }
    }
}

我在上面的代碼中遇到的問題是,當我進行第一個results.Read<T>()調用時,它會嘗試將兩個REFCURSOR都轉換為T 然后,此轉換會生成一個T ,其中所有屬性的值為null 然后對results.Read<T>()的下一次調用會拋出以下異常:

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

那么, Dapper 是如何處理多個REFCURSOR的呢? 有沒有一種方法可以在不手動解除對游標的引用的情況下讀取結果?

我有一個香草示例,它返回多個REFCURSOR s 而不使用 Dapper,它可以在我手動取消引用游標並讀取結果的地方工作,我還有一些示例可以針對 SQL 服務器存儲過程返回多個結果。

我還沒有(還)找到任何特定的文檔來指出 PostgreSQL 和 SQL 服務器應該如何調用QueryMultiple的具體區別,但是這樣的文檔將不勝感激。

即使在使用 Dapper 調用返回單個REFCURSOR的 PostgreSQL function 時,我發現有必要手動處理 cursor 取消引用,如下例所示。

但是從我到目前為止所讀的內容來看,這似乎不是必需的,盡管我很難找到 Dapper+PostgreSQL 的特定文檔/示例來說明它應該如何工作。

使用單個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);
        }
    }
}

那么,使用 Dapper + PostgreSQL + REFCURSOR ,是否總是需要手動引用 cursor 來讀取結果? 或者 Dapper 可以為您處理嗎?

我最近遇到了這個問題,為了模仿SQL 服務器的多結果集行為,我使用了返回SETOF REFCURSOR 的 function 我制作了自己的簡單庫來解決這個問題,因此我可以將它重新用於我的其他項目。 查看我的GitHub 存儲庫

假設數據庫中有 5 個表

  • 用戶
    • ID
    • 用戶名
  • 角色
    • ID
    • 姓名
  • 允許
    • ID
    • 姓名
  • 用戶角色
    • 用戶名| FK:用戶.Id
    • 角色編號| FK:角色.Id
  • 角色權限
    • 角色編號| FK:角色.Id
    • 權限編號| FK: Permission.Id

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#

/* 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>(); 
}

嘗試使用 conn.QueryMultipleAsync 在當前源中,您正在使用 conn.QueryMultiple。 這是它的完整指南。 小巧玲瓏

你可以這樣使用。 當然它會工作..!

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();
            }
        }

來自 sql server 背景,其中只是存儲過程中的 select 語句列表和“准備就緒”的問題; 使用 postgresql 以及使用 refcursors 和“獲取”另一側的那些 refcusors 的要求可能非常痛苦。

我可以建議的是:

1.) 使用帶有 refcursors 作為 INOUT 參數的 postgresql 過程。

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

2.) 調用該過程,然后使用“FETCH ALL”獲取返回數據的引用。

用 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.) 然后是你通常的 Dapper QueryMutliple 和 Reads。

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

這是未經測試的,但我希望它可以有所幫助。 Postgresql 很痛苦但很精彩。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM