skip to Main Content

I’m trying to insert data with parameters to PostgreSQL 16 in C#. I have the following code:

using (var myConnection = new NpgsqlConnection(myConnectionString))
{
    myConnection.Open();

    var insertAllData = $@"
        DO $$
        DECLARE
            new_production_item_id INTEGER;
        BEGIN
            INSERT INTO public.production_items (my_data_type_id, created_by, creation_time_utc)
            VALUES (@MyTypeId, @CreatedBy, @CreationTimeUtc)
            RETURNING Id INTO new_production_item_id;

            INSERT INTO public.some_other_table (some_other_table_id, some_additional_table)
            VALUES (new_production_item_id, @SomeAdditionalTableValue);
        END $$;
    ";

    using (var insertAllDataCommand = new NpgsqlCommand(insertAllData, myConnection))
    {
        insertAllDataCommand.Parameters.Add(new NpgsqlParameter<int>("MyTypeId", (int)DataTypeEnum.SomeTypeId) { NpgsqlDbType = NpgsqlDbType.Integer });
        insertAllDataCommand.Parameters.Add(new NpgsqlParameter<int>("CreatedBy", (int)400) { NpgsqlDbType = NpgsqlDbType.Integer });
        insertAllDataCommand.Parameters.Add(new NpgsqlParameter<DateTime>("CreationTimeUtc", (new DateTime(2018,1,1,12,0,0)).ToUniversalTime()) { NpgsqlDbType = NpgsqlDbType.TimestampTz });

        insertAllDataCommand.ExecuteNonQuery();
    }
}

However, I receive

Npgsql.PostgresException: ‘42703: column "mytypeid" does not exist’

It surprises me because it means that parameter @MyTypeId is considered as a column, rather than as a parameter.

I have also tried changing the @ sign to a : sign. I have tried changing from upper cased table and column names (as was previously) to lower case names with underscore (as it is in the code shown). None of these helped.

I need transaction because the code is in fact more complex, this is just simplified version of what I have.

What is the correct way to add parameters in PostgreSQL in C# so that they are considered as parameters, rather than column names?

As suggested in the comments, I have also tried:

using (var insertAllDataCommand = new NpgsqlCommand(insertAllData, myConnection)
{
    Parameters =
    {
        new("MyTypeId", (int)DataTypeEnum.SomeTypeId),
        new("CreatedBy", 400),
        new("CreationTimeUtc", (new DateTime(2018, 1, 1, 12, 0, 0)).ToUniversalTime())
    }
})
{
    insertAllDataCommand.ExecuteReader();
}

But it leads to the same error.

2

Answers


  1. Chosen as BEST ANSWER

    I have solved my issue by using:

    string insertAllData = @"
        BEGIN;
    
        INSERT INTO production_items (my_data_type_id, created_by, creation_time_utc)
        VALUES (@MyDataTypeId, @CreatedBy, @CreationTimeUtc)
        RETURNING id;
    
        INSERT INTO Some_Other_Table (My_Item_Id, Work_Plan_Id)
        VALUES (currval(pg_get_serial_sequence('production_items', 'id')), @WorkPlanId);
        
        COMMIT;
    ";
    

    Basically, I use currval(pg_get_serial_sequence('production_items', 'id') to get the value inserted in the previous block.


  2. We can’t really help without the real query. The problem is in that query, not PostgreSQL or NpgSQL. If PostgreSQL complains about mytypeid it means the real SQL query contains exactly this string, without the @ prefix or title-casing. As it is, the question’s code contains several problems, beyond the query itself.

    The Parameters and Positional and Named Placeholders paragraphs in NpgSQL’s Basic Usage page show how easy it is to execute an INSERT with parameters.

    Using positional parameters ($1, $2):

    await using var cmd = new NpgsqlCommand("INSERT INTO table (col1) VALUES ($1), ($2)", conn)
    {
        Parameters =
        {
            new() { Value = "some_value" },
            new() { Value = "some_other_value" }
        }
    };
    
    await cmd.ExecuteNonQueryAsync();
    

    Or named parameters (p1, p2). The parameter name definitions do not contain the @ prefix

    await using var cmd = new NpgsqlCommand("INSERT INTO table (col1) VALUES (@p1), (@p2)", conn)
    {
        Parameters =
        {
            new("p1", "some_value"),
            new("p2", "some_other_value")
        }
    };
    
    await cmd.ExecuteNonQueryAsync();
    

    In both cases, RETURNING id can be used to return an auto-generated ID value. To read it, ExecuteReader or ExecuteReaderAsync should be used :

    var sql="""
            INSERT INTO table (col1) 
            VALUES 
                (@p1),
                (@p2)
            RETURNING id;
            """;
    
    await using var cmd = new NpgsqlCommand(sql, conn)
    {
        Parameters =
        {
            new("p1", "some_value"),
            new("p2", "some_other_value")
        }
    };
    
    using var reader=await cmd.ExecuteNonQueryAsync();
    while(reader.Read())
    {
        var id=reader.GetInt32(0);
        ...
    }
    

    Adopting this to the question’s query would look like this :

    var sql="""
            INSERT INTO public.production_items (my_data_type_id, created_by, creation_time_utc)
            VALUES (@MyTypeId, @CreatedBy, @CreationTimeUtc)
            RETURNING Id;
            """;
    
    await using var cmd = new NpgsqlCommand(sql, conn)
    {
        Parameters =
        {
            new("MyTypeId", (int)DataTypeEnum.SomeTypeId),
            new("CreatedBy", 400),
            new("CreationTimeUtc",new DateTime(2018, 1, 1, 12, 0, 0,DateTimeKind.Utc)
        }
    };
    
    using var reader=await cmd.ExecuteNonQueryAsync();
    ...
    

    In this case, since only one row is inserted, ExecuteScalar can be used to get the single result value:

    var newId=await cmd.ExecuteScalarAsync();
    

    This can be simplified to a single line using eg Dapper, to create the entire command and parameters automatically, from a parameter object’s properties:

    var theParams=new {
        MyTypeId=DataTypeEnum.SomeTypeId,
        CreatedBy=400,
        CreationTimeUtc"=new DateTime(2018, 1, 1, 12, 0, 0,DateTimeKind.Utc)
    };
    
    var newId=con.ExecuteScalar(sql,theParams);
    

    Batching

    It seems the actual question is how to batch multiple queries. This is shown in the Batching section of the Basic Usage docs. There are two ways to do this.

    The legacy way is to create a single string, separating the queries by a semicolon. This isn’t supported by PostgreSQL itself though. NpgSql has to parse the query, find the semicolons and split the string into individual statements :

    var sql="""
            INSERT INTO table (col1) VALUES ('foo'); 
            SELECT * FROM table
            """;
    await using var cmd = new NpgsqlCommand(sql, conn);
    await using var reader = await cmd.ExecuteReaderAsync();
    

    The current way is to use a NpgSqlBatch instance containing the individual commands:

    await using var batch = new NpgsqlBatch(conn)
    {
        BatchCommands =
        {
            new("INSERT INTO table (col1) VALUES ('foo')"),
            new("SELECT * FROM table")
        }
    };
    
    await using var reader = await batch.ExecuteReaderAsync();
    

    Inserting the generated ID from the first INSERT into another table isn’t as simple as using currval in the second INSERT though. Sequences aren’t affected by transactions so currval will return the current sequence value, not the last value created in the same batch or transaction. If 2 or more connections execute INSERT at the same time, they may both end up using the same ID value.

    In this particular case it’s possible to use a single statement, by using INSERT .. RETURNING as a normal query, in a CTE or subquery :

    with times as (
        INSERT INTO public.production_items (
            my_data_type_id, 
            created_by, 
            creation_time_utc)
        VALUES (@MyTypeId, @CreatedBy, @CreationTimeUtc)
        RETURNING Id
    )
    INSERT INTO public.some_other_table (
        some_other_table_id, 
        some_additional_table)
    SELECT 
        times.Id, 
        @SomeAdditionalTableValue
    from times;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search