Add EF Core MoneyConvention for auto-mapping Money types#314
Add EF Core MoneyConvention for auto-mapping Money types#314xavierjohn merged 2 commits intomainfrom
Conversation
Introduces MoneyConvention to Trellis.EntityFrameworkCore, enabling automatic mapping of Money properties as owned types with standardized column naming (e.g., Price, PriceCurrency) and correct precision. Updates ApplyTrellisConventions to register this convention. Modifies Money value object for EF Core compatibility. Adds documentation and a comprehensive test suite to verify convention behavior and override support. This eliminates the need for manual configuration of Money properties in EF Core entities.
There was a problem hiding this comment.
Pull request overview
Adds first-class EF Core convention support for Money so consumers can persist Money properties without manual OwnsOne configuration, while updating documentation and tests to describe and validate the behavior.
Changes:
- Introduces
MoneyConventionand registers it viaApplyTrellisConventionsto auto-mapMoneyas an owned type with standardized column naming. - Updates
Moneyto support EF Core materialization (private parameterless ctor + private setters). - Adds a new
MoneyConventionTestssuite and updates docs/README/changelog/API reference to document the convention and override behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
Trellis.EntityFrameworkCore/src/MoneyConvention.cs |
New EF Core convention to register Money as owned and configure column naming/precision/length. |
Trellis.EntityFrameworkCore/src/ModelConfigurationBuilderExtensions.cs |
Registers the new MoneyConvention inside ApplyTrellisConventions. |
Trellis.Primitives/src/Primitives/Money.cs |
Enables EF Core materialization via private setters + private parameterless constructor. |
Trellis.EntityFrameworkCore/tests/MoneyConventionTests.cs |
Adds tests validating column naming, precision/length, round-trips, and explicit override precedence. |
docs/docfx_project/articles/integration-ef.md |
Adds a “Money Property Convention” section to the EF Core integration guide. |
Trellis.EntityFrameworkCore/README.md |
Documents the Money convention in the package README. |
Trellis.EntityFrameworkCore/NUGET_README.md |
Documents the Money convention for NuGet package description. |
trellis-api-reference.md |
Updates API reference to mention Money auto-mapping via conventions. |
CHANGELOG.md |
Notes the addition of the Money convention and Money EF materialization support. |
.github/copilot-instructions.md |
Adds internal guidance documenting Money EF Core behavior. |
.github/copilot-instructions.md
Outdated
| | `Price` | `Price` | `PriceCurrency` | | ||
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | | ||
|
|
||
| Amount columns: `decimal(18,2)`. Currency columns: `nvarchar(3)` (ISO 4217). |
There was a problem hiding this comment.
This line states amount columns are decimal(18,2), but Money supports some 3-decimal currencies. Update this guidance to match the convention’s final precision/scale decision, or document the rounding/truncation limitation.
| Amount columns: `decimal(18,2)`. Currency columns: `nvarchar(3)` (ISO 4217). | |
| By default, amount columns are mapped as `decimal(18,2)`. This works for most currencies but will round or truncate 3-decimal currencies to 2 decimal places; configure `HasPrecision` (for example, `HasPrecision(18,3)` or higher) on the amount property if you need full 3-decimal support. Currency columns: `nvarchar(3)` (ISO 4217). |
| amount.Builder.HasAnnotation("Relational:ColumnName", navigationName); | ||
| amount.Builder.HasPrecision(18); | ||
| amount.Builder.HasScale(2); | ||
| } |
There was a problem hiding this comment.
The convention forces Money.Amount to scale 2, but Money supports currencies with 3 decimal places (e.g., BHD/KWD/OMR/TND). On providers that enforce scale, this will round/truncate persisted values and break round-trips. Consider using a scale that can represent the maximum supported decimals (e.g., 3) or persisting minor units instead.
| amount.Builder.HasAnnotation("Relational:ColumnName", navigationName); | ||
| amount.Builder.HasPrecision(18); |
There was a problem hiding this comment.
"Relational:ColumnName" is used as a magic-string annotation key (twice in this method). Prefer EF Core’s relational constants/APIs (e.g., RelationalAnnotationNames.ColumnName / relational HasColumnName) so this doesn’t depend on an internal string key.
trellis-api-reference.md
Outdated
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,2)` | `nvarchar(3)` | | ||
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,2)` | `nvarchar(3)` | |
There was a problem hiding this comment.
This table lists Money.Amount as decimal(18,2), but Money supports currencies with 3 decimal places (e.g., BHD/KWD/OMR). Either update the convention (and this table) to support the max scale, or explicitly document the rounding/truncation limitation for those currencies.
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,2)` | `nvarchar(3)` | | |
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,2)` | `nvarchar(3)` | | |
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,3)` | `nvarchar(3)` | | |
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,3)` | `nvarchar(3)` | |
| | `Price` | `Price` | `PriceCurrency` | | ||
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | | ||
|
|
||
| Amount columns use `decimal(18,2)` precision. Currency columns use `nvarchar(3)` (ISO 4217). |
There was a problem hiding this comment.
Docs state amount columns use decimal(18,2), but Money supports some 3-decimal currencies. Consider updating the convention/docs to a scale that can represent the max supported decimals, or call out the rounding/truncation limitation explicitly.
| Amount columns use `decimal(18,2)` precision. Currency columns use `nvarchar(3)` (ISO 4217). | |
| By default, amount columns are configured as `decimal(18,2)`, which is sufficient for currencies with 2 decimal places but will round or truncate 3-decimal currencies. Use a higher scale (for example, `decimal(18,3)` or `HasPrecision(19, 4)` as shown below) if you need to store 3-decimal currencies without loss. Currency columns use `nvarchar(3)` (ISO 4217). |
| | `Price` | `Price` | `PriceCurrency` | | ||
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | | ||
|
|
||
| Amount columns use `decimal(18,2)` precision. Currency columns use `nvarchar(3)` (ISO 4217). |
There was a problem hiding this comment.
This section says amount columns use decimal(18,2), but Money supports some 3-decimal currencies. Consider updating the convention/docs to support the max scale (or document the rounding/truncation limitation).
| Amount columns use `decimal(18,2)` precision. Currency columns use `nvarchar(3)` (ISO 4217). | |
| By default, amount columns use `decimal(18,2)` precision. This supports most currencies, but three-decimal currencies (for example, JOD, KWD, BHD) will be rounded or truncated to two decimal places unless you override the precision via `OwnsOne` (see below). Currency columns use `nvarchar(3)` (ISO 4217). |
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,2)` | `nvarchar(3)` | | ||
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,2)` | `nvarchar(3)` | |
There was a problem hiding this comment.
The table lists the amount type as decimal(18,2), but Money supports currencies with 3 decimal places. Either adjust the convention (and this table) to support the max scale, or explicitly document the persistence rounding behavior for 3-decimal currencies.
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,2)` | `nvarchar(3)` | | |
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,2)` | `nvarchar(3)` | | |
| | `Price` | `Price` | `PriceCurrency` | `decimal(18,3)` | `nvarchar(3)` | | |
| | `ShippingCost` | `ShippingCost` | `ShippingCostCurrency` | `decimal(18,3)` | `nvarchar(3)` | |
CHANGELOG.md
Outdated
|
|
||
| #### Trellis.EntityFrameworkCore — Money Convention | ||
|
|
||
| - **`MoneyConvention`** — `ApplyTrellisConventions` now automatically maps `Money` properties as owned types with `{PropertyName}` (decimal 18,2) + `{PropertyName}Currency` (nvarchar 3) columns. No `OwnsOne` configuration needed. Explicit `OwnsOne` takes precedence. |
There was a problem hiding this comment.
Changelog claims {PropertyName} is stored as decimal 18,2, but Money supports some 3-decimal currencies. If you change the convention to a higher scale to avoid truncation, update this entry; otherwise consider noting the limitation/rounding behavior.
| - **`MoneyConvention`** — `ApplyTrellisConventions` now automatically maps `Money` properties as owned types with `{PropertyName}` (decimal 18,2) + `{PropertyName}Currency` (nvarchar 3) columns. No `OwnsOne` configuration needed. Explicit `OwnsOne` takes precedence. | |
| - **`MoneyConvention`** — `ApplyTrellisConventions` now automatically maps `Money` properties as owned types with `{PropertyName}` (decimal 18,2) + `{PropertyName}Currency` (nvarchar 3) columns. Note: this default mapping uses two decimal places; currencies requiring three decimal places will be rounded by the database provider to two decimal places. No `OwnsOne` configuration needed. Explicit `OwnsOne` takes precedence. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #314 +/- ##
==========================================
+ Coverage 85.51% 85.57% +0.05%
==========================================
Files 158 159 +1
Lines 4930 4949 +19
Branches 1012 1019 +7
==========================================
+ Hits 4216 4235 +19
+ Misses 419 418 -1
- Partials 295 296 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
MoneyConvention now maps Money.Amount columns as decimal(18,3) (instead of decimal(18,2)) to support all ISO 4217 minor units, including currencies with three decimal places (e.g., KWD, BHD). Docs and tests updated to reflect this change. Implementation now uses RelationalAnnotationNames.ColumnName and sets scale to 3. Added test for three-decimal currency round-trip. Switched package references to Microsoft.EntityFrameworkCore.Relational.
| // In OnModelCreating or ConfigureConventions | ||
| configurationBuilder.ApplyTrellisConventions(typeof(Order).Assembly); | ||
| // Auto-registers converters for all IScalarValue and RequiredEnum types | ||
| // Auto-maps Money properties as owned types (Amount + Currency columns) |
There was a problem hiding this comment.
The comment says ApplyTrellisConventions can be used in OnModelCreating, but the API is an extension on ModelConfigurationBuilder and is only callable from ConfigureConventions. Update this snippet/comment to avoid misleading usage.
| // In OnModelCreating or ConfigureConventions | |
| configurationBuilder.ApplyTrellisConventions(typeof(Order).Assembly); | |
| // Auto-registers converters for all IScalarValue and RequiredEnum types | |
| // Auto-maps Money properties as owned types (Amount + Currency columns) | |
| // In your DbContext.ConfigureConventions override | |
| protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) | |
| { | |
| configurationBuilder.ApplyTrellisConventions(typeof(Order).Assembly); | |
| // Auto-registers converters for all IScalarValue and RequiredEnum types | |
| // Auto-maps Money properties as owned types (Amount + Currency columns) | |
| } |
Introduces MoneyConvention to Trellis.EntityFrameworkCore, enabling automatic mapping of Money properties as owned types with standardized column naming (e.g., Price, PriceCurrency) and correct precision. Updates ApplyTrellisConventions to register this convention. Modifies Money value object for EF Core compatibility. Adds documentation and a comprehensive test suite to verify convention behavior and override support. This eliminates the need for manual configuration of Money properties in EF Core entities.