ConventionMapper.Testing.EntityFrameworkCore 1.10.0

dotnet add package ConventionMapper.Testing.EntityFrameworkCore --version 1.10.0
                    
NuGet\Install-Package ConventionMapper.Testing.EntityFrameworkCore -Version 1.10.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ConventionMapper.Testing.EntityFrameworkCore" Version="1.10.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ConventionMapper.Testing.EntityFrameworkCore" Version="1.10.0" />
                    
Directory.Packages.props
<PackageReference Include="ConventionMapper.Testing.EntityFrameworkCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ConventionMapper.Testing.EntityFrameworkCore --version 1.10.0
                    
#r "nuget: ConventionMapper.Testing.EntityFrameworkCore, 1.10.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ConventionMapper.Testing.EntityFrameworkCore@1.10.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ConventionMapper.Testing.EntityFrameworkCore&version=1.10.0
                    
Install as a Cake Addin
#tool nuget:?package=ConventionMapper.Testing.EntityFrameworkCore&version=1.10.0
                    
Install as a Cake Tool

ConventionMapper.Testing.EntityFrameworkCore

Database checks for mappings into EF Core entities. The source generator's warnings about keys, lengths and database defaults (CM0027, CM0028, CM0034) come from the EF Core model; these checks run against a real database, so they also catch what the model gets wrong. Failures throw MapperAssertException from ConventionMapper.Testing, which xUnit, NUnit and MSTest all report.

dotnet add package ConventionMapper.Testing.EntityFrameworkCore

Supports SQL Server and SQLite, EF Core 8 and later.

Does what the map wrote survive the database?

DatabaseAssert.SavesRoundTrip maps a source object, inserts the entity, reads it back and fails for every column whose stored value differs from what the map wrote. It runs inside a transaction that is rolled back (or inside the test's own transaction when one is open), so nothing stays in the database.

[Test]
public void ToEndorsementForm_keeps_every_value()
{
    var quoteForm = new QuoteForm { FormName = "IL 00 21", PrintText = false };

    DatabaseAssert.SavesRoundTrip(context, (QuoteForm f) => f.ToEndorsementForm(policyId: 7), quoteForm);
}
// Form.PrintText: the map wrote false, the database holds true (the column has a database default: EF Core leaves a
// property holding its CLR default out of the insert, so the default applied; see CM0034).

What it finds:

  • Database defaults that replace a copied value. EF Core leaves a non-nullable bool or number holding its CLR default (false, 0) out of the insert when the column has a default, so the database's default is stored instead.
  • Text the database cut, numbers it rounded (decimal scale, real), and conversions that don't round-trip.
  • Inserts that fail: a value too long for its column, a missing foreign key row, a NOT NULL column the map leaves null. The failure carries the database's message.

Without a source, SampleData.Create<TSource>() fills one. prepare sets what the map leaves to the caller (foreign keys of rows the test seeded), and ignore skips members the database is expected to change. Generated keys, computed columns and row versions are not compared. datetime columns may differ by up to 1/300 of a second.

DatabaseAssert.SavesRoundTrip(context, (QuoteForm f) => f.ToEndorsementForm(policyId: 0), quoteForm,
    prepare: form => form.PolicyId = seededPolicy.PolicyId,
    ignore: new[] { nameof(EndorsementForm.ModifiedDate) });

Every map at once

DatabaseAssert.AllMappersSaveRoundTrip runs SavesRoundTrip for every generated create method of a mapper class whose result is an entity of the context, so a new map is checked against the database before it has a test of its own:

[Test]
public void Every_map_into_the_database_round_trips() =>
    DatabaseAssert.AllMappersSaveRoundTrip(context, typeof(Mappings));
// 1 of 24 runs of 12 maps into the database don't round-trip:
//   ToEndorsementForm(QuoteForm, Int32) with false and 0 values: After saving and reading back, these values changed:
//     EndorsementForm.PrintText: the map wrote false, the database holds true (... see CM0034).

Each map runs twice: with sample data, and with false and 0 for every bool and number (sample data never holds them, and they are what a database default replaces). Parameters get sample data too. Foreign keys are not checked inside the rolled-back transaction, so no related rows need seeding; sample strings are cut to the column's maximum length (that's CM0027's job), sample decimals are rounded to the column's scale, and xml columns get a valid document. Inside the test's own transaction, each run is rolled back to a savepoint, so runs with the same sample keys don't collide. Pass SampleDataOptions for values the database constrains (codes a check constraint allows) and skip for methods to leave out.

Does the model match the database?

Scaffolded models drift: a column is widened, a default is added, a column becomes nullable, and nobody scaffolds again. DatabaseAssert.ModelMatchesDatabase compares every mapped table and column of the model with the schema: whether it exists, its nullability, its maximum length (SQL Server) and whether it has a default.

[Test]
public void The_model_matches_the_database() =>
    DatabaseAssert.ModelMatchesDatabase(context);                       // or (context, typeof(Policy), typeof(Form))
// The EF Core model does not match the database:
//   dbo.tblLocations.TaxCode (Location.TaxCode): maximum length unlimited in the model, 5 in the database.
//   dbo.tblForms.PrintText (Form.PrintText): the database column has the default ((1)), the model does not know it.

Run it against a database with the real schema, such as a copy of production. A database created from the model (EnsureCreated, migrations of the same model) always matches.

Without a database in CI

Export the real schema once and commit it; every test run then checks the model against the file:

// Once, against a copy of production (and again after schema changes):
DatabaseAssert.ExportSchema(productionCopyContext, "schema/premium.json");

// In the test project (copy the file to the output directory):
[Test]
public void The_model_matches_the_production_schema() =>
    DatabaseAssert.ModelMatchesSchema(context, "schema/premium.json");

The file lists, for the model's tables only, each column's nullability, maximum length (SQL Server) and default, sorted for readable diffs. The context only needs its model; no connection is opened.

Product 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 was computed.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.10.0 99 10/27/2026

## 1.10.0 (unreleased)

`ReverseMap()` for enum maps, plus database-level checks, honest acknowledgements and version safety from adopting 1.9 in a large scaffolded codebase.

**Upgrading:**
- New warnings can appear on existing code: CM0034 (a copied `false` or `0` replaced by a database default) and CM0035 (a `[MapReviewed]` entry that acknowledges nothing, such as entries added for the 1.9.0 CM0028 false positive). With warnings as errors, check before upgrading.
- ConventionMapper.Testing now fails when the mapper was generated by another major or minor version of the generator. Reference the same version of both packages.

### Runtime package
- **`ReverseMap()` for enum maps:** `ConvertUsingEnumMapping(o => o.MapValue(Color.Purple, ColorDto.Violet).ReverseMap())` also creates `ColorDto` → `Color` with the same matching (by name, case-insensitive or by value) and every override inverted. `ReverseMap(fallback: Color.Red)` sets the reverse map's fallback (a forward `Fallback` can't be inverted). Two overrides onto the same value can't be inverted: validation reports it and mapping that value back throws. `ReverseMap()` on the map itself, before or after `ConvertUsingEnumMapping`, inverts the mapping too, matching AutoMapper.Extensions.EnumMapping.

### Source generator
- **CM0034 (warning):** a create method writes a non-nullable `bool`, number or enum of an EF Core entity whose column has a database default other than the CLR default (`HasDefaultValueSql("((1))")`, `HasDefaultValue(1)`). EF Core leaves a property holding `false` or `0` out of the insert, so the row silently gets the default instead of the copied value. Fix by making the property nullable or configuring `HasSentinel`, or acknowledge it with `[MapReviewed]`. Strings, `DateTime`, `Guid`, nullable members, keys, members with `HasSentinel` and defaults equal to the CLR default (`((0))`) are not reported.
- **CM0035 (warning):** a `[MapReviewed]` entry on a method or the mapper class that silences nothing: no method that uses it (the method, methods inheriting it through `[MapInherit]`, or every method for the class) writes the member as a key, a database-generated value, a navigation, a column with a database default or a string that can be too long. Stale acknowledgements would hide a future problem.
- **CM0036 (info):** a create method copies a value that can be null into a nullable entity member whose column has a database default; EF Core leaves null out of the insert, so the default is stored. Often intended, so information; acknowledge it with `[MapReviewed("X", For = ReviewKind.DatabaseDefault)]`.
- **`[MapReviewed(..., For = ReviewKind.Key)]`**: an acknowledgement can name the kinds of warning it covers (`Key`, `GeneratedValue`, `Navigation`, `DatabaseDefault`, `Length`; `All` by default, as before), so acknowledging a key write no longer hides a length problem on the same member. CM0035 tracks each attribute and its kinds.
- **`[MapReviewed(..., Reason = "...")]`**: the data-flow report lists every acknowledgement with its kinds and reason in a new "Acknowledged with [MapReviewed]" section.
- The EF model reader also reads `HasDefaultValue(...)` and `HasSentinel(...)`.
- Scaffolded-model tests: code shaped exactly like `dotnet ef dbcontext scaffold` output, with every diagnostic of a typical row copy listed, so false positives on scaffolded models fail the build of the library.

### Testing
- **Version check:** `MapperAssert` methods that read generated metadata (`WriteSets`, `Lineage`, `GeneratedCode`) fail with both versions when the generator's major or minor version differs from ConventionMapper.Testing's, instead of failing in confusing ways.
- `SampleDataOptions.Clone()`: an independent copy, to add values for one run.

### ConventionMapper.Testing.EntityFrameworkCore (new package)
- **`DatabaseAssert.SavesRoundTrip(context, map, source)`**: maps, inserts inside a rolled-back transaction, reads back, and fails for every value the database changed: a default that replaced a copied `false` (CM0034 in action), cut text, rounded numbers; failed inserts carry the database's message.
- **`DatabaseAssert.AllMappersSaveRoundTrip(context, typeof(Mappings))`**: `SavesRoundTrip` for every generated create method that returns an entity of the context, with sample data and again with `false` and `0` values (the ones a database default replaces). Foreign keys aren't checked inside the rolled-back transaction, so no rows need seeding; CHECK constraints, NOT NULL and unique indexes stay on, so a value the database rejects still fails.
- **`DatabaseAssert.ModelMatchesDatabase(context)`**: compares the EF Core model's tables and columns (existence, nullability, maximum length on SQL Server, defaults) with the real schema, so a scaffolded model that drifted is found before the generator's warnings mislead. SQL Server and SQLite.
- **`DatabaseAssert.ExportSchema(context, "schema.json")`** and **`ModelMatchesSchema(context, "schema.json")`**: export the real schema once (from a copy of production), commit the file, and check the model against it in every test run, without a database.

### Build
- Running the workflow manually on `main` publishes `<next version>-preview.<run>` packages to nuget.org (unless that version is already released), so changes can be tried in a real codebase before the release tag. Previews are published on request because nuget.org keeps every version.
- A `sqlserver` CI job runs the database checks against a real SQL Server 2022 and is required before packing.
- After a release tag, the workflow opens a pull request merging `main` into every `feature/*` branch that doesn't contain it yet.
- Release checklist in [RELEASING.md](https://github.com/katragvb/Conventionmapper/blob/main/RELEASING.md), including trying the preview in a real codebase before tagging.
- Package validation baselines moved to 1.9.1; the new package validates its API across target frameworks from its first release.

Full history: CHANGELOG.md in this package, or https://github.com/katragvb/Conventionmapper/blob/main/CHANGELOG.md