Neon.EntityFrameworkCore
4.0.20-preview.2
See the version list below for details.
dotnet add package Neon.EntityFrameworkCore --version 4.0.20-preview.2
NuGet\Install-Package Neon.EntityFrameworkCore -Version 4.0.20-preview.2
<PackageReference Include="Neon.EntityFrameworkCore" Version="4.0.20-preview.2" />
<PackageVersion Include="Neon.EntityFrameworkCore" Version="4.0.20-preview.2" />
<PackageReference Include="Neon.EntityFrameworkCore" />
paket add Neon.EntityFrameworkCore --version 4.0.20-preview.2
#r "nuget: Neon.EntityFrameworkCore, 4.0.20-preview.2"
#:package Neon.EntityFrameworkCore@4.0.20-preview.2
#addin nuget:?package=Neon.EntityFrameworkCore&version=4.0.20-preview.2&prerelease
#tool nuget:?package=Neon.EntityFrameworkCore&version=4.0.20-preview.2&prerelease
Neon.EntityFrameworkCore
Provider agnostic Entity Framework Core extensions, instrumented with OpenTelemetry.
You can get started here: Neon.EntityFrameworkCore
dotnet add package Neon.EntityFrameworkCore
Targets net10.0 and Entity Framework Core 10.
What's in here
| API | Purpose |
|---|---|
DbSet<T>.ExistsAsync(...) |
Existence check by primary key, without materializing or tracking the entity. |
DbSet<T>.DeleteAsync(...) |
Delete by primary key, without loading the entity first. |
DbSet<T>.UpsertAsync(...) |
Stage an insert or an update depending on whether the key is already present. |
ModelBuilder.SetDefaultDateTimeKind(...) |
Make every DateTime property round trip with a known DateTimeKind. |
TracerProviderBuilder.AddNeonEntityFrameworkCore() |
Collect this library's OpenTelemetry activities. |
Looking for the YugabyteDB migration locking fix? That lives in Neon.EntityFrameworkCore.Npgsql, so that provider agnostic consumers of this package don't inherit a dependency on Npgsql.
Key based DbSet operations
FindAsync() is the usual way to reach an entity by its primary key, but it materializes and tracks
the entity — wasted work when all you wanted to know was whether a row exists, or you simply want it
gone.
using Neon.EntityFrameworkCore;
// SELECT EXISTS (SELECT 1 FROM users WHERE id = @p0) — nothing enters the change tracker.
if (await context.Users.ExistsAsync(userId))
{
// DELETE FROM users WHERE id = @p0 — sent immediately, no round trip to load it first.
await context.Users.DeleteAsync(userId);
}
// Stages an INSERT or an UPDATE. You still call SaveChangesAsync().
await context.Users.UpsertAsync(user, cancellationToken);
await context.SaveChangesAsync(cancellationToken);
All three work with composite keys — supply the values positionally, in the order the key properties are declared:
await context.Memberships.ExistsAsync(new object[] { tenantId, userId }, cancellationToken);
Each method has a params object[] overload for the common single column case, and an
(object[], CancellationToken) overload for when you need cancellation.
Three things worth knowing before you use them:
ExistsAsync()always hits the database. UnlikeFindAsync(), it won't reporttruefor an entity that has been added to the change tracker but not yet saved.DeleteAsync()bypasses the change tracker. It's built onExecuteDeleteAsync(), so already loaded copies of the entity are left stale, and cascade behaviours configured in your model are not applied — the database's foreign key actions are what take effect.UpsertAsync()is not atomic on its own. The existence check and the write are separate round trips, so a concurrent insert of the same key in between turns the staged insert into a duplicate key violation atSaveChangesAsync()time. Where that matters, use a serializable transaction or your provider's native merge (INSERT ... ON CONFLICTon PostgreSQL).
DateTime kind handling
Many providers discard DateTime.Kind on the way in and hand back DateTimeKind.Unspecified on the
way out, which makes it easy to end up with values whose kind depends on whether they came from the
database or from memory. One call at the end of OnModelCreating() removes the whole class of bug:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// ...configure entities...
modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
}
Writes apply ToUniversalTime(); reads apply DateTime.SpecifyKind(value, kind).
- Call it last. Properties discovered after the call don't get a converter.
- Pass
Utc. Because unspecified values are treated as local on write, the round trip is only lossless forDateTimeKind.Utc.Localrelabels UTC values without shifting them, corrupting them by the server's UTC offset;Unspecifiedis a no-op. - On Npgsql, keep the default
timestamp with time zonemapping. Npgsql accepts onlyKind=Utcfortimestamptzand onlyKind=Unspecifiedfortimestamp without time zone, so pointing this at a naive column produces a write the driver refuses. Againsttimestamptzthe read side is a no-op — Npgsql already returns UTC — and all the value is on the write side: a local or unspecifiedDateTimethat would otherwise blow up atSaveChanges()gets normalized into the UTC instant it meant.
OpenTelemetry tracing
Every public method emits an activity from a single ActivitySource named after the assembly.
Nothing is collected until you subscribe:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddNeonEntityFrameworkCore()
.AddOtlpExporter();
});
AddNeonEntityFrameworkCore() subscribes to this library's activities only. For a complete picture
you'll usually want EF Core's own instrumentation and your driver's alongside it:
tracing.AddNeonEntityFrameworkCore()
.AddEntityFrameworkCoreInstrumentation() // OpenTelemetry.Instrumentation.EntityFrameworkCore
.AddNpgsql() // Npgsql.OpenTelemetry
.AddOtlpExporter();
If you'd rather not reference the extension method, TracerProviderBuilderExtensions.ActivitySourceName
is the string to pass to AddSource().
The cost when nothing is listening is one null check per call: no Activity is allocated and none of
the tag values are computed. It's safe to leave this package referenced in projects that don't use
OpenTelemetry at all.
Activities emitted
| Activity | Kind | Display name |
|---|---|---|
ExistsAsync |
Client |
EXISTS <table> |
DeleteAsync |
Client |
DELETE <table> |
UpsertAsync |
Client |
UPSERT <table> |
SetDefaultDateTimeKind |
Internal |
SetDefaultDateTimeKind |
Tags
Names are exposed as constants on TraceTags. Where an OpenTelemetry semantic convention exists it's
used verbatim; everything else is namespaced under neon.efcore.*.
| Tag | On | Value |
|---|---|---|
db.operation.name |
key based ops | EXISTS, DELETE or UPSERT |
db.collection.name |
key based ops | The mapped table name |
db.namespace |
key based ops | The mapped schema, when the entity has one |
neon.efcore.entity.type |
key based ops | Full CLR type name of the entity |
neon.efcore.key.count |
key based ops | Number of key values supplied |
neon.efcore.result.exists |
ExistsAsync |
The result |
neon.efcore.result.rows_affected |
DeleteAsync |
Rows the DELETE affected |
neon.efcore.upsert.action |
UpsertAsync |
insert or update |
neon.efcore.datetime.kind |
SetDefaultDateTimeKind |
The DateTimeKind applied |
neon.efcore.model.properties_converted |
SetDefaultDateTimeKind |
How many properties got a converter |
Failures are recorded with Activity.AddException() and the span status is set to Error, so both
exception-event and error-rate views in your backend light up.
Primary key values are never recorded — only their count. Keys are so often personal data, and traces are so routinely exported to third party backends, that capturing them would be a liability rather than a feature. If you need to correlate a span with a specific row, add the tag yourself at the call site where you can make that judgement.
Documentation
Full API documentation: https://sdk.neonforge.com
Copyright © 2005-2024 by NEONFORGE LLC. All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore (>= 10.0.10)
- Microsoft.EntityFrameworkCore.Abstractions (>= 10.0.10)
- Microsoft.EntityFrameworkCore.Analyzers (>= 10.0.10)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Logging (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.ObjectPool (>= 10.0.10)
- Neon.Common (>= 4.0.20-preview.2)
- Newtonsoft.Json (>= 13.0.3)
- OpenTelemetry (>= 1.17.0)
- SharpZipLib.NETStandard (>= 1.0.7)
- System.ValueTuple (>= 4.6.2)
- YamlDotNet (>= 15.1.2)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Neon.EntityFrameworkCore:
| Package | Downloads |
|---|---|
|
Neon.EntityFrameworkCore.Npgsql
EntityFrameworkCore extensions for the Npgsql PostgreSQL provider, including advisory lock based migration locking for YugabyteDB and other Postgres wire compatible databases that don't implement LOCK TABLE. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.20 | 46 | 8/13/2026 |
| 4.0.20-preview.2 | 46 | 8/11/2026 |
| 4.0.20-preview.1 | 76 | 6/10/2026 |
| 4.0.20-preview.0 | 78 | 5/9/2026 |
| 4.0.19 | 125 | 4/29/2026 |
| 4.0.19-preview.4 | 69 | 4/26/2026 |
| 4.0.19-preview.3 | 68 | 4/17/2026 |
| 4.0.19-preview.2 | 207 | 8/14/2025 |
| 4.0.19-preview.1 | 167 | 8/13/2025 |
| 4.0.19-preview.0 | 172 | 8/13/2025 |
| 4.0.18 | 306 | 8/6/2025 |
| 4.0.17 | 329 | 4/15/2025 |
| 4.0.17-preview.0 | 228 | 4/14/2025 |
| 4.0.16 | 271 | 4/14/2025 |
| 4.0.15 | 266 | 4/10/2025 |
| 4.0.14 | 230 | 12/19/2024 |