56

I have a column inside my sql server 2008 wih type of Decimal(18,2). But on entity framework what is the best data annotation validation I can apply to this property, inside my asp.net MVC web application ?

ediblecode
  • 11,701
  • 19
  • 68
  • 116

11 Answers11

75

There is no explicit data annotation for a decimal so you need to use two separate ones to add constraints.

Two Decimal Points

[RegularExpression(@"^\d+(\.\d{1,2})?$")]

This regular expression will make sure that the property has at most two decimal places.

Max 18 digits

[Range(0, 9999999999999999.99)]

Assuming you aren't accepting any negative numbers. Otherwise, replace 0 with -9999999999999999.99.

Result

[RegularExpression(@"^\d+(\.\d{1,2})?$")]
[Range(0, 9999999999999999.99)]
public decimal Property { get; set; }
Ian Robinson
  • 16,892
  • 8
  • 47
  • 61
ediblecode
  • 11,701
  • 19
  • 68
  • 116
  • 4
    this regular expression is invalid Try input 1234m12 and it will pass the expression. Dot needs to be escaped as it's treated as any character. [RegularExpression(@"^\d+\.\d{0,2}$")] – 100r Oct 29 '15 at 15:46
  • I believe the regular expression in your example should be "^\d*.\d{0,2}$". Otherwise a single digit value would be unacceptable, yet a single digit value should be acceptable to OP. – Rob S. Aug 26 '16 at 16:46
  • @Jay How to have decimal like (0.1234) or (456.0009) ? – SAR Dec 12 '16 at 09:49
  • 5
    Great answer, however, I found the regex forced you to have to have decimal places which for my use case wasn't what I needed, so a regex to make the decimal places optional is: "^\d+(\.\d{1,2})?$" This works great for entry of currency, etc. – jjr2000 Oct 16 '17 at 14:14
  • Important to note that `RegularExpressionAttribute` takes current culture into account when converting the value being tested into a string (in order to test it against the supplied regular expression) so if the current culture's decimal point is a comma (which it may be) then you'll need to account for that in your regular expression. – Jimbo Jul 17 '20 at 09:32
26

I think @jumpingcode's answer can be combined into one RegularExpressionAttribute.

[RegularExpression(@"^(0|-?\d{0,16}(\.\d{0,2})?)$")]
public decimal Property
{
    get;
    set;
}

This can be used for any precision and scale. The 16 is replaced by precision - scale and the 2 is replaced by the scale. The regular expression should match numbers entered like ###, 0.##, .##, 0, and ###.## as well as negative values.

Ben Smith
  • 19,589
  • 6
  • 65
  • 93
Schmalls
  • 1,434
  • 1
  • 19
  • 19
  • 6
    If this was going to be used regularly, extending the RegularExpression attribute would probably be the best option. Then you could just have an attribute where you provide the precision and scale. – Schmalls Feb 28 '17 at 22:07
20

In EF Core 6

You can simply use:

[Precision(18,2)]
public decimal Property{ get; set; }
Ahmad Kelany
  • 376
  • 3
  • 8
14

If you write the 'column' annotation, will work fine

    [Required]
    [Column(TypeName = "decimal(18, 6)")]
    public decimal Foo { get; set; }
Daniel Jacobson
  • 161
  • 1
  • 3
10

For a different approach which some may consider more readable, you can override the OnModelCreating method of your DbContext to set precision, like so:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

           modelBuilder.Entity<YourEntity>()
                    .Property(x => x.TheProprty)
                    .HasPrecision(18, 2);
    }

Advantage: strongly typed vs custom regular expression

Disadvantage: can't see it on the class with just a scan

Adam Diament
  • 4,290
  • 3
  • 34
  • 55
6

This seems to be the correct answer ( the above answers either restrict valid numbers that can be inserted into a data type of Decimal(18,2) or cause compile errors if you apply them to your code -- please confirm for yourself):

Use the following two constraints together:

Two Decimal Points

[RegularExpression(@"^\d+.?\d{0,2}$", ErrorMessage = "Invalid Target Price; Maximum Two Decimal Points.")]

Max 18 digits

  [Range(0, 9999999999999999.99, ErrorMessage = "Invalid Target Price; Max 18 digits")]
mgalpy
  • 369
  • 4
  • 13
6

Following on from @Schmalls example (and comment re building it into an attribute) I've created a working example (uses C# 6 string interpolation):

public class PrecisionAndScaleAttribute : RegularExpressionAttribute
{
    public PrecisionAndScaleAttribute(int precision, int scale) : base($@"^(0|-?\d{{0,{precision - scale}}}(\.\d{{0,{scale}}})?)$")
    {

    }
}

Usage:

[PrecisionAndScale(6, 2, ErrorMessage = "Total Cost must not exceed $9999.99")]
public decimal TotalCost { get; set; }
Breeno
  • 3,007
  • 2
  • 31
  • 30
  • 4
    **Great idea for reusability**! *Important Note*: that [`RegularExpressionAttribute` subclasses will not automatically emit client side validation attributes](https://stackoverflow.com/a/3634265/1366033). To do so, you need to call `DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(PrecisionAndScaleAttribute),typeof(RegularExpressionAttributeAdapter));` – KyleMit Apr 11 '19 at 18:57
2

.net core/5/6 solution that works 2021

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

[AttributeUsage(AttributeTargets.Property)]
public class ScalePrecisionValidationAttribute : ValidationAttribute
{
    private int _scale;
    private int _precision;

    public ScalePrecisionValidationAttribute(int scale, int precision)
    {
        _scale = scale;
        _precision = precision;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            if (!Regex.IsMatch(value.ToString(), $@"^(0|-?\d{{0,{_scale-_precision}}}(\.\d{{0,{_precision}}})?)$"))
            {
                return new ValidationResult($"Allowed scale: {_scale}, precision: {_precision}");
            }
        }

        return ValidationResult.Success;
    }
}

use as

[ScalePrecisionValidationAttribute(8, 3)]
public decimal Weight { get; set; }

you might want to add/modify additional guards depending on the use-case. p.s. I have used the Regex pattern from one of the other answers

Tim Rutter
  • 4,549
  • 3
  • 23
  • 47
Alex
  • 4,607
  • 9
  • 61
  • 99
0
 [Range(1,(double) decimal.MaxValue, ErrorMessage="value should be between{1} and {2}."]
mjyazdani
  • 2,110
  • 6
  • 33
  • 64
0

Im using almost excplusively (b/c it's simple and works)

[Range(typeof(decimal), "0", "1")]
public decimal Split { get; set; }

Then if I need to convert back to double I add a conversion

(double)model.Split
ransems
  • 641
  • 7
  • 19
0

Adding to Ahmad Kelany's answer.

From the Microsoft documentation Entity Properties

Entity Framework does not do any validation of precision or scale before passing data to the provider. It is up to the provider or data store to validate as appropriate.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Chris Catignani
  • 5,040
  • 16
  • 42
  • 49