Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property not updated after SaveChanges (EF database first)

First of all, I would like to say that I read the related posts (notably EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way?, and Entity Framework 4.1 RC (Code First) - Entity not updating over association).

However, I could not solve my problem. I am quite new to Entity Framework so I guess I must have misunderstood those posts answers. Anyway I would be really grateful is someone could help me understand because I am quite stuck.

I have two tables :

  • Person
  • Item with a nullable PersonId and a Type

An item can have an owner, or not. Consequently, Person has an Items property which is an IEnumerable of Item.

A person can have one only Item by type. If the person wants to change, he can replace his current item by any other of the same type in his items :

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}

Here is the repositories structure and the AddOrUpdate method :

public class PersonRepo : RepositoryBase<Person>
{
    ...
}

public class RepositoryBase<TObject> where TObject : class, IEntity
{
    protected MyEntities entities
    {
        get { return UnitOfWork.Current.Context; }
    }

    public virtual void AddOrUpdate(TObject entity)
    {
        if (entity != null)
        {
            var entry = entities.Entry<IEntity>(entity);

            if (Exists(entity.Id))
            {
                if (entry.State == EntityState.Detached)
                {
                    var set = entities.Set<TObject>();
                    var currentEntry = set.Find(entity.Id);
                    if (currentEntry != null)
                    {
                        var attachedEntry = entities.Entry(currentEntry);
                        attachedEntry.CurrentValues.SetValues(entity);
                    }
                    else
                    {
                        set.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
                else
                    entry.State = EntityState.Modified;
            }
            else
            {
                entry.State = EntityState.Added;
            }
        }
    }
}

This works pretty well and the old and the new items' PersonId properties are correctly updated in database. However, if I check person.Items after the SaveChanges(), the old item still appears instead of the new one and I need it to be correct in order to update the page's controls values.

Although I read the posts with the same issue I could not resolve it... I tried lots of things, notably calling entities.Entry(person).Collection(p => p.Items).Load() but got an exception each time I tried.

If somebody has any idea please feel free, I can add some more code if needed.

Thanks a lot !

EDIT : UnitOfWork

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;

public class UnitOfWork : IDisposable
{
    private const string _httpContextKey = "_unitOfWork";
    private MyEntities _dbContext;

    public static UnitOfWork Current
    {
        get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
    }

    public UnitOfWork()
    {
        HttpContext.Current.Items[_httpContextKey] = this;
    }

    public MyEntities Context
    {
        get
        {
            if (_dbContext == null)
                _dbContext = new MyEntities();

            return _dbContext;
        }
    }

    public void Commit()
    {
        _dbContext.SaveChanges();
    }

    public void Dispose()
    {
        if (_dbContext != null)
            _dbContext.Dispose();
    }
}

Two solutions that worked

Solution 1 (reload from context after SaveChanges)

public partial class MyPage
{
    private MyService service;
    private Person person;

    protected void Page_Load(object sender, EventArgs e)
    {
        service = new MyService();
        person = service.GetCurrentPerson(Request.QueryString["id"]);
        ...
    }

    protected void SelectNewItem(object sender, EventArgs e)
    {
        Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);

        service.SelectNewItem(person, itemId);

        UpdatePage();
    }

    private void UpdatePage()
    {
        if (person != null)
            person = service.GetCurrentPerson(Request.QueryString["id"]);

        // Update controls values using person's properties here
    }
}

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}

Solution 2 (update database AND property)

public partial class MyPage
{
    private MyService service;
    private Person person;

    protected void Page_Load(object sender, EventArgs e)
    {
        service = new MyService();
        person = service.GetCurrentPerson(Request.QueryString["id"]);
        ...
    }

    protected void SelectNewItem(object sender, EventArgs e)
    {
        Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);

        service.SelectNewItem(person, itemId);

        UpdatePage();
    }

    private void UpdatePage()
    {
        // Update controls values using person's properties here
    }
}

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;
            person.Items.Remove(oldItem);
            person.Items.Add(newItem);

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}
like image 904
Flash_Back Avatar asked May 29 '15 08:05

Flash_Back


People also ask

What does the DbContext SaveChanges () method return?

Returns. The number of state entries written to the underlying database. This can include state entries for entities and/or relationships.

Does SaveChanges commit?

In Entity Framework, the SaveChanges() method internally creates a transaction and wraps all INSERT, UPDATE and DELETE operations under it. Multiple SaveChanges() calls, create separate transactions, perform CRUD operations and then commit each transaction.

How does DbContext change state of entity?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.

What is SaveChanges?

SaveChanges()Saves all changes made in this context to the database. This method will automatically call DetectChanges() to discover any changes to entity instances before saving to the underlying database.


1 Answers

How about refreshing your context to make sure you have the latest db changes after the .SaveChanges() method. Pass in the entity to be refreshed an call Refresh on the context:

((IObjectContextAdapter)_dbContext).ObjectContext.Refresh(RefreshMode.StoreWins, entityPassed);

Or leave the Commit() method as is and use a more dynamic approach something like:

var changedEntities = (from item in context.ObjectStateManager.GetObjectStateEntries(
                                        EntityState.Added
                                       | EntityState.Deleted
                                       | EntityState.Modified
                                       | EntityState.Unchanged)
                              where item.EntityKey != null
                              select item.Entity);

    context.Refresh(RefreshMode.StoreWins, changedEntities);

The RefreshMode.StoreWins simply indicates that the database (store) takes priority and will override client (in-memory) changes.

If the Refresh method does not work, you can consider the following:

public void RefreshEntity(T entity)
{
  _dbContext.Entry<T>(entity).Reload();
}

Or if all else fails, keep it simple and Dispose of your DbContext once you're done with each transaction (In this case after SaveChanges() has been called). Then if you need to use results after a commit, treat it as a new transaction and, instantiating a fresh DbContext and load your necessary data again.

like image 114
Niels Filter Avatar answered Sep 22 '22 17:09

Niels Filter