[英]How do I read multiple results sets from a PostgreSQL function using Dapper?
我試圖了解如何使用 Dapper 調用返回多個結果集的 PostgreSQL function。 我的理解是,在 PostgreSQL 中,目前實現此目的的最佳(唯一?)方法是聲明 function RETURNS SETOF REFCURSOR
。
REFCURSOR
的示例 PostgreSQL FunctionCREATE 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 個表
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$
/* 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.