Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework 5 - Immediately refresh DbContext after saving changes

I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.

Here's the example (simplified code that I have)

Models

class User : Model
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

class Car : Model
{
    public Guid Id { get; set; }
    public Guid DriverId { get; set; }
    public virtual User Driver { get; set; }

    [NotMapped]
    public string DriverName
    {
        get { return this.Driver.Name; }
    }
}

Controller

public CarController
{
    public Create()
    {
       return this.View();
    }

    [HttpPost]
    public Create(Car car)
    {
        if (this.ModelState.IsValid)
        {
            this.Context.Cars.Create(booking);
            this.Context.SaveChanges();

            // here I need to access some of the resolved nav properties
            var test = booking.DriverName;
        }

        // error handling (I'm removing it in the example as it's not important)
    }
}

The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.

public virtual void Create(TObject obj)
{
    return this.DbSet.Add(obj);
}

public virtual void Update(TObject obj)
{
    var currentEntry = this.DbSet.Find(obj.Id);
    this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
    currentEntry.LastModifiedDate = DateTime.Now;
}

Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.

In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.

I've ovewrite the SaveChanges method to refresh object context immediately after save

public int SaveChanges()
{
    var rowsNumber = this.Context.SaveChanges();
    var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
    objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);

    return rowsNumber;
}

I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:

car = this.Context.Cars.Find(car.Id);

Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?

EDIT

I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.

class Car : Model
{    
    [NotMapped]
    public string DriverName
    {
        get
        {
            if (this.Driver == null)
            {
                using (var context = new DbContext())
                {
                    this.Driver = this.context.Users.Find(this.DriverId);
                }
            }

            return this.Driver.Name;
         }
    }
}
like image 287
RaYell Avatar asked Jul 05 '13 10:07

RaYell


1 Answers

The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.

I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:

public virtual TObject Create(TObject obj)
{
    var newEntry = this.DbSet.Create();
    this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
    return newEntry;
}

UPDATE

If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:

private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
    var newEntry = this.DbSet.Create();

    if (!mapCreated)
    {
        Mapper.CreateMap(obj.GetType(), newEntry.GetType());
        mapCreated = true;
    }
    newEntry = Mapper.Map(obj, newEntry);

    this.DbSet.Add(newEntry;
    return newEntry;
}
like image 172
qujck Avatar answered Oct 24 '22 11:10

qujck