MoneyConventionExtensions
Namespace: RA.Utilities.Data.EntityFramework.Extensions
MoneyConventionExtensions maps a Money property to two separate database columns — a decimal Amount column and a string Currency column (through CurrencyConverter) — instead of storing the whole value in a single column.
🎯 Purpose
EF Core value converters map a single property to a single column, so a Money cannot be split across two columns with a converter such as MoneyConverter. HasMoney() maps the property as an EF Core complex property instead, giving Money.Amount and Money.Currency a column each.
Because complex type property discovery skips get-only members, HasMoney() maps both properties explicitly so the Money(decimal, Currency) constructor binds during materialization.
Use it when amounts must be queried, indexed, or aggregated in SQL. Use MoneyConverter when a single JSON string column is enough.
Signature
extension<T>(EntityTypeBuilder<T> entityTypeBuilder) where T : class
{
public ComplexPropertyBuilder<Money> HasMoney(Expression<Func<T, Money?>> money)
}
The method is declared as a C# 14 extension member on EntityTypeBuilder<T>; the calling syntax is unchanged. It accepts both Money and Money? properties and returns the complex-property builder.
🚀 How to Use
using RA.Utilities.Data.EntityFramework.Extensions;
public sealed class Wallet
{
public int Id { get; set; }
public Money? Balance { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Wallet>().HasMoney(w => w.Balance);
}
Keep configuring the columns
The returned ComplexPropertyBuilder<Money> lets you set column names, types, and other facets:
modelBuilder.Entity<Wallet>()
.HasMoney(w => w.Balance)
.Property(m => m.Amount)
.HasColumnType("decimal(18,4)");
📋 Behavior
- Column naming — columns are named after the property, e.g.
Balance_AmountandBalance_Currency. - Amount column type — the provider default for
decimal(decimal(18,2)on SQL Server,TEXTon SQLite) unless you configure it explicitly. - Required and optional — supports both
MoneyandMoney?properties; optional values are stored asNULLin both columns. - Multiple amounts per entity — several
Moneyproperties on the same entity each get their own column pair. - Updates — because
Moneyis immutable, updates persist by replacing the instance on a tracked entity. - Validation on read — the
Currencycolumn is materialized throughCurrencyConverter, so an invalid currency code fails on read. - Single-column alternative —
MoneyConverter(single JSON string column) remains available.
⚠️ Requirements
- The property type must be
MoneyorMoney?. Tmust be a reference type (where T : class).- If the same property is also configured with
HasConversion(new MoneyConverter()), the model contains conflicting mappings. Choose one approach per property.
🧠 Summary
HasMoney() is the two-column counterpart to MoneyConverter: prefer it when you want to query, index, or aggregate amounts in SQL, and MoneyConverter when a single JSON string column is enough.