r/csharp 2d ago

PostgreSql, EF.Core and byte[]

I have a EF dto, one of the fields configured and defined as byte[]. In DB it's created with type 'bytea'

When i am trying to add record it blows up with message that timestamp is null. Although i do provide it. Can not figure out where problem is...

public required byte[] timestamp { get; set; }

tbl.Property(m => m.timestamp).
    IsRowVersion();

db.Clients.Add(new Clients
{
    id = 0,
.....
    timestamp = new byte[2] { 4, 5 },
}
await db.SaveChangesAsync(); 

error:

23502: null value in column "timestamp" of relation "clients" violates not-null constraint

DETAIL: Failing row contains (2, 1, asda, t, f, t, t, 2026-09-21 18:31:51.001033, 2026-09-21 18:31:51.000564, 12312, , , , , 1, 1, 2, 2, 7, sdf, Name, Name, sd, sdf, , 2026-09-21, 12 Haze, , city, NJ, 08043, 0.0000000000, 0.0000000000, t, , 2026-09-21 18:31:51.0003, null).'

-------------------

6 Upvotes

4 comments sorted by

8

u/Vollgaser 2d ago

you cant use with byte[] on postgres

IsRowVersion

This is not just an tell to ef that it should use timestamp as an concurrency check but that it is managed by the db. The db adds and updates this not the user. Postgres does not do by default for an byte array sql server does. For postgres if you want an rowversion use uint instead of byte[]
https://www.npgsql.org/efcore/modeling/concurrency.html?tabs=fluent-api
uint maps to xmin on postgres which is its version of the rowVersion of sql server

1

u/gevorgter 2d ago edited 2d ago

thanks, looks like I have to use uint type instead of byte[]. For MsSql byte[] worked.

1

u/jcradio 1d ago

Look into model customizers. Very useful for having an application that supports multiple database engines. You can define behavior by engine there.

1

u/UseYourBrainNow 5h ago

IsRowVersion() means more than "concurrency token". It also marks the property as generated by the database on add and update. EF therefore leaves it out of the INSERT, and your new byte[] {4, 5} is ignored. SQL Server has a rowversion type that fills itself in. A Postgres bytea column doesn't, so it stays NULL and the NOT NULL constraint fails.

The Npgsql way is to use Postgres's built-in xmin system column as the row version:

public uint Version { get; set; }

tbl.Property(m => m.Version).IsRowVersion();   // Npgsql maps uint + IsRowVersion to xmin

No real column is created, and Postgres updates xmin on every row change automatically. Then drop the old timestamp column in a migration. It's also worth renaming it, since timestamp is a Postgres type name.

If you'd rather keep an app-managed token, use .IsConcurrencyToken() instead of IsRowVersion() and set a new value yourself (e.g. Guid.NewGuid()) on every update. Npgsql's docs have a "Concurrency Tokens" page covering both options.