Repository: npgsql/npgsql

Issue #5415: Mechanism for writing unmapped composites
Created: 2023-11-19T10:43:03Z
Npgsql support mapping PG composite types to arbitrary .NET types, but we also want to support interacting with composite types without mapping them. Support for reading composite types has been done in #3558 (via nested data readers), but it's not yet possible to write them.
Possible options:

Accept a DbDataReader as a parameter value; that's consistent with how we do reading. However:

DbDataReader is an abstraction, and AFAIK there's no easy/nice implementations that users can use to just instantiate, populate and give us.
Using DbDataReader, there isn't a way to properly represent the difference between a nested composite, and an array of the same nested composite that happens to contain one element - see test NestedDataReaderTests.Composite on the read side. On the read side this isn't so important, but on the right side Npgsql would write the wrong thing and the operation would fail.


Accept a DataTable as a parameter value.

DataTable is readily available in .NET, and unlike DbDataReader can just be instantiated and used - that's great.
However, we'd probably require users to fill in the DataTable's schema, which is a heavy and cumbersome.
DataTable also has lots of other metadata that isn't relevant here, since it models a full relational table (primary key constraints, computed columns with expressions...)


ValueTuple/Tuple/ITuple

Very lightweight and easy, but has no support for field names; this means that binding the .NET tuple values to the PG composite fields can only happen positionally. This is potentially a big pit of failure - a name-based binding seem important here.

Duplicate issue #5041: Map DataTable parameters to composite
Created: 2023-04-26T16:01:06Z
Hello Team,
I'm trying to run a function that has multiple parameters, including DataTable, integer, integer, and refcursor, using NpgSql to connect to a Postgres database. Here is how my function looks,
CREATE OR REPLACE FUNCTION ************(p_deallocationsla schema.udtt_deallocationservicelevel[], p_dealid integer, p_userid integer, result refcursor)
RETURNS refcursor
LANGUAGE plpgsql
AS
and this is the.net code I used to call the function
public async Task *********(int dealId, DataTable dealServiceLevelData, int userId)
{
IEnumerable dsSaveResponse = new List();
var parameters = new PgParam();
var json = JsonConvert.SerializeObject(dealServiceLevelData);
parameters.Add("deallocationsla", dealServiceLevelData, NpgsqlTypes.NpgsqlDbType.?);
parameters.Add("p_dealid", dealId, NpgsqlTypes.NpgsqlDbType.Integer);
parameters.Add("p_userid", userId, NpgsqlTypes.NpgsqlDbType.Integer);
dsSaveResponse = await _postGresDapperBaseRepository.GetAllAsync(DatabaseName.SMPSPostGres, DbConstants.UpsertDealServiceLevelInfo.ToLower(), parameters);
return dsSaveResponse.FirstOrDefault();
}
I would like to know what NpgsqlDbType we should provide for the dataTable (schema.udtt_deallocationservicelevel[]).
We are utilizing SqlDbType.Structured in MSSQL with SqlParameter, but it's missing from NpgsqlDbType.
Could you kindly check and let us know the answer?
Further technical details
Npgsql version:5.0.7
PostgreSQL version:
Operating system: Windows

Closing comment (2025-01-02T15:54:28Z): Formally, DataTable will also be strongly typed, as its construction is performed based on classes marked with a special attribute. The class fields are then compared with the metadata representing the table type columns. Only after all checks are successfully passed is the DataTable constructed.

DataTable itself is not strongly typed in the .NET sense - the fact that it carries schema information with it doesn't change that. In your specific case you may construct your DataTables automatically based on whatever criteria, but I'm thinking about this as a general feature in Npgsql rather than for your very specific use case.
In any case... I think that in principle we do want to allow using composites in Npgsql without going through mapping to a POCO type; that can be important for totally dynamic scenarios (where there can be no POCO types available for mapping in advance). Currently the POCO-based approach is also incompatible with NativeAOT because of code generation; a more dynamic approach would work around that, although we can also enable POCO composite mapping for NativeAOT as well (via source generation). Note that for the reading side we did implement #3558, allowing reading arbitrary PG records via nested DbDataReaders (this is similar, but for the write side).
Now, DataTable specifically as a way to dynamically map PG composites (without a POCO) aren't ideal: DataTable is meant to represent a full database table, and so includes a lot more information that isn't relevant to composite mapping (primary keys, foreign keys and lots of other metadata). On the other hand, it's readily-available in .NET itself, and wouldn't require introducing a whole new type just to carry that dynamic information.
I'm keeping this closed as a duplicate of #5415, which already tracks introducing a non-POCO, dynamic mechanism for writing composites; I'll add a bit there to discuss DataTable as an option, and we can revisit in the future which option makes most sense as more users request this.