EF Code First
decimal precision
decimal scale
entity framework
database design

Decimal precision and scale in EF Code First

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Decimal columns are one of the places where code-first defaults can cause expensive surprises. If you let Entity Framework choose the database type without thinking about precision and scale, values can be rounded, truncated, or stored with a shape that does not match the business rule.

What Precision and Scale Mean

A decimal definition such as decimal(18, 2) has two parts:

  • precision is the total number of digits that can be stored
  • scale is the number of digits to the right of the decimal point

So decimal(18, 2) can store values up to 9999999999999999.99, while decimal(5, 4) is better suited for rates such as 0.1250. Choosing these numbers is a domain decision, not just a database detail. Money, tax rates, discounts, and scientific measurements all need different limits.

In EF Code First, making the column shape explicit helps in three ways. It protects data integrity, keeps migrations predictable, and makes the intent clear to the next developer who reads the model.

Configuring Precision in Code First

The most portable way to set precision and scale is the Fluent API. The example below defines an Invoice entity and ensures the total and tax rate map to the right SQL types.

csharp
1using Microsoft.EntityFrameworkCore;
2
3public class Invoice
4{
5    public int Id { get; set; }
6    public decimal Total { get; set; }
7    public decimal TaxRate { get; set; }
8}
9
10public class BillingContext : DbContext
11{
12    public DbSet<Invoice> Invoices => Set<Invoice>();
13
14    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
15        => optionsBuilder.UseSqlServer(
16            "Server=(localdb)\\MSSQLLocalDB;Database=BillingDemo;Trusted_Connection=True;");
17
18    protected override void OnModelCreating(ModelBuilder modelBuilder)
19    {
20        modelBuilder.Entity<Invoice>()
21            .Property(i => i.Total)
22            .HasPrecision(18, 2);
23
24        modelBuilder.Entity<Invoice>()
25            .Property(i => i.TaxRate)
26            .HasPrecision(5, 4);
27    }
28}

Total gets enough room for currency values, while TaxRate reserves more fractional detail. That split is common: monetary amounts usually need a moderate scale, but rates and ratios often need a much finer one.

If you are on a modern EF Core version, you can also use the Precision attribute for simple cases:

csharp
1using Microsoft.EntityFrameworkCore;
2
3public class Product
4{
5    public int Id { get; set; }
6
7    [Precision(18, 2)]
8    public decimal Price { get; set; }
9}

The Fluent API is still worth knowing because it keeps database concerns in one place and works well when different providers need different conventions.

Choosing the Right Values

There is no universal best precision and scale. The right choice depends on the largest legal value and the smallest meaningful fractional step.

A few practical examples:

  • unit price: often decimal(18, 2) or decimal(19, 4) depending on the domain
  • interest rate: often decimal(5, 4) or decimal(9, 6)
  • quantity with fractions: sometimes decimal(18, 3) or decimal(18, 6)

The key is to model the real business limit. If you know an amount should never exceed a certain size, encode that limit deliberately. If you need more fractional precision for calculations than for display, keep the stored scale higher and round only at the presentation layer.

After changing precision or scale, generate a migration and inspect the SQL. That step is important because provider defaults differ, and a migration can reveal whether EF will alter the column exactly the way you expect.

Common Pitfalls

  • Relying on provider defaults for decimal columns. Defaults are not always aligned with the business domain and can change between providers.
  • Using double or float for money. Binary floating-point types are fast, but they are the wrong tool for exact decimal amounts.
  • Picking a scale that is too small. The app may appear correct until a calculation starts rounding values in production.
  • Changing precision in the model without applying and reviewing the migration. The code and database can drift apart if the schema is never updated.
  • Reusing one decimal definition for every field. Price, exchange rate, and quantity often need different scales.

Summary

  • Precision is total digits, and scale is digits after the decimal point.
  • In EF Code First, configure decimal columns explicitly so the generated schema matches the domain.
  • The Fluent API with HasPrecision is a reliable way to define currency, rate, and measurement columns.
  • Use decimal types for exact numeric values such as money, not double or float.
  • Review migrations after precision changes so the database stays in sync with the model.

Course illustration
Course illustration

All Rights Reserved.