Změna asociace v Entity Frameworku 4.0 při použití POCO

Entity Framework 4.0 přináší možnost pracovat s vlastními POCO třídami. Tento přístup je vhodný pro složitější vícevrstvé aplikace. Jestliže přístup do databáze přes Entity framework používají i jiné prostředí (C++, Java, Silverlight) nezbývala jiná možnost než napsat POCO třídy a pomocí vlastních algoritmů převést výstup z Entity Frameworku na tyto čisté třídy.

V nové verzi můžete využít přímého mapování bez nutnosti dědit z třídy frameworku, nebo implementovat rozhraní. Tento přístup však sebou přináší úskalí, kterým je nemožnost sledovat změny. Chcete-li sledovat změny můžete využít mechanizmu Self-tracking entities. Při použití tohoto mechanizmu musí každá entita implementovat rozhraní IObjectWithChangeTracker a INotifyPropertyChanged.

Jestliže se rozhodnete pro mapování pomocí čistých POCO tříd narazíte na problém, že metoda SaveChanges() neuloží změnu relace. Při řešení tohoto problému si je nutné nejdříve uvědomit, že framework považuje asociaci za relaci mezi dvěma objekty. Z toho je jasné, že tato relace může nabývat pouze stavů Unchanged, Added, Deleted. Nelze tak změnit stav relace na Modified. Základem řešení problému je tedy nutnost odpojit od entity původní entitu a připojit novou entitu.

Pro ukázku budou použity tabulky Product  a ProductCategory.

Article1_ef

Jak bylo napsáno výše pro úpravu asociace musí být odpojena stará vazba a připojena nová. Proto je vhodné vytvořit jednoduchou generickou strukturu.

Article1_change_tracker

Základní kód pro změnu asociace bude tedy vypadat následovně:

   1:  private static void UpdateRelationship<TItem, TChangeTrackerItem>(System.Data.Objects.ObjectContext context, TItem item, ChangeTracker<TChangeTrackerItem> changeTracker, string navigationProperty)
   2:  {
   3:              // Odebrání staré asociace
   4:              context.ObjectStateManager.ChangeRelationshipState(
   5:                  item,
   6:                  changeTracker.Original,
   7:                  navigationProperty,
   8:                  System.Data.EntityState.Deleted);
   9:   
  10:              // Přidání nové asociace
  11:              context.ObjectStateManager.ChangeRelationshipState(
  12:                  item,
  13:                  changeTracker.Current,
  14:                  navigationProperty,
  15:                  System.Data.EntityState.Added);
  16:  }

 

Jako nejobecnější a nejelegantnější řešení se nabízí vytvořit extension metodu pro ObjectSet<T>.

Metoda vypadá následovně:

   1:  public static void ChangeRelationship<TItem, TRelationship>(this System.Data.Objects.ObjectSet<TItem> objectSet, object item, string associationName)
   2:              where TRelationship : class
   3:              where TItem : class
   4:  {
   5:              ChangeRelationship<TItem, TRelationship>(objectSet.Context, item, objectSet.EntitySet.Name, associationName);
   6:  }

Metoda volaná na řádku číslo 5 vytvoří instanci ChangeTrackeru a pokud se instance liší dojde ke změně. Metoda vypadá následovně:

   1:  private static void ChangeRelationship<TItem, TRelationship>(System.Data.Objects.ObjectContext context, object item, string entitySetName, string associationName) where TRelationship : class
   2:  {
   3:              ChangeTracker<TRelationship> tracker = CreateChangeTracker<TRelationship>(context, entitySetName, associationName, item);
   4:   
   5:              // Jestliže došlo ke změně upraví se asociace.
   6:              if (!tracker.Current.Equals(tracker.Original))
   7:              {
   8:                  UpdateRelationship(context, item, tracker, associationName);
   9:              }
  10:  }
 

Nejdůležitější metodou je metoda CreateChangeTracker.

 

   1:  private static ChangeTracker<T> CreateChangeTracker<T>(System.Data.Objects.ObjectContext context, string entitySetName, string associationName, object item) where T : class
   2:  {
   3:              ChangeTracker<T> tracker = new ChangeTracker<T>();
   4:              context.AttachTo(entitySetName, item);
   5:   
   6:              var entryCurrent = context.ObjectStateManager.GetObjectStateEntry(item);
   7:   
   8:              // Nalezení vazby, která odpovídá dané relaci mezi dvěma objekty.
   9:              System.Data.Objects.DataClasses.EntityReference<T> endCurrent = (System.Data.Objects.DataClasses.EntityReference<T>)entryCurrent
  10:                  .RelationshipManager
  11:                  .GetAllRelatedEnds()
  12:                  .Where(e => e.TargetRoleName.Equals(associationName))
  13:                  .FirstOrDefault();
  14:   
  15:              var entry = context.ObjectStateManager.GetObjectStateEntry(endCurrent.Value);
  16:              tracker.Current = (T)endCurrent.Value;
  17:   
  18:              // Načtení originálního objektu z databáze a donačtení asociace.
  19:              object original = GetOriginal(context, entitySetName, item);
  20:              context.AttachTo(entitySetName, item);
  21:              context.LoadProperty(item, associationName);
  22:   
  23:              // Načtení asociace z originálního objektu.
  24:              var entryOriginal = context.ObjectStateManager.GetObjectStateEntry(item);
  25:              System.Data.Objects.DataClasses.EntityReference<T> endOriginal = (System.Data.Objects.DataClasses.EntityReference<T>)entryOriginal
  26:                  .RelationshipManager
  27:                  .GetAllRelatedEnds()
  28:                  .Where(e => e.TargetRoleName.Equals(associationName))
  29:                  .FirstOrDefault();
  30:   
  31:              tracker.Original = (T)endOriginal.Value;
  32:              return tracker;
  33:  }

Metoda zjistí z databáze stav původní a současné entity a do instance třídy ChangeTracker uloží asociace, které tyto třídy mají. Zjišťování původního stavu probíhá pomocí metody GetOriginal:

   1:  private static T GetOriginal<T>(System.Data.Objects.ObjectContext context, string entitySetName, T current) where T : class
   2:  {
   3:              // Připojení současného objektu ke kontextu.
   4:              context.AttachTo(entitySetName, current);
   5:              // Vyhledání stavu v kontextu
   6:              System.Data.Objects.ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(current);
   7:              // Nalezení podle klíče
   8:              System.Data.EntityKey key = entry.EntityKey;
   9:              T originalItem = (T)context.GetObjectByKey(key);
  10:              // Odpojení originálních dat od kontextu.
  11:              context.Detach(originalItem);
  12:              return originalItem;
  13:  }

 

Vytvořenou extension metodu stačí zavolat například v repozitory při ukládání upravené entity.

   1:  public Shared.Product Update(Shared.Product item)
   2:  {
   3:              using (ArticleShopEntities context = new ArticleShopEntities())
   4:              {
   5:                  // Připojení entity k aktuálnímu contextu.
   6:                  context.Product.Attach(item);
   7:                  
   8:                  // Pokus o změnu asociace. 
   9:                  // Jestliže jsou původní a nová asociace stejné nedochází k žádné změně.
  10:                  context.Product.ChangeRelationship<Product, ProductCategory>(item, "ProductCategory");
  11:   
  12:                  // Nastavení produktu na modifikovaný stav.
  13:                  context.ObjectStateManager.ChangeObjectState(item, System.Data.EntityState.Modified);
  14:                  
  15:                  // Uložení produktu.
  16:                  context.SaveChanges(System.Data.Objects.SaveOptions.DetectChangesBeforeSave | System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
  17:              }
  18:              return item;
  19:  }

Extension metoda ChangeRelationship může být použita pro změnu relace 1:N.

Není tedy nutné používat generované Proxy nebo Self-tracking entities.

V příštích několika dnech nahraji hotovou knihovnu zde pod příspěvek.

Zveřejněno 20 června 10 10:20 by t.jerabek
Vedeno pod:

Komentář

Žádné komentáře
Neregistrovaní uživatele nemužou přidávat komentáře.

Search

Go

Štítky

Archives

Syndication