RossWright.MetalCore.Data
2026.1.1
dotnet add package RossWright.MetalCore.Data --version 2026.1.1
NuGet\Install-Package RossWright.MetalCore.Data -Version 2026.1.1
<PackageReference Include="RossWright.MetalCore.Data" Version="2026.1.1" />
<PackageVersion Include="RossWright.MetalCore.Data" Version="2026.1.1" />
<PackageReference Include="RossWright.MetalCore.Data" />
paket add RossWright.MetalCore.Data --version 2026.1.1
#r "nuget: RossWright.MetalCore.Data, 2026.1.1"
#:package RossWright.MetalCore.Data@2026.1.1
#addin nuget:?package=RossWright.MetalCore.Data&version=2026.1.1
#tool nuget:?package=RossWright.MetalCore.Data&version=2026.1.1
Ross Wright's Metal Core Data Library
Copyright (c) 2023-2026 Pross Co.
Table of Contents
Entity Framework Extensions
Database Context
DbContextExtensions adds three utility methods to any DbContext that targets a relational database.
| Method | Description |
|---|---|
DatabaseExists() |
Returns true if the underlying relational database has been created |
CheckForChangesToAny<T>() |
Returns true if the change tracker holds pending changes for entity type T |
Obliterate() |
Drops all FK constraints, tables, and stored procedures — SQL Server only; intended for test teardown |
Obliterate()is destructive. It is designed for integration test teardown where a fresh schema is required between runs. Do not call it in production code.
SaveChanges with FK Errors
DbContextExtensions.SaveChangesAsyncWithFkErrors wraps SaveChangesAsync and provides structured handling for foreign key constraint violations. When EF throws a DbUpdateException caused by an FK constraint, the callback receives the parsed constraint name rather than requiring you to parse the exception message yourself.
await context.SaveChangesAsyncWithFkErrors(report =>
throw new InvalidOperationException($"Cannot delete: referenced by '{report.ConstraintName}'"));
| Method | Description |
|---|---|
SaveChangesAsyncWithFkErrors(onError) |
Calls SaveChangesAsync; on FK violation invokes onError with a ForeignKeyErrorReport; always re-throws the original exception |
ForeignKeyErrorReport properties:
| Property | Type | Description |
|---|---|---|
EntityName |
string |
Name of the entity type involved in the violation |
ConstraintName |
string? |
Database constraint name parsed from the exception message |
Values |
Dictionary<string, object?> |
Current property values of the violating entity, keyed by property name |
RefreshTable
RefreshTableExtensions.RefreshTable synchronizes a DbSet<DBENTITY> against an incoming collection of INENTITY objects in a single call, performing the insert/update/delete logic automatically. It returns an IRefreshResult reporting how many records were added, updated, and deleted.
var result = await context.Users.RefreshTable(incomingUsers);
// result.Adds, result.Updates, result.Deletes
By default, records absent from the incoming data are not deleted. Pass deleteSourceEntities: true to enable removal of absent records.
Record matching — overloads constrained to IHasId match by Guid Id. Overloads without that constraint accept a custom Func<DBENTITY, INENTITY, bool> isSame predicate.
Entity copying — by default, matching members are copied using CopyTo. Pass a custom Action<DBENTITY, INENTITY> update delegate to control exactly which fields are updated.
Data fetching — by default, ToListAsync() loads the current set. Pass a custom Func<DbSet<DBENTITY>, Task<List<DBENTITY>>> fetchOldData delegate to apply filters (e.g. restrict to a specific parent record).
The seven overloads cover combinations of these three axes:
| Overload | Match | Update | Fetch |
|---|---|---|---|
(dbSet, newData, deleteSourceEntities?) |
IHasId |
auto | default |
(dbSet, newData, Action update, deleteSourceEntities?) |
IHasId |
custom | default |
(dbSet, fetchOldData, newData, Action update, deleteSourceEntities?) |
IHasId |
custom | custom |
(dbSet, newData, Func isSame, deleteSourceEntities?) |
custom | auto | default |
(dbSet, newData, Func isSame, Action update, deleteSourceEntities?) |
custom | custom | default |
(dbSet, fetchOldData, newData, Func isSame, Action update, deleteSourceEntities?) |
custom | custom | custom |
(dbSet, fetchOldData, newData, Func isSame, Func add, Action update, deleteSourceEntities) |
custom | custom | custom + custom add |
IHasId is a core MetalCore contract (RossWright namespace) that requires a Guid Id property. Most database entity base classes implement it.
IRefreshResult
| Property | Description |
|---|---|
Adds |
Number of entities inserted |
Updates |
Number of entities updated |
Deletes |
Number of entities deleted |
Database Timing Interceptor
IDatabaseTimingInterceptor accumulates the total EF command execution time for the current DI scope, enabling per-request database timing without external tooling.
Register in Program.cs and attach to your DbContext options:
// Program.cs
services.AddDatabaseTimingInterceptor();
services.AddDbContext<AppDbContext>((sp, opts) =>
opts.UseSqlServer(connectionString)
.UseDatabaseTimingInterceptor(sp));
// Inject IDatabaseTimingInterceptor anywhere in the same scope
var ms = timingInterceptor.RunTimeInMilliseconds;
| Method / Member | Description |
|---|---|
AddDatabaseTimingInterceptor(IServiceCollection) |
Registers IDatabaseTimingInterceptor as a scoped EF interceptor |
UseDatabaseTimingInterceptor(DbContextOptionsBuilder, IServiceProvider) |
Attaches the interceptor to a DbContext options builder |
IDatabaseTimingInterceptor.RunTimeInMilliseconds |
Cumulative EF command execution time for the current scope in milliseconds |
Registration lifetime:
AddDatabaseTimingInterceptorregisters the interceptor as scoped, which is required for per-request timing to work correctly. Do not registerIDatabaseTimingInterceptoras a singleton — doing so will cause timing to accumulate across all requests rather than resetting per scope.
GeoCoder
IGeoCoderService provides a single method that resolves a US postal code or "City, State" string to a latitude/longitude coordinate pair.
Offline lookup only. The bundled
GeoCoderServiceuses an embedded data file — it does not contact any external API. Only US postal codes and "City, State" strings are supported. Unrecognized inputs throwMetalCoreException.
var coords = geoCoderService.GetCoordinates("90210"); // zip code
var coords = geoCoderService.GetCoordinates("Beverly Hills, CA"); // City, State
// coords.Lat, coords.Lng
Register with AddGeoCoderService():
services.AddGeoCoderService();
LatLong
LatLong is a value struct holding a latitude and longitude. It provides two utility methods for geographic calculations:
| Member | Description |
|---|---|
Lat |
Latitude in decimal degrees |
Lng |
Longitude in decimal degrees |
DistanceTo(double lat, double lng) |
Returns the distance in miles to the given coordinate using the Haversine formula, rounded to 2 decimal places |
CalcBound(double distance) |
Returns a bounding box (LatLong min, LatLong max) for the given radius in miles |
Service Reference
| Type / Method | Description |
|---|---|
IGeoCoderService.GetCoordinates(string address) |
Resolves an address or place string to a LatLong |
AddGeoCoderService(IServiceCollection) |
Registers IGeoCoderService as a singleton |
Installation
dotnet add package RossWright.MetalCore.Data
Or add directly to your project file:
<PackageReference Include="RossWright.MetalCore.Data" Version="*" />
See Also
| Package | Purpose |
|---|---|
RossWright.MetalCore |
Core extensions, utilities, options builders, load logging, exceptions, signing |
RossWright.MetalCore.Blazor |
Blazor WASM utilities: local storage, JS script loader, host builder extensions |
RossWright.MetalCore.Server |
ASP.NET Core messaging contracts, SMTP email service |
RossWright.MetalCore.Populi |
Zero-dependency test-data generator: names, addresses, emails, coordinates, dates, prices, and lorem ipsum |
License
All Ross Wright Metal Libraries including this one are licensed under Apache License 2.0 with Commons Clause.
You are free to:
- Use the libraries in any project (personal or commercial)
- Modify them
- Include them in products or services you sell
You may not:
- Sell the libraries themselves (or any product/service whose primary value comes from the libraries)
- Repackage them with minimal changes and sell them as your own standalone product
Full legal text: LICENSE.md
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- Geolocation (>= 1.2.1)
- Microsoft.EntityFrameworkCore (>= 10.0.3)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.3)
- RossWright.MetalCore (>= 2026.1.1)
-
net8.0
- Geolocation (>= 1.2.1)
- Microsoft.EntityFrameworkCore (>= 8.0.24)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.24)
- RossWright.MetalCore (>= 2026.1.1)
-
net9.0
- Geolocation (>= 1.2.1)
- Microsoft.EntityFrameworkCore (>= 9.0.13)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.13)
- RossWright.MetalCore (>= 2026.1.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on RossWright.MetalCore.Data:
| Package | Downloads |
|---|---|
|
RossWright.MetalCommand.Data
Licensed under Apache 2.0 with Commons Clause — free to use in commercial products, but you may not sell the Metal Libraries themselves or any product whose primary value comes from them. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2026.1.1 | 150 | 5/17/2026 | |
| 2026.1.0 | 149 | 5/14/2026 | |
| 2026.0.0 | 139 | 4/26/2026 | |
| 10.0.12 | 160 | 3/10/2026 | |
| 10.0.11 | 156 | 2/23/2026 | |
| 10.0.10 | 150 | 2/16/2026 | |
| 10.0.9 | 143 | 2/16/2026 | |
| 10.0.8 | 146 | 2/16/2026 | |
| 10.0.7 | 162 | 2/12/2026 | |
| 10.0.6 | 144 | 2/12/2026 | |
| 10.0.5 | 151 | 1/26/2026 | |
| 10.0.4 | 158 | 1/24/2026 | |
| 10.0.3 | 166 | 1/16/2026 | |
| 10.0.2 | 163 | 1/11/2026 | |
| 10.0.1 | 166 | 1/10/2026 | |
| 10.0.0 | 163 | 1/9/2026 | |
| 8.5.3 | 181 | 1/11/2026 | |
| 8.5.2 | 189 | 1/11/2026 | |
| 8.5.1 | 181 | 1/10/2026 | |
| 8.5.0 | 179 | 1/10/2026 |
[2026.1.1] - 5/17/2026
- Minor Fixes, additional Unit Tests and documentation.
[2026.1.0] - 5/13/2026
- Minor Fixes, additional Unit Tests and documentation.
[2026.0.0] - 4/26/2026
- Initial public release
** Prior versions released of this library were private and not intended for public use. Use version 2026.0.0 or later.**