16

I have a view model that encapsulates only some of the database model properties. These properties contained by the view model are the only properties I want to update. I want the other properties to preserve their value.

During my research I found this answer which appears to be perfect for my needs however, despite my best efforts, I cannot get the code to work as expected.

Here is an isolated example of what I came up with:

static void Main() {
    // Person with ID 1 already exists in database.

    // 1. Update the Age and Name.
    Person person = new Person();
    person.Id = 1;
    person.Age = 18;
    person.Name = "Alex";

    // 2. Do not update the NI. I want to preserve that value.
    // person.NINumber = "123456";

    Update(person);
}

static void Update(Person updatedPerson) {
    var context = new PersonContext();

    context.Persons.Attach(updatedPerson);
    var entry = context.Entry(updatedPerson);

    entry.Property(e => e.Name).IsModified = true;
    entry.Property(e => e.Age).IsModified = true;

    // Boom! Throws a validation exception saying that the 
    // NI field is required.
    context.SaveChanges();
}

public class PersonContext : DbContext {
    public DbSet<Person> Persons { get; set; }
}

public class Person {
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required] 
    public int Age { get; set; } // this is contrived so, yeah.
    [Required]
    public string NINumber { get; set; }
}

What am I doing wrong?

Community
  • 1
  • 1
Caster Troy
  • 2,796
  • 2
  • 25
  • 45
  • 1
    Why are you attaching the person? Generally when I work with the entity framework I just retrieve the record, modify its properties and execute SaveChanges(); Something like: Person person = context.People.First(); person.Name = "John"; context.SaveChanges(); –  May 09 '14 at 14:14
  • Because that would require two queries on the database + in reality, I am using a generic repository. – Caster Troy May 09 '14 at 14:16
  • 1
    That's not really a generic repository, but anyways, +1 for @Areks said. If you're worried about 2 queries then you have other problems – mituw16 May 09 '14 at 14:17
  • 1
    @mituw16 None the less, I would like to know why this code doesn't work. Thanks for your comments. – Caster Troy May 09 '14 at 14:19
  • try entry.State = EntityState.Modified; – Radu Pascal May 09 '14 at 14:21
  • @RaduPascal Setting the `State` property to `EntityState.Modified` does not help. Thanks anyway. – Caster Troy May 09 '14 at 14:24
  • I'm not really sure why you're even manually setting the individual properties. Once you attach the object, you shouldn't have to to anything other than do a `context.Entry(updatedPerson).State = EntityDtate.Modified;` but that is assuming `updatedPerson` is an EF object, and not just a normal class object. – mituw16 May 09 '14 at 14:26
  • @mituw16 Because such an approach would cause EF to *attempt* to update the `NINumber` property (in this case it would become `null` and cause a validation error). This is what I am trying to avoid. – Caster Troy May 09 '14 at 14:30
  • found a similar question, with a proposed solution: http://stackoverflow.com/questions/12871892/entity-framework-validation-with-partial-updates – Radu Pascal May 09 '14 at 14:31
  • @CasterTroy Right, which is why you need the first query to grab a valid entity object of person, then you update only the properties you need to change. – mituw16 May 09 '14 at 14:31
  • @RaduPascal That did the trick for me Radu. Thanks man. I am still not sure as to why I needed to do that when [Ladu *seemingly* didn't](http://stackoverflow.com/a/15339512/1500199) and am still interested to know but at least for now I can continue with my project. Thanks again. – Caster Troy May 09 '14 at 14:46
  • @CasterTroy to my best understanding of the problem from the other thread, is that fields that weren't changed (and as such weren't in the attached model) weren't mandatory, and that's why it worked. Since your fields are required, you'll get this validation error. – Radu Pascal May 09 '14 at 14:55
  • @RaduPascal Damn Radu. You're killing it dude. Thanks so much. If you post an answer I will gladly accept. – Caster Troy May 13 '14 at 20:05
  • @CasterTroy my answer on May 9 said as much; have you read it? http://stackoverflow.com/a/23567961/571637 – jltrem May 14 '14 at 19:17
  • glad it helps! I've added an answer, hope it encapsulates all the important bits. – Radu Pascal May 14 '14 at 19:54

3 Answers3

4

You based your work on the post https://stackoverflow.com/a/15339512/2015959, but in the other thread the fields that weren't changed (and as such weren't in the attached model) weren't mandatory, and that's why it worked. Since your fields are required, you'll get this validation error.

Your problem can be solved by the solution provided in question Entity Framework validation with partial updates

Community
  • 1
  • 1
Radu Pascal
  • 1,275
  • 14
  • 18
  • Hey Radu, thanks for being so specific. You're second link is exactly the issue I was having. – Jono Mar 30 '16 at 11:58
3

It is the validation that is causing it not to be saved. You can disable validation with context.Configuration.ValidateOnSaveEnabled = false; and it will work. To validate specific fields you can call var error = entry.Property(e => e.Name).GetValidationErrors();. So you certainly can make an 'UpdateNameAndAge' method that only only enforces business rules and flags those properties as modified. No double query required.

  private static bool UpdateNameAndAge(int id, string name, int age)
  {
     bool success = false;

     var context = new PersonContext();
     context.Configuration.ValidateOnSaveEnabled = false;

     var person = new Person() {Id = id, Name = name, Age = age};
     context.Persons.Attach(person);
     var entry = context.Entry(person);

     // validate the two fields
     var errorsName = entry.Property(e => e.Name).GetValidationErrors();
     var errorsAge = entry.Property(e => e.Age).GetValidationErrors();

     // save if validation was good
     if (!errorsName.Any() && !errorsAge.Any())
     {
        entry.Property(e => e.Name).IsModified = true;
        entry.Property(e => e.Age).IsModified = true;

        if (context.SaveChanges() > 0)
        {
           success = true;
        }
     }

     return success;
  }
jltrem
  • 12,124
  • 4
  • 40
  • 50
0

(Edited for clarity)

The context must have a complete copy of the object to enforce business rules. This can only happen if the attached object has all the necessary properties populated or the partial view is merged with a complete copy before updating.

I believe that what you want to do is conceptually impossible: doing updates like this will require either a preserved pre-change copy, or two queries to the database because the business layer needs a full copy of the object for validation.

esmoore68
  • 1,276
  • 1
  • 8
  • 16
  • Could you please cite a source? I *think* that [this answer](http://stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record/15339512#15339512) suggests otherwise. – Caster Troy May 09 '14 at 14:31
  • 1
    not so. you can enforce business rules for only the modified fields. see my answer. – jltrem May 09 '14 at 15:07
  • @jltrem- yes, it is possible to disable record validation, and you can check field level validation (required, range, etc) property by property. But this method won't work for an object with record level rules (IValidatableObject, or ValidateEntity), both of which may require all the values of the entity to pass. Either way, bypassing business layer validation requires falling back on the database to make sure bad data isn't inserted. I suppose my original post should have said "conceptually impossible in a responsible design"? – esmoore68 May 09 '14 at 15:22
  • @esmoore68 there are plenty of cases where a simple update to a field is needed. If your db is getting heavy traffic, reducing the number of queries is essential. EF makes it easy to do things in a safe, cautious manner ... which is great. But to scale a system you have to work around the easy/safe/convenient way that is provided, use your knowledge about your data and the database, and implement an efficient update. – jltrem May 09 '14 at 15:31