[英]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
。
REFCURSOR
sREFCURSOR
的示例 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
s ExampleREFCURSOR
的损坏的 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 的特定文档/示例来说明它应该如何工作。
REFCURSOR
ExampleREFCURSOR
示例使用 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 个表
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>();
}
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.