jonwagner / Insight.Database

Fast, lightweight .NET micro-ORM
Other
861 stars 145 forks source link

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

Closed rndor closed 6 years ago

rndor commented 6 years ago

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.

rndor commented 6 years ago

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

jonwagner commented 6 years ago

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.

jonwagner commented 6 years ago

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

jonwagner commented 6 years ago

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.

rndor commented 6 years ago

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

jonwagner commented 6 years ago

6.2.5 is now available