unlimit
unlimit

Reputation: 3752

Not able to retrieve OriginalValues in Entity Framework 5

I am writing a asp.net mvc4 app and I am using entity framework 5. Each of my entities have fields like EnteredBy, EnteredOn, LastModifiedBy and LastModifiedOn.

I am trying to auto-save them by using the SavingChanges event. The code below has been put together from numerous blogs, SO answeres etc.

public partial class myEntities : DbContext
{
    public myEntities()
    {            
        var ctx = ((IObjectContextAdapter)this).ObjectContext;
        ctx.SavingChanges += new EventHandler(context_SavingChanges);
    }

    private  void context_SavingChanges(object sender, EventArgs e)
    {

        ChangeTracker.DetectChanges();

        foreach (ObjectStateEntry entry in
                 ((ObjectContext)sender).ObjectStateManager
                   .GetObjectStateEntries
                    (EntityState.Added | EntityState.Modified))
        {         
            if (!entry.IsRelationship)
            {
                CurrentValueRecord entryValues = entry.CurrentValues;               

                if (entryValues.GetOrdinal("LastModifiedBy") > 0)
                {
                    HttpContext currContext = HttpContext.Current;
                    string userName = "";
                    DateTime now = DateTime.Now;

                    if (currContext.User.Identity.IsAuthenticated)
                    {
                        if (currContext.Session["userId"] != null)
                        {
                            userName = (string)currContext.Session["userName"];
                        }
                        else
                        {
                            userName = currContext.User.Identity.Name;
                        }
                    }

                    entryValues.SetString(
                      entryValues.GetOrdinal("LastModifiedBy"), userName);
                    entryValues.SetDateTime(
                      entryValues.GetOrdinal("LastModifiedOn"), now);

                    if (entry.State == EntityState.Added)
                    {
                        entryValues.SetString(
                          entryValues.GetOrdinal("EnteredBy"), userName);
                        entryValues.SetDateTime(
                          entryValues.GetOrdinal("EnteredOn"), now);
                    }
                    else
                    {                
                     string enteredBy = 
                  entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy"));
                     DateTime enteredOn =
entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn"));

                        entryValues.SetString(
                       entryValues.GetOrdinal("EnteredBy"),enteredBy);
                        entryValues.SetDateTime(
                        entryValues.GetOrdinal("EnteredOn"), enteredOn);

                    }
                }
            }
        }
    }
}

My problem is that entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy")) and entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn")) are not returning the original values but rather the current values which is null. I tested with other fields in the entity and they are returning the current value which were entered in the html form.

How do I get the original value here?

Upvotes: 1

Views: 2813

Answers (2)

johnny 5
johnny 5

Reputation: 20997

I was running into a similar problem when trying to audit log the Modified values of an Entity.

It turns out during the post back the ModelBinder doesn't have access to the original values so the Model received is lacking the correct information. I fixed my problem by using this function which clones the current values, relods the object, and then reset the current values.

    void SetCorrectOriginalValues(DbEntityEntry Modified)
    {
        var values = Modified.CurrentValues.Clone();
        Modified.Reload();
        Modified.CurrentValues.SetValues(values);
        Modified.State = EntityState.Modified;
    }

You can gain access to the DbEntityEntry though the change tracker, or the entry function from your context.

Upvotes: 3

Daniel J.G.
Daniel J.G.

Reputation: 34992

I think the problem may be that you are using the instance provided by the model binder as the input to your controller method, so EF does not know anything about that entity and its original state. Your code may look like this:

public Review Update(Review review)
{
    _db.Entry(review).State = EntityState.Modified;
    _db.SaveChanges();
    return review;
}

In that case, EF knows nothing about the Review instance that is being saved. It is trusting you and setting it as modified, so it will save all of its properties to the database, but it does not know the original state\values of that entity.

Check the section named Entity States and the Attach and SaveChanges Methods of this tutorial. You can also check the first part of this article, that shows how EF does not know about the original values and will update all properties in the database.

As EF will need to know about the original properties, you may first load your entity from the database and then update its properties with the values received in the controller. Something like this:

public Review Update(Review review)
{
    var reviewToSave = _db.Reviews.SingleOrDefault(r => r.Id == review.Id);
    //Copy properties from entity received in controller to entity retrieved from the database
    reviewToSave.Property1 = review.Property1;
    reviewToSave.Property2 = review.Property2;
    ...

    _db.SaveChanges();
    return review;
}

This has the advantage that only modified properties will be send and updated in the database and that your views and view models don't need to expose every field in your business objects, only those that can be updated by the users. (Opening the door for having different classes for viewModels and models\business objects). The obvious disadvantage is that you will incur an additional hit to the database.

Another option mentioned in the tutorial I referenced above is for you to save the original values somehow (hidden fields, session, etc) and on save use the original values to attach the entity to the database context as unmodified. Then update that entity with the edited fields. However I would not recommend this approach unless you really need to avoid that additional database hit.

Hope that helps!

Upvotes: 5

Related Questions