jonwagner / Insight.Database

Fast, lightweight .NET micro-ORM

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using Postgres function with a SETOF (or TABLE) return value throws exception

rndor opened this issue · comments

Issue

Using a Postgres function with a SETOF (or TABLE) return value throws the following exception:

System.NotSupportedException: Output parameters aren't supported with SequentialAccess
  at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:1144
  at at System.Threading.Tasks.ValueTask`1.get_Result()
  at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior) in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:1130
  at at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
  at at Insight.Database.DBConnectionExtensions.ExecuteAndAutoClose[T](IDbConnection connection, Func`2 getCommand, Func`3 translate, CommandBehavior commandBehavior)
  at at Insight.Database.DBConnectionExtensions.Query[T](IDbConnection connection, String sql, Object parameters, IQueryReader`1 returns, CommandType commandType, CommandBehavior commandBehavior, Nullable`1 commandTimeout, IDbTransaction transaction, Object outputParameters)

Steps to reproduce

Database

Create table:

CREATE SCHEMA test;
CREATE TABLE test.widget
(
    id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    name TEXT
)

and plpgsql functions:

CREATE OR REPLACE FUNCTION test.get_widget(_id INT) RETURNS SETOF test.widget AS
$$
BEGIN
    RETURN QUERY SELECT * FROM test.widget WHERE id = _id;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION test.insert_widget(_name TEXT) RETURNS INT AS
$$
DECLARE
    _id INT;
BEGIN
    INSERT INTO test.Widget (Name) VALUES (_name) RETURNING id INTO _id;
    RETURN _id;
END;
$$ LANGUAGE plpgsql;

C# code

Along with the following C# code:

public class Widget {
    public int Id { get; set; }
    public string Name { get; set; }
}

public interface IWidgetRepository {
    [Sql("get_widget", Schema = "test")]
    Widget GetWidget(int id);

    [Sql("insert_widget", Schema = "test")]
    int InsertWidget(Widget widget);
}

Run the following (i.e. in Startup()):

ColumnMapping.Parameters.RemovePrefixes("_");

var db = new NpgsqlConnectionStringBuilder(... my connection string ...);

using (var conn = db.Connection()) {
    var newId = repo.InsertWidget(new Widget { Name = "This is a test" });
    var widget = repo.GetWidget(newId);
} 

Expected behavior

The record should be returned as expected. This doesn't appear to be an issue with other return types (like returning INT in the insert_widget function). Also, changing the Sql attribute to the following...

[Sql("SELECT * FROM test.widget WHERE id = @id")]

...works as expected as well.

  • Dotnet version: netcore2.1
  • Database: Postgres 10.5
  • Library version: 6.2.4

Of note, calling the function thusly [Sql("SELECT * FROM test.get_widget(@id)")] also works as expected.

It looks likes the latest version of the npgsql library changed the behavior. We use sequential access to speed up performance, but that's no longer compatible with output parameters. Should be an easy fix.

This is definitely a difference in npgsql 4.0. v3.x works fine. Let me see how we can fix it.

I've updated the code to strip out the sequential access mode when using pgsql and there is an output parameter.

This is fixed in v6.2.5, to be released next week.

In the meantime, using npgsql 3.x is a workaround.

Fantastic, I look forward to the update. Thanks for your quick response on this as well as issue #381.

6.2.5 is now available