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
I have solved my issue by using:
Basically, I use
currval(pg_get_serial_sequence('production_items', 'id')
to get the value inserted in the previous block.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):
Or named parameters (p1, p2). The parameter name definitions do not contain the
@
prefixIn both cases,
RETURNING id
can be used to return an auto-generated ID value. To read it,ExecuteReader
orExecuteReaderAsync
should be used :Adopting this to the question’s query would look like this :
In this case, since only one row is inserted,
ExecuteScalar
can be used to get the single result value: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:
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 :
The current way is to use a
NpgSqlBatch
instance containing the individual commands: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 socurrval
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 :