2015-05-29 7 views
11

Zunächst möchte ich sagen, dass ich die zugehörigen Beiträge (insbesondere EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way? und Entity Framework 4.1 RC (Code First) - Entity not updating over association) gelesen habe.Eigenschaft nicht nach SaveChanges (EF-Datenbank zuerst) aktualisiert

Allerdings konnte ich mein Problem nicht lösen. Ich bin ziemlich neu im Entity Framework, also denke ich, dass ich diese Post-Antworten falsch verstanden habe. Jedenfalls wäre ich wirklich dankbar, dass mir jemand helfen konnte zu verstehen, weil ich ziemlich feststecke.

Ich habe zwei Tabellen:

  • Person
  • Item mit einem Nullable-PersonId und ein Type

Ein Element kann einen Eigentümer haben, oder nicht. Folglich hat Person eine Items Eigenschaft, die ein IEnumerable von Item ist.

Eine Person kann eine nur Item nach Typ haben. Wenn die Person sich ändern will, kann er seine aktuelle Element von einem anderen des gleichen Typs in seine Einzelteile ersetzen:

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

Hier ist die Repositorys Struktur und die AddOrUpdate Methode:

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

Diese funktioniert sehr gut und die alten und neuen Artikel 'PersonId Eigenschaften werden korrekt in der Datenbank aktualisiert. Wenn ich jedoch person.Items nach dem SaveChanges() überprüfe, erscheint das alte Element immer noch anstelle des neuen und ich brauche es, um die Steuerwerte der Seite zu aktualisieren.

Obwohl ich die Beiträge mit dem gleichen Problem gelesen habe, konnte ich es nicht lösen ... Ich versuchte viele Dinge, vor allem Aufruf entities.Entry(person).Collection(p => p.Items).Load(), aber bekam jedes Mal eine Ausnahme, die ich versuchte.

Wenn jemand eine Idee hat, fühlen Sie sich bitte frei, ich kann etwas mehr Code bei Bedarf hinzufügen.

Vielen Dank!

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

Zwei Lösungen, die

Lösung 1 (Nachladen aus dem Kontext nach Savechanges) arbeitete

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

Lösung 2 (Update-Datenbank und Eigentum)

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

Ich bin nicht sicher, dass auf einer Entität den Fremdschlüssel zu ändern EF löst es zu platzieren in der richtigen Sammlung. Was passiert, wenn Sie einen neuen Kontext hochfahren und von der Datenbank neu laden? – user2697817

+0

Vielen Dank für Ihre Antwort. Ja, wenn ich es von der Datenbank neu lade funktioniert es (es funktioniert übrigens auch nach dem folgenden Page_Load). Wenn ich 'Item currentItem = entities.Items.SingleOrDefault (i => i.PersonId == Person.Id && i.TypeId == newItem.TypeId);' direkt nach SaveChanges überprüfe, ist 'currentItem' vollkommen in Ordnung. Aber ich würde wirklich brauchen, dass die Eigenschaft up-to-date ist, um die Seitensteuerungen zu aktualisieren. Zurzeit muss ich die Seite aktualisieren (gehe zu Page_Load und lade die aktuelle Person neu), um zu sehen, dass sich das Element geändert hat. –

+0

@jlvaquero: Natürlich habe ich meinen ersten Post mit dem UnitOfWork-Code bearbeitet :) –

Antwort

4

Wie wäre Zusammenhang sicherstellen erfrischend Sie die neuesten db Änderungen nach dem .SaveChanges() Methode haben. Der Pass in der Einheit werden, um einen Anruf Refresh auf den Kontext aktualisiert:

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

Oder die Commit() Methode lassen wie es ist und verwenden Sie einen dynamischeren Ansatz so etwas wie:

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

Die RefreshMode.StoreWins einfach, dass die angibt Datenbank (Speicher) hat Priorität und überschreibt Client (im Speicher) Änderungen.

Wenn die Refresh-Methode, nicht funktioniert, kann ist folgendes zu beachten:

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

Oder wenn alle Stricke reißen, halten Sie es einfach und Dispose Ihrer DbContext, wenn Sie bei jeder Transaktion fertig sind (in diesem Fall nach SaveChanges() wurde aufgerufen). Wenn Sie Ergebnisse nach einem Commit verwenden müssen, behandeln Sie sie als eine neue Transaktion und instanziieren Sie einen neuen DbContext und laden Sie Ihre notwendigen Daten erneut.

+0

Vielen Dank für Ihre Antwort! Ich bin mir jedoch nicht sicher, ob ich es gut verstanden habe: Ich könnte die Methode 'Commit()' so lassen, wie sie ist, und eine 'RefreshEntity()' Methode erstellen, die '_dbContext.Refresh (RefreshMode.StoreWins, entityPassed);'. Ist das richtig ? –

+0

Es scheint, ich habe Ihren Kommentar falsch gelesen. Ich entschuldige mich. Ja, der RefreshEntity-Ansatz sollte gut funktionieren! –

+1

Vielen Dank, ich werde es sofort versuchen. Allerdings hat mein '_dbcontext' keine' Refresh() 'Methode, noch' ObjectStateManager'. Ich weiß nicht, ob es wichtig sein könnte, aber der 'MyEntities' Typ ist von' DbContext' übernommen –

0

Verwenden Trennung zum Beispiel seine adaequat

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

} 
Verwandte Themen