2011-08-07 8 views
13

Zajęło mi zbyt długo, aby znaleźć rozwiązanie dla scenariusza opisanego poniżej. To, co na pozór wydawało się proste, okazało się dość trudne. Pytanie brzmi:Entity Framework, Code First, Zaktualizuj relację "jeden do wielu" z niezależnymi stowarzyszeniami

Korzystanie z Entity Framework 4.1 (podejście Code First) i "Independent association" w jaki sposób przypisać inny koniec do istniejącej relacji "wiele do jednego" w scenariuszu "odłączonym" (Asp.Net w moim walizka).

Model:

Zdaję sobie sprawę, że za pomocą relacji ForeignKey zamiast niezależnych stowarzyszeń byłaby opcja, ale to było moje preferencje nie mają realizację ForeignKey w moim Poços.

Klient ma jeden lub więcej celów:

public class Customer:Person 
{ 
    public string Number { get; set; } 
    public string NameContactPerson { get; set; } 
    private ICollection<Target> _targets; 

    // Independent Association 
    public virtual ICollection<Target> Targets 
    { 
     get { return _targets ?? (_targets = new Collection<Target>()); } 
     set { _targets = value; } 
    } 
} 

cel ma jeden klient:

public class Target:EntityBase 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
    public string Note { get; set; } 
    public virtual Address Address { get; set; } 
    public virtual Customer Customer { get; set; } 
} 

klienta wywodzi się z klasy Person:

public class Person:EntityBase 
{   
    public string Salutation { get; set; } 
    public string Title { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set ; }   
    public string Telephone1 { get; set; } 
    public string Telephone2 { get; set; } 
    public string Email { get; set; }   

    public virtual Address Address { get; set; } 
} 

klasa PodstawaJednostki zapewnia pewne właściwości wspólne:

public abstract class EntityBase : INotifyPropertyChanged 
{ 
    public EntityBase() 
    { 
     CreateDate = DateTime.Now; 
     ChangeDate = CreateDate; 
     CreateUser = HttpContext.Current.User.Identity.Name; 
     ChangeUser = CreateUser; 
     PropertyChanged += EntityBase_PropertyChanged; 
    } 

    public void EntityBase_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (Id != new Guid()) 
     { 
      ChangeDate = DateTime.Now; 
      ChangeUser = HttpContext.Current.User.Identity.Name; 
     } 
    } 

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, e); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    public Guid Id { get; set; } 
    public DateTime CreateDate { get; set; } 
    public DateTime? ChangeDate { get; set; } 
    public string CreateUser { get; set; } 
    public string ChangeUser { get; set; } 
} 

Kontekst:

public class TgrDbContext : DbContext 
{ 
    public DbSet<Person> Persons { get; set; } 
    public DbSet<Address> Addresses { get; set; } 
    public DbSet<Customer> Customers { get; set; } 
    public DbSet<Target> Targets { get; set; } 
    public DbSet<ReportRequest> ReportRequests { get; set; } 

    // If OnModelCreating becomes to big, use "Model Configuration Classes" 
    //(derived from EntityTypeConfiguration) instead 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Entity<Person>().HasOptional(e => e.Address);    
     modelBuilder.Entity<Customer>().HasMany(c => c.Targets).WithRequired(t => t.Customer);    
    } 

    public static ObjectContext TgrObjectContext(TgrDbContext tgrDbContext) 
    {   
     return ((IObjectContextAdapter)tgrDbContext).ObjectContext; 
    } 
} 
+0

Nie mam wystarczającej liczby punktów rekomendacji, aby opublikować odpowiedź w ciągu najbliższych 8 godzin. Publikuje posty, gdy tylko będzie to możliwe. – Martin

+0

To jest dość trudne Pracuję nad całym blogiem na temat tego problemu (mam prawie gotowe przez dwa miesiące, ale nadal nie jestem w pełni z tego zadowolony). Aktualizacja relacji jeden-do-wielu po wielu stronach jest prawdopodobnie główną przyczyną zaimplementowania stowarzyszeń kluczy zagranicznych (zamiast naprawiania tego strasznego zachowania). –

+0

Zgadzam się. Procedura wydaje się zdecydowanie trudniejsza niż powinna być. Mimo to, EF 4 i 4.1 były znacznymi ulepszeniami w stosunku do poprzednich wersji. Badanie tematów związanych z EF 4.1 jest trudne ze względu na zmiany API między CTP a wersjami wydania, różne podejścia w scenariuszach oderwanych i połączonych, wiele, nadmiernie uproszczonych przykładowych aplikacji, które nie dostarczają wskazówek dla dodatkowej złożoności, która -tier rozwiązań ... – Martin

Odpowiedz

5

Czekałem na odpowiedź @Martin, ponieważ istnieje więcej rozwiązań tego problemu. Oto kolejny jeden (przynajmniej działa z ObjectContext API więc powinien działać z DbContext API również):

// Existing customer 
var customer = new Customer() { Id = customerId }; 
// Another existing customer 
var customer2 = new Customer() { Id = customerId2 }; 

var target = new Target { ID = oldTargetId }; 
// Make connection between target and old customer 
target.Customer = customer; 

// Attach target with old customer 
context.Targets.Attach(target); 
// Attach second customer 
context.Customers.Attach(customer2); 
// Set customer to a new value on attached object (it will delete old relation and add new one) 
target.Customer = customer2; 

// Change target's state to Modified 
context.Entry(target).State = EntityState.Modified; 
context.SaveChanges(); 

Problem polega na tym modelu wewnętrznego państwa i państwowe validations wewnątrz EF.Podmiot w stanie niezmienionym lub zmodyfikowanym z relacją obowiązkową (po wielu stronach) nie może mieć niezależnego powiązania w stanie dodanym, gdy nie ma innego w stanie usuniętym. Zmodyfikowany stan dla asocjacji nie jest dozwolony.

+0

Dziękuję za odpowiedź, Ladislav. Twoje rozwiązanie działa i wolę go od mojego, ponieważ twoje nie wymaga użycia API ObjectContext. public void UpdateTarget (Target target, Target origTarget) { spróbuj { _tgrDbContext.Targets.Attach (origTarget); _tgrDbContext.Entry (origTarget) .Reload(); _tgrDbContext.Customers.Attach (target.Customer); _tgrDbContext.Entry (origTarget) .CurrentValues.SetValues ​​(target); origTarget.Customer = target.Customer; _tgrDbContext.Entry (origTarget) .State = EntityState.Modified; _tgrDbContext.SaveChanges(); } – Martin

+0

Preferuj rozwiązanie @Martin. Na wypadek, gdybyśmy musieli zastąpić relację 1-> 1, można uruchomić kod Martina, pomijając część kodu usunięcia. Wymagana jest tylko linia "EntityState.Added". Zasiłek? Nie musisz znać poprzedniego obiektu podrzędnego. Który w mojej sytuacji ma bardzo duży zysk. – Avrohom

4

Istnieje wiele informacji można znaleźć na ten temat; na stackoverflow szczególnie pomocne okazały się spostrzeżenia Ladislava Mrnki. Więcej informacji na ten temat można znaleźć tutaj: NTier Improvements for Entity Framework i tutaj What's new in Entity Framework 4?

W moim projekcie (Webforms Asp.Net) użytkownik ma możliwość zastąpienia Klienta przypisanego do obiektu Target innym (istniejącym) obiektem Klienta. Ta transakcja jest wykonywana przez formant FormView powiązany z ObjectDataSource. ObjectDataSource komunikuje się z warstwą BusinessLogic projektu, który z kolei przekazuje transakcję do klasy repozytorium dla obiektu Target w warstwie DataAccess. Metoda Aktualizacja dla obiektu docelowego w repozytorium klasy wygląda następująco:

public void UpdateTarget(Target target, Target origTarget) 
    { 
     try 
     { 
      // It is not possible to handle updating one to many relationships (i.e. assign a 
      // different Customer to a Target) with "Independent Associations" in Code First. 
      // (It is possible when using "ForeignKey Associations" instead of "Independent 
      // Associations" but this brings about a different set of problems.) 
      // In order to update one to many relationships formed by "Independent Associations" 
      // it is necessary to resort to using the ObjectContext class (derived from an 
      // instance of DbContext) and 'manually' update the relationship between Target and Customer. 

      // Get ObjectContext from DbContext - ((IObjectContextAdapter)tgrDbContext).ObjectContext; 
      ObjectContext tgrObjectContext = TgrDbContext.TgrObjectContext(_tgrDbContext); 

      // Attach the original origTarget and update it with the current values contained in target 
      // This does NOT update changes that occurred in an "Independent Association"; if target 
      // has a different Customer assigned than origTarget this will go unrecognized 
      tgrObjectContext.AttachTo("Targets", origTarget); 
      tgrObjectContext.ApplyCurrentValues("Targets", target); 

      // This will take care of changes in an "Independent Association". A Customer has many 
      // Targets but any Target has exactly one Customer. Therefore the order of the two 
      // ChangeRelationshipState statements is important: Delete has to occur first, otherwise 
      // Target would have temporarily two Customers assigned. 
      tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
       origTarget, 
       origTarget.Customer, 
       o => o.Customer, 
       EntityState.Deleted); 

      tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
       origTarget, 
       target.Customer, 
       o => o.Customer, 
       EntityState.Added); 

      // Commit 
      tgrObjectContext.Refresh(RefreshMode.ClientWins, origTarget); 
      tgrObjectContext.SaveChanges(); 
     } 
     catch (Exception) 
     { 
      throw; 
     } 
    }    

Działa to dla sposobu aktualizacji dla obiektu docelowego. Co godne uwagi, procedura wstawiania nowego obiektu docelowego jest znacznie łatwiejsza. DbContext poprawnie rozpoznaje koniec niezależnego powiązania klienta i zatwierdza zmianę w bazie danych bez dalszych ceregieli. Metoda Insert w klasie repozytoriów wygląda następująco:

 public void InsertTarget(Target target) 
    { 
     try 
     { 
      _tgrDbContext.Targets.Add(target); 
      _tgrDbContext.SaveChanges(); 
     } 
     catch (Exception) 
     { 
      throw; 
     } 
    } 

Mam nadzieję, że będzie to przydatne dla kogoś, kto ma podobne zadanie. Jeśli zauważysz problem z powyższym podejściem, daj mi znać w swoich komentarzach. Dzięki!

Powiązane problemy