1

I have a MeterPeak entity and this entity has a MeterReading entity as a foreign key (included at bottom of question). The MeterReading entity has a composite Primary Key composed of MeterSiteId and DateTime.

So my understanding is that in my MeterPeak entity I must enter a MeterSiteId and a DateTime that matches an existing MeterReading entity to meet the foreign key constraint. I should not be allowed to link to a foreign key that doesn't exist.

However, in the Edit Action of the MeterPeakController I have the following code

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(MeterPeak meterpeak)
    {
        if (ModelState.IsValid)
        {
            unitOfWork.MeterPeakRepository.Update(meterpeak);
            unitOfWork.Save();
            return RedirectToAction("Index");
        }
        ViewBag.MeterSiteId = new SelectList(unitOfWork.MeterSiteRepository.Get(b => true), "Id", "Location", meterpeak.MeterSiteId);
        return View(meterpeak);
    }

When I enter a MeterSiteId and a DateTime which do not match an existing meter reading and try to save I would expect the ModelState.IsValid check to return false, but it doesn't. It only fails when it gets to the unitOfWork.save() line and it errors when saving changes to the dbcontext with this error

The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_dbo.MeterPeaks_dbo.MeterReadings_MeterSiteId_DateTime"

Why does the ModelState.IsValid not find this problem before getting to the dbcontext.save?

public class MeterPeak
{
    [Key]
    public int Id { get; set; }

    [ForeignKey("PeakReading"), Column(Order = 1)]
    public int MeterSiteId { get; set; }

    [ForeignKey("PeakReading"), Column(Order = 2)]
    public DateTime DateTime { get; set; }

    public int? Rating { get; set; }

    public String Note { get; set; }

    public virtual MeterReading PeakReading { get; set; }
}

public class MeterReading
{
    [Key, Column(Order = 1)]
    [Required(ErrorMessage = "Please Select a Meter Site")]
    public int MeterSiteId { get; set; }

    [Key, Column(Order = 2)]
    [Required]
    public DateTime DateTime { get; set; }

    //This is not the Primary key but I need one unique value to assist in getting records, especially from Javascript
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid SingleKey { get; set; }

    public virtual MeterSite MeterSite { get; set; }

    [Required]
    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double RawLevel { get; set; }

    [Required]
    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double RawVelocity { get; set; }

    [Required]
    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double RawFlow { get; set; }

    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double ValidLevel { get; set; }

    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double ValidVelocity { get; set; }

    [DisplayFormat(DataFormatString = "{0:#,##0.00000#}", ApplyFormatInEditMode = true)]
    public Double ValidFlow { get; set; }

    [Range(-1, 3, ErrorMessage = "Rating must be one of the following -1,0,1,2,3")]
    public int Rating { get; set; }

    public bool? Valid { get; set; }

    public virtual ICollection<Note> Notes { get; set; }

}
Mort
  • 837
  • 1
  • 14
  • 20

1 Answers1

6

Why does the ModelState.IsValid not find this problem before getting to the dbcontext.save?

The ModelState is an ASP.NET MVC concept and has no knowledge of your database. The ModelState property represents the state of the information passed to the action method in the HTTP post. It is working correctly.

You haven't shown your form, but seeing as your Edit action method accepts a MeterPeak I am assuming the following values are being sent in your HTTP post: Id, MeterSiteId, DateTime, Rating, Note and PeakReading (you should be able to verify this by using the debug tools in your browser).

ASP.NET MVC retrieves these values and uses the model binder to construct a MeterPeak object using the values you have submitted.

The object is then validated, using the values you have supplied, against any validation attributes that are present on your object or by calling the Validate method if your object implements the IValidatableObject interface.

Note your MeterPeak object does not decorate any properties with validation attributes or implement the IValidatableObject interface so there is nothing to validate against which explains why the ModelState.IsValid property is true. (The attributes you have applied are used by Entity Framework to understand how to map your class to a database table).

To summarise:

  1. You make an HTTP post
  2. MVC retrieves the posted values and uses the model binder to construct a MeterPeak object.
  3. The object is validated using the rules above and passed to your action method.

no database calls have been made yet so there is no way for MVC to know that the foreign key constraint has been violated at this point.

On a side note I would advise against using your entities as the 'model' in your ASP.NET MVC application controllers. What you should really have is a view specific model a (viewModel) that is then transformed to your entity in your controller (either manually or using an framework such as AutoMapper). This might sound like a lot of work but pays off in the end. Some of the benefits are provided in my answer to the question how to avoid needing a viewModel for every model. Further discussion is available in the question Real example of TryUpdateModel, ASP .NET MVC 3.

Community
  • 1
  • 1
Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
  • Thanks for this :-) Very comprehensive answer – Mort May 10 '13 at 03:24
  • Question - The way I am reading this, it seems that the `ModelState.IsValid` has a check to determine if the model is an `IValidatableObject`, and if so will call the `Validate` method in addition to other decorators. Is that an accurate statement, or do I misunderstand? – Andrew Gray Oct 25 '13 at 20:26
  • The validation system looks for data annotations and the `IValidatableObject` interface and is performed as part of the model binding process. Any violations of the constraints are added to the `ModelState`. `ModelState.IsValid` simply returns true if there are no errors in the errors dictionary, otherwise false. – Benjamin Gale Oct 25 '13 at 20:50