admin管理员组

文章数量:1362834

I'm working with Npgsql and and Dapper I have some tables in PostgreSql with a composite data type:

create type datetimeoffset as
(
    "DateTimeUtc" timestamp without time zone,
    "Offset" smallint
);

To represent this data type I have the following class:

 public class PgDateTimeOffset
 {
     public PgDateTimeOffset(DateTime dateTimeUtc, short offset)
     {
         DateTimeUtc = DateTime.SpecifyKind(dateTimeUtc, DateTimeKind.Utc);
         Offset = offset;
     }

     public DateTime DateTimeUtc { get; set; }
     public short Offset { get; set; }
}

Let's say I have a table:

create table mytable
(
    Id int not null generated always as identity,
    Timestamp datetimeoffset
);

And I have code to write to this table:

public void Insert(PgDateTimeOffset timestamp)
{
    Connection.Execute("INSERT INTO mytable (Timestamp) VALUES (@timestamp)", new { timestamp });
}

I also created a type handler:

public class DapperDateTimeOffsetTypeHandler: SqlMapper.TypeHandler<DateTimeOffset>
{
    public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
    {
        // When sending data, create an instance of DateTimeOffsetPg
        parameter.Value = (PgDateTimeOffset)value;
    }

    public override DateTimeOffset Parse(object value)
    {
        // When reading data, cast the object to DateTimeOffsetPg
        if (value is PgDateTimeOffset composite)
        {
            return composite;
        }
        if(value is DateTimeOffset dto)
        {
            return dto;
        }
        if (value is DateTime dt)
        {
            return dt;
        }
        throw new DataException("Unexpected type when converting to DateTimeOffset.");
    }
}

And I am registering the type handler:

SqlMapper.AddTypeHandler(new DapperDateTimeOffsetTypeHandler());

And the composite type is registered:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.MapComposite<PgDateTimeOffset>("datetimeoffset");
var dataSource = dataSourceBuilder.Build();

The problem is I continue getting this exception:

System.NotSupportedException
  HResult=0x80131515
  Message=The member timestamp of type Neurologistica.Data.Repositories.PostgreSql.PgDateTimeOffset cannot be used as a parameter value
  Source=Dapper
  StackTrace:

Any idea? What am I missing?

I'm working with Npgsql and and Dapper I have some tables in PostgreSql with a composite data type:

create type datetimeoffset as
(
    "DateTimeUtc" timestamp without time zone,
    "Offset" smallint
);

To represent this data type I have the following class:

 public class PgDateTimeOffset
 {
     public PgDateTimeOffset(DateTime dateTimeUtc, short offset)
     {
         DateTimeUtc = DateTime.SpecifyKind(dateTimeUtc, DateTimeKind.Utc);
         Offset = offset;
     }

     public DateTime DateTimeUtc { get; set; }
     public short Offset { get; set; }
}

Let's say I have a table:

create table mytable
(
    Id int not null generated always as identity,
    Timestamp datetimeoffset
);

And I have code to write to this table:

public void Insert(PgDateTimeOffset timestamp)
{
    Connection.Execute("INSERT INTO mytable (Timestamp) VALUES (@timestamp)", new { timestamp });
}

I also created a type handler:

public class DapperDateTimeOffsetTypeHandler: SqlMapper.TypeHandler<DateTimeOffset>
{
    public override void SetValue(IDbDataParameter parameter, DateTimeOffset value)
    {
        // When sending data, create an instance of DateTimeOffsetPg
        parameter.Value = (PgDateTimeOffset)value;
    }

    public override DateTimeOffset Parse(object value)
    {
        // When reading data, cast the object to DateTimeOffsetPg
        if (value is PgDateTimeOffset composite)
        {
            return composite;
        }
        if(value is DateTimeOffset dto)
        {
            return dto;
        }
        if (value is DateTime dt)
        {
            return dt;
        }
        throw new DataException("Unexpected type when converting to DateTimeOffset.");
    }
}

And I am registering the type handler:

SqlMapper.AddTypeHandler(new DapperDateTimeOffsetTypeHandler());

And the composite type is registered:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.MapComposite<PgDateTimeOffset>("datetimeoffset");
var dataSource = dataSourceBuilder.Build();

The problem is I continue getting this exception:

System.NotSupportedException
  HResult=0x80131515
  Message=The member timestamp of type Neurologistica.Data.Repositories.PostgreSql.PgDateTimeOffset cannot be used as a parameter value
  Source=Dapper
  StackTrace:

Any idea? What am I missing?

Share Improve this question edited 2 days ago silkfire 26k16 gold badges91 silver badges114 bronze badges asked Feb 17 at 14:54 Yván EcarriYván Ecarri 1,73818 silver badges42 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

There are several things that I did to make this work.

First of all as far as I can see the parameterless ctor is required for the complex type:

public class PgDateTimeOffset
{
    public PgDateTimeOffset()
    {
        
    }

    // ...
}

Then since you don't use the standard snake-case naming for the PostgreSQL then NpgsqlNullNameTranslator usage is needed for the mapping:

dataSourceBuilder
    .MapComposite<PgDateTimeOffset>("datetimeoffset", new NpgsqlNullNameTranslator());

Then dapper mapping I used was to DbType.Object:

SqlMapper.AddTypeMap(typeof(PgDateTimeOffset), DbType.Object);

And last but not least - the I've used non-UTC time to insert:

PgDateTimeOffset timestamp = new PgDateTimeOffset
{
    DateTimeUtc = DateTime.Now,
    Offset = 3
};

The full snippet I've used for testing:

var dataSourceBuilder = new NpgsqlDataSourceBuilder(...);
dataSourceBuilder.MapComposite<PgDateTimeOffset>("datetimeoffset", new NpgsqlNullNameTranslator());
var dataSource = dataSourceBuilder.Build();
SqlMapper.AddTypeMap(typeof(PgDateTimeOffset), DbType.Object);

using var conn = dataSource.CreateConnection();
conn.Open();

PgDateTimeOffset timestamp = new PgDateTimeOffset
{
    DateTimeUtc = DateTime.Now,
    Offset = 3
};
conn.Execute("INSERT INTO mytable (Timestamp) VALUES (@timestamp)", new { timestamp });
var pgDateTimeOffsets = conn.Query<PgDateTimeOffset>("select Timestamp from mytable; ")
    .ToList();

Notes:

Since 6th version as far as I remember Npgsql maps UTC DateTime to timestamp with time zone:

The .NET and PostgreSQL types differ in the resolution and range they provide; the .NET type usually have a higher resolution but a lower range than the PostgreSQL types:

PostgreSQL type Precision/Range .NET Native Type Precision/Range
timestamp with time zone 1 microsecond, 4713BC-294276AD DateTime (UTC) 100 nanoseconds, 1AD-9999AD
timestamp without time zone 1 microsecond, 4713BC-294276AD DateTime (Unspecified) 100 nanoseconds, 1AD-9999AD
date 1 day, 4713BC-5874897AD DateOnly (6.0+), DateTime 100 nanoseconds, 1AD-9999AD
time without time zone 1 microsecond, 0-24 hours TimeOnly (6.0+), TimeSpan 100 nanoseconds, -10,675,199 - 10,675,199 days
time with time zone 1 microsecond, 0-24 hours DateTimeOffset (ignore date) 100 nanoseconds, 1AD-9999AD
interval 1 microsecond, -178000000-178000000 years TimeSpan 100 nanoseconds, -10,675,199 - 10,675,199 days

So if you want to use UTC it would be better to use timestamp with time zone for your type.

See also:

  • Need in-depth understanding of NpgSQL DateTimeOffset processing
  • How to say Datetime - timestamp without time zone in EF Core 6.0

本文标签: cComposite type parameter in PostgreSql using Npgsql Stack Overflow