1

I have an ASP.NET MVC view for editing a model object. The edit page includes most of the properties of my object but not all of them -- specifically it does not include CreatedOn and CreatedBy fields since those are set upon creation (in my service layer) and shouldn't change in the future.

Unless I include these properties as hidden fields they will not be picked up during Binding and are unavailable when I save the modified object in my EF 4 DB Context. In actuality, upon save the original values would be overwritten by nulls (or some type-specific default).

I don't want to drop these in as hidden fields because it is a waste of bytes and I don't want those values exposed to potential manipulation.

Is there a "first class" way to handle this situation? Is it possible to specify a EF Model property is to be ignored unless explicitly set?

kingdango
  • 3,799
  • 2
  • 21
  • 43

5 Answers5

3

Use either:

public bool SaveRecording(Recording recording)
{
    // Load only the DateTime property, not the full entity
    DateTime oldCreatedOn = db.Recordings
       .Where(r => r.Id == recording.Id)
       .Select(r => r.CreatedOn)
       .SingleOrDefault();

    recording.CreatedOn = oldCreatedOn;

    db.Entry(recording).State = EntityState.Modified;
    db.SaveChanges();

    return true;
}

(Edit: The query only loads the CreatedOn column from the database and is therefore cheaper and faster than loading the full entity. Because you only need the CreatedOn property using Find would be unnecessary overhead: You load all properties but need only one of them. In addition loading the full entity with Find and then detach it afterwards could be shortcut by using AsNoTracking: db.Recordings.AsNoTracking().SingleOrDefault(r => r.Id == recording.Id); This loads the entity without attaching it, so you don't need to detach the entity. Using AsNoTracking makes loading the entity faster as well.)

Edit 2

If you want to load more than one property from the database you can project into an anonymous type:

public bool SaveRecording(Recording recording)
{
    // Load only the needed properties, not the full entity
    var originalData = db.Recordings
       .Where(r => r.Id == recording.Id)
       .Select(r => new
       {
           CreatedOn = r.CreatedOn,
           CreatedBy = r.CreatedBy
           // perhaps more fields...
       })
       .SingleOrDefault();

    recording.CreatedOn = originalData.CreatedOn;
    recording.CreatedBy = originalData.CreatedBy;
    // perhaps more...

    db.Entry(recording).State = EntityState.Modified;
    db.SaveChanges();

    return true;
}

(End of Edit 2)

Or:

public bool SaveRecording(Recording recording)
{
    Recording oldVersion = db.Recordings.Find(recording.Id);

    recording.CreatedOn = oldVersion.CreatedOn;

    // flag only properties as modified which did really change
    db.Entry(oldVersion).CurrentValues.SetValues(recording);

    db.SaveChanges();

    return true;
}

(Edit: Using CurrentValues.SetValues flags only properties as Modified which indeed have been changed compared to the original state in the database. When you call SaveChanges EF will sent only the properties marked as modified in an UPDATE statement to the database. Whereas setting the state in Modified flags all properties as modified, no matter if they really changed or not. The UPDATE statement will be more expensive because it contains an update for all columns.)

Slauma
  • 167,754
  • 56
  • 385
  • 407
  • Thank you for the thoughtful reply! Why is this method better than using the EntityState.Detach approach I provided below? BTW - that's not a defensive question, I'm truly curious to learn your opinion and if it makes sense I'll mark your answer instead of mine. – kingdango Nov 16 '11 at 21:02
  • 1
    @kingdango: Sorry, I had actually started to write only a comment to your answer but it was getting too long quickly, so I moved it to an answer. I was a bit in a rush and didn't explain well why I would prefer the solutions above. It's now in the two "Edit" brackets in my answer, making it hopefully clearer now. – Slauma Nov 16 '11 at 23:54
  • I went with the second option you provided. I actually need to pull both the CreatedOn and CreatedBy fields. I could probably get more efficient by pulling just the two columns but how would I store them -- as an array? Thanks again for this very good and customized answer! – kingdango Nov 17 '11 at 01:33
  • 1
    @kingdango: You can use a projection into an anonymous type to retrieve more than one field, see my Edit 2. – Slauma Nov 17 '11 at 11:46
0

If you don't want to send that data down to the client, I don't see any other option but to load up the original from the db in your service layer when you save and merge those original property values back in to the updated object. There's no way for EF to know that you didn't set those values to null on purpose and don't actually want to save them that way.

Todd Bellamy
  • 152
  • 3
  • Yes, this is the path I took and I leveraged the answer provided in this link http://stackoverflow.com/questions/6022346/asp-net-mvc3-code-first-error-attempting-to-update-entity/6022550#6022550 – kingdango Nov 16 '11 at 15:08
0

You could implement your own model binder that ignores the properties you don't want to pass around. Start here - http://lostechies.com/jimmybogard/2009/03/18/a-better-model-binder/

Jason
  • 15,640
  • 3
  • 46
  • 70
0

I think when you going to update use getById to get all the entity and then set your relevant properties and then you can update. It will be easy if you are using some kind of mapper (Automapper) to map your properties from view model to loaded entity from DB.

Jayantha Lal Sirisena
  • 20,611
  • 10
  • 65
  • 90
0

If you want to avoid making an additional (unnecessary) call to your database before every update, you can either use self-tracking entities or set StoreGeneratedPattern="Identity" for those fields in your entity model. And yes, Identity is misleading, but that sounds like the setting you'd want:

Identity A value is generated on insert and remains unchanged on update.

http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.storegeneratedpattern.aspx

Joel C
  • 5,427
  • 1
  • 18
  • 30
  • This would be good except I can't implement a DateTime as an identity. I can see this being a good fit for certain data types and usages but not for audit tracking (CreatedOn, CreatedBy). Thanks for the response though! – kingdango Nov 16 '11 at 15:06
  • 1
    You can definitely create a `DateTime` as an identity, in the database that would mean setting a default value of `GETDATE()`. `StoreGeneratedPattern="Identity"` in Entity Framework does not mean the same thing as what "Identity" means in database terminology; it was a bad/misleading choice of name by the EF team. Unless you're using pass-through authentication to your database, though, you're right, that won't work for your `CreatedBy` field; I just saw the DateTime field when I read your question. Sounds like that leaves self-tracking entities then. – Joel C Nov 16 '11 at 15:13
  • I believe self-tracking entities are useless in web applications. They are designed for service scenarios and limited to the situation that both client and server are .NET applications. The STE's can be moved through a WCF channel then for example. But a web browser is not a .NET client. – Slauma Nov 17 '11 at 00:04