Свойство не обновляется после SaveChanges (сначала база данных EF)

Прежде всего, я хотел бы сказать, что я прочитал связанные сообщения (особенно EF 4.1 SaveChanges не обновляет навигационные или ссылочные свойства, Entity Framework Code First - Почему я не могу так изменить сложные свойства? и Entity Framework 4.1 RC (первый код) - сущность, не обновляющаяся по ассоциации).

Однако я не мог решить свою проблему. Я совершенно новичок в Entity Framework, поэтому, наверное, я неправильно понял ответы на эти сообщения. Во всяком случае, я был бы очень благодарен, если кто-то может помочь мне понять, потому что я совсем застрял.

У меня есть две таблицы:

  • Person
  • Item с нулевыми значениями PersonId и a Type

Элемент может иметь владельца или нет. Следовательно, Person имеет свойство Items, которое является IEnumerable из Item.

У человека может быть только один Item по типу. Если человек хочет изменить, он может заменить свой текущий предмет любым другим из того же типа в своих предметах:

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()
        }
    }
}

Вот структура репозиториев и метод AddOrUpdate:

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;
            }
        }
    }
}

Это работает очень хорошо, а старые и новые элементы PersonId свойства корректно обновляются в базе данных. Однако, если я проверю person.Items после SaveChanges(), старый элемент все же появляется вместо нового, и мне нужно, чтобы оно было правильным, чтобы обновить значения параметров страницы.

Хотя я читал сообщения с той же проблемой, я не мог ее решить... Я пробовал много вещей, особенно вызывал entities.Entry(person).Collection(p => p.Items).Load(), но получал исключение каждый раз, когда я пытался.

Если у кого-то есть идея, пожалуйста, не стесняйтесь, я могу добавить еще какой-то код, если это необходимо.

Спасибо большое!

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();
    }
}

Два решения, которые работали

Решение 1 (перезагрузка из контекста после 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 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()
        }
    }
}

Решение 2 (обновить базу данных И свойство)

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 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()
        }
    }
}

Ответ 1

Как обновить контекст, чтобы убедиться, что у вас есть последние изменения db после метода .SaveChanges(). Перейдите в сущность, чтобы обновить вызов Refresh в контексте:

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

Или оставьте метод Commit() как есть и используйте более динамичный подход примерно так:

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);

RefreshMode.StoreWins просто указывает, что база данных (хранилище) имеет приоритет и переопределяет изменения клиента (в памяти).

Если метод Refresh не работает, вы можете рассмотреть следующее:

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

Или, если все остальное не удается, сохраните его и Dispose вашего DbContext, как только вы закончите с каждой транзакцией (в этом случае после вызова SaveChanges()). Затем, если вам нужно использовать результаты после коммита, рассматривайте его как новую транзакцию и создайте новый DbContext и снова загрузите необходимые данные.

Ответ 2

Используйте Transection, например его рабочий тон

публичный класс UnitOfWork: IUnitOfWork

{

    public readonly DatabaseContext _context;
    private readonly IDbTransaction _transaction;
    private readonly ObjectContext _objectContext;

      public UnitOfWork(DatabaseContext context)
    {
        _context = context as DatabaseContext ?? new DatabaseContext ();
        this._objectContext = ((IObjectContextAdapter)this._context).ObjectContext;
        if (this._objectContext.Connection.State != ConnectionState.Open)
        {
            this._objectContext.Connection.Open();
            this._transaction = _objectContext.Connection.BeginTransaction();
        }
      }             

    public int Complete()
    {
        int result = 0;
        try
        {
            result = _context.SaveChanges();
            this._transaction.Commit();
        }
        catch (Exception ex)
        {
            Rollback();
        }
        return result;
    }
    private void Rollback()
    {
        this._transaction.Rollback();

        foreach (var entry in this._context.ChangeTracker.Entries())
        {
            switch (entry.State)
            {
                case System.Data.Entity.EntityState.Modified:
                    entry.State = System.Data.Entity.EntityState.Unchanged;
                    break;
                case System.Data.Entity.EntityState.Added:
                    entry.State = System.Data.Entity.EntityState.Detached;
                    break;
                case System.Data.Entity.EntityState.Deleted:
                    entry.State = System.Data.Entity.EntityState.Unchanged;
                    break;
            }
        }
    }
    public void Dispose()
    {
        if (this._objectContext.Connection.State == ConnectionState.Open)
        {
            this._objectContext.Connection.Close();
        }
        _context.Dispose();
    }

}