C# 3.0 SpecificationINotifyPropertyChanged è ormai di casa per chi usa databinding sopratutto per chi lo fa con WPF, con LINQ è in arrivo un altra interfaccia denominata INotifyPropertyChanging.
Mentre la prima ha lo scopo di informare il motore di binding che una determinata proprietà è stata modificata e che quindi l'eventuale controllo ad essa bindato deve essere aggiornato, la seconda serve per ottimizzare il meccanismo di change tracking implementato da LINQ per determinare se un entità è stata modificata e quindi inviare il comando SQL di aggiornamento quando invochiamo DataContext.SubmitChanges().

In assenza di questa interfaccia quando recuperiamo delle entities via DataContext come nell'esempio che segue:

   1:  DataContext dc=new DataContext(@"Data Source=vmxp\SQLEXPRESS;Initial Catalog=Linq2Sql;Integrated Security=True;Pooling=False");
   2:  Table<Customer> customerTable = dc.GetTable<Customer>();
   3:  var query = from c in customerTable where c.City == "Bergamo" select new { FullName = c.Name + " " + c.LastName, c.City };
   4:  dataGridView1.DataSource = query;


una copia delle entities viene memorizzata nel DataContext e viene poi utilizzata per capire se un entity è stata modificata o meno quando si invoca il metodo SubmitChanges().
Oltre a questa copia, l'istanza dell'entity restituita viene a sua volta memorizzata dal DataContext e utilizzata dal cosidetto Identity Management Service affinchè a parità di riga recuperata dal db contentente una primary key corrisponda la stessa istanza dell'entity associata.
Ovvero, se nel DB abbiamo un solo customer che si chiama Teodoro e ha 15 anni recuperando lo stesso usando due diverse query l'entity ritornata è la stessa come dimostrato di seguito:

   1:  MyDb db = new MyDb();
   2:  db.ObjectTrackingEnabled = true; //Change to false to modify behaviour
   3:  Customer c1=db.Customers.Single(c => c.Name == "Teodoro");
   4:  Customer c2 = db.Customers.Single(c => c.CustomerAge == 15);
   5:  bool same= (c1 == c2); //True if ObjectTracking enabled

 

In questo caso MyDb è una classe che eredita da DataContext ed espone una proprietà Customers di tipo Table<Customer> ma quello che è importante notare è che quella che viene ritornata è esattamente la stessa istanza (in alcuni casi non viene nemmeno inviato il comando SQL al DB...)
Questo è vero se il servizio di tracking è attivo ovvero se la proprietà DataContext.ObjectTrackingEnabled=true, impostandola a false il servizio di tracking viene disattivato con relativo incremento di performances anche se, in questo caso, il contenuto del db diventa non modificabile o almeno non direttamente da LINQ.
Sta di fatto che, alla fine, il DataContext mantiene due copie delle entities recuperate:

  • Quella per l' Identity management (che coincide con quella restituita dalla query)
  • Quella per il Change Tracking Service (se ObjectTrackingEnabled=true)

Per la prima dobbiamo ricordarci che l'istanza rimane attiva fino a quando il relativo DataContext rimane caricato, ovvero se scriviamo qualcosa tipo:

   1:  MyDb db = new MyDb();
   2:  Customer c1=db.Customers.Single(c => c.Name == "Corrado");
   3:  c1 = null;

L'istanza di C1 non verrà richiamata dal garbage collector fino a quando il relativo DataContext non verrà impostato a null.
Per la seconda, implementando INotifyPropertyChanging possiamo evitare la creazione della seconda copia facendoci carico di informare il change tracking service quando un entità viene modificata come nell'esempio che segue:

   1:  [Table(Name="Customers")]
   2:   public class Customer:INotifyPropertyChanging
   3:   {
   4:   
   5:     private string _City;
   6:   
   7:     [Column(Name="City",Storage="_City")]
   8:     public string City
   9:     {
  10:      get { return _City; }
  11:      set
  12:      {
  13:        if (value != this.City)
  14:        {
  15:         this.OnPropertyChanging("City");
  16:         _City = value;
  17:        }
  18:      }
  19:     }
  20:     
  21:     private void OnPropertyChanging (string property)
  22:     {
  23:      if (PropertyChanging != null)
  24:        PropertyChanging(this, new PropertyChangingEventArgs(property));
  25:     
  26:     }
  27:  }

 

Gran parte di questo codice ripetitivo viene generato automaticamente da SQLMetal oppure dal designer integrato di Visual Studio 2008 (peccato non funzioni con SQL Server Compact 3.5)
Sia che facciate uso di un tool oppure che le classi le generate voi "by hand" è comunque importante conoscere i "retroscena" legati al DataContext affinchè tutte queste "copie interne" non finiscano per affossare la vostra applicazione.