Как передать составные типы в функцию PL / pgsql? - PullRequest
0 голосов
/ 18 мая 2018

Я пытаюсь (безуспешно) передать составной тип PostgreSql в функцию PL / pgsql.Сообщение об ошибке и пример кода приведены ниже.Я также пробовал несколько разных вариантов кода (например, this безуспешно - каждая версия генерирует свое сообщение об ошибке).Я новичок в SQL и ожидаю, что я делаю простую ошибку.Я был бы признателен, если бы кто-то мог просмотреть приведенный ниже пример кода и объяснить, в чем заключается ошибка, которую я допустил.

Сообщение об ошибке


    System.InvalidCastException: When specifying NpgsqlDbType.Enum, SpecificType must be specified as well
       at Npgsql.TypeHandlerRegistry.get_Item(NpgsqlDbType npgsqlDbType, Type specificType)
       at Npgsql.NpgsqlParameter.ResolveHandler(TypeHandlerRegistry registry)
       at Npgsql.NpgsqlParameter.Bind(TypeHandlerRegistry registry)
       at Npgsql.NpgsqlCommand.ValidateParameters()
       at Npgsql.NpgsqlCommand.d__71.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
       at Npgsql.NpgsqlCommand.d__87.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
       at Npgsql.NpgsqlCommand.ExecuteScalar()
       at TestDatabase.TestCompositeType.Test() in F:\Visual Studio 2017\Projects\TestDatabase\TestDatabase\TestCompositeType.cs:line 42
       at TestDatabase.Program.Main(String[] args) in F:\Visual Studio 2017\Projects\TestDatabase\TestDatabase\Program.cs:line 17

Информация о системе


    Windows 10, 64 bit
    PostgreSQL 10.3, compiled by Visual C++ build 1800, 64-bit
    Npgsql, Version=3.2.6.0

Код PostgreSql


    create schema if not exists MySchema;

    create type MySchema.MyType as(
            X real,
            Y real
        );

    create table if not exists MySchema.MyTable(
        ItemID int primary key generated by default as identity,
        MyType MySchema.MyType
    );

    create or replace function MySchema.SetMyType( 
        ItemID2 int,
        MyType2 MySchema.MyType
    ) 
    returns int
    as $$
    declare
        resultID int;
    begin
        resultID := ItemID2;

        if( exists( select 1 from MySchema.MyTable as mt where mt.ItemID = ItemID2 ) ) then
            insert into MySchema.MyTable( MyType ) 
            values ( MyType2 )
            returning mt.ItemID into resultID;
        else
            update MySchema.MyTable as mt 
            set MyType = MyType2
            where mt.ItemID = ItemID2;
        end if;

        return resultID;
    end;
    $$ language plpgsql;

код C #


    public void Test()
    {
        NpgsqlConnection.MapCompositeGlobally( "MySchema.MyType" );

        var connection = new NpgsqlConnection( "Host=localhost;Username=postgres;Password=123456;database=testdb" );
        if( null == connection )
            throw new NullReferenceException( "connection" );
        try
        {
            connection.Open();

            var cmd = new NpgsqlCommand( "MySchema.SetMyType", connection );
            cmd.CommandType = System.Data.CommandType.StoredProcedure;

            var par = new NpgsqlParameter( "ItemID2", NpgsqlDbType.Integer );
            par.Value = 1;
            cmd.Parameters.Add( par );

            par = new NpgsqlParameter( "MyType2", NpgsqlDbType.Composite );
            MyType myType = new MyType();
            myType.X = 1;
            myType.Y = 2;
            par.Value = myType;
            cmd.Parameters.Add( par );

            int id = Convert.ToInt32( cmd.ExecuteScalar() );
        }
        finally
        {
            connection.Close();
        }
    }   

Ответы [ 2 ]

0 голосов
/ 19 мая 2018

Предложения, сделанные «липким битом», исправили проблему.Я включил обновленный пример кода ниже для всех, кто может иметь дело с той же проблемой.Все строки преобразуются в нижний регистр с помощью «ToLower ()», но это необходимо только для отображения типа базы данных в «NpgsqlConnection.MapCompositeGlobally».


    namespace TestDatabase
    {
        public class MyType
        {
            public float X;
            public float Y;
        };

        public class TestCompositeType
        {
            public void Test()
            {
                NpgsqlConnection.MapCompositeGlobally<TestDatabase.MyType>( "MySchema.MyType".ToLower() );

                var connection = new NpgsqlConnection( "Host=localhost;Username=postgres;Password=123456;database=testdb".ToLower() );
                if( null == connection )
                    throw new NullReferenceException( "connection" );
                try
                {
                    connection.Open();

                    var cmd = new NpgsqlCommand( "MySchema.SetMyType".ToLower(), connection );
                    cmd.CommandType = System.Data.CommandType.StoredProcedure;

                    var par = new NpgsqlParameter( "ItemID2".ToLower(), NpgsqlDbType.Integer );
                    par.Value = 1;
                    cmd.Parameters.Add( par );

                    par = new NpgsqlParameter( "MyType2".ToLower(), NpgsqlDbType.Composite );
                    MyType myType = new MyType();
                    myType.X = 1;
                    myType.Y = 2;
                    par.Value = myType;
                    par.SpecificType = typeof( MyType );
                    cmd.Parameters.Add( par );

                    int id = Convert.ToInt32( cmd.ExecuteScalar() );
                }
                finally
                {
                    connection.Close();
                }
            }
        }
    }

0 голосов
/ 19 мая 2018

Перед вызовом connection.Open() вы должны сопоставить свой тип C # с типом базы данных, вызвав NpgsqlConnection.MapCompositeGlobally<>() с вашим типом C # в <> (мне интересно, если ваш код, который не имеет <MyType>часть, даже скомпилированная? Я получаю сообщение об ошибке, если я пытаюсь это сделать.)

...
NpgsqlConnection.MapCompositeGlobally<MyType>( "MySchema.MyType" );

var connection = new NpgsqlConnection( "Host=localhost;Username=postgres;Password=123456;database=testdb" );
...

(MyType должен иметь открытый конструктор, не принимающий никаких аргументов для NpgsqlConnection.MapCompositeGlobally<>(), чтобы работать с ним.знать это.)

Кроме того, вы должны установить для свойства SpecificType типа NpgsqlParameter тип C #.Это то, что вы, кажется, на самом деле пропустили.(Неважно, где вы это делаете (если только до вызова ExecuteScalar() и после создания параметра, конечно), например, давайте поместим его после того, как вы установили параметр Value.)

...
myType.X = 1;
myType.Y = 2;
par.Value = myType;
par.SpecificType = typeof(MyType);
cmd.Parameters.Add( par );
...

Все это объяснено в «Доступ к перечислениям и композитам PostgreSQL» .

Видимо, это станет проще в версии 4.0 Npgsql.Я сам протестировал его с Npgsql версии 3.2.7.


Редактировать:

Другое решение - не указывать NpgsqlDbType, а передавать объект MyType в конструкторе.

...
MyType myType = new MyType();
myType.X = 1;
myType.Y = 2;
par = new NpgsqlParameter( "MyType2", myType );
cmd.Parameters.Add( par );
...

Тогда правильный тип Postgres вращается типом сопоставления типа C # и Prostgres, установленным с MapCompositeGlobally<>() ранее.Явная установка SpecificType тогда не требуется.

...