2015-05-29 2 views
11

Прежде всего, я хотел бы сказать, что я прочитал соответствующие должности (в частности, EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way? и Entity Framework 4.1 RC (Code First) - Entity not updating over association).Недвижимость не обновляется после SaveChanges (сначала база данных EF)

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

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

  • Person
  • Item с обнуляемого PersonId и Type

Элемент может иметь владельца, или нет. Следовательно, Person имеет свойство Items, которое является IEnumerable of 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 (Reload из контекста после 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() 
     } 
    } 
} 

Решение 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'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() 
     } 
    } 
} 
+0

Я не уверен, что изменение внешнего ключа на хозяйствующий субъект инициирует EF, чтобы поместить его в правильной коллекции. Что произойдет, если вы создадите новый контекст и перезагрузитесь из db? – user2697817

+0

Большое спасибо за ваш ответ. Да, если я перезагружу его из базы данных, он работает (он также работает после следующей Page_Load, кстати). Если я проверю 'Item currentItem = entities.Items.SingleOrDefault (i => i.PersonId == person.Id && i.TypeId == newItem.TypeId);' сразу после SaveChanges, 'currentItem' отлично справляется. Но мне действительно нужно, чтобы свойство было актуальным, чтобы обновить элементы управления страницей. В настоящее время мне нужно обновить страницу (перейдите по странице_Load и перезагрузите текущего пользователя), чтобы увидеть, что элемент изменился. –

+0

@jlvaquero: Конечно, я отредактировал свой первый пост с кодом UnitOfWork :) –

ответ

4

Как насчет пополняете свой контекст, чтобы убедиться, что у вас есть последние изменения базы данных после метода .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 и снова загрузите необходимые данные.

+0

Большое спасибо за ваш ответ! Однако я не уверен, что хорошо понял: я мог оставить метод 'Commit()' таким, какой он есть, и создать метод RefreshEntity(), который будет делать '_dbContext.Refresh (RefreshMode.StoreWins, entityPassed);'. Это правильно ? –

+0

Кажется, я неправильно прочитал ваш комментарий. Я приношу извинения. Да, метод метода RefreshEntity должен работать хорошо! –

+1

Большое спасибо, я попробую сразу. Однако мой '_dbcontext' не имеет метода' Refresh() ', а не' ObjectStateManager'. Я не знаю, может ли это быть важным, но тип MyEntities наследуется от 'DbContext' –

0

Использование Рассечение, например его работает отлично

общественного класса 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(); 
    } 

} 
Смежные вопросы