Skip to main content

Relazioni e proprietà di navigazione

In questo argomento vengono fornite una panoramica sulla gestione delle relazioni tra le entità da parte di Entity Framework, nonché alcune informazioni aggiuntive sul mapping e sulla modifica delle relazioni.

 

Relazioni, proprietà di navigazione e chiavi esterne

Nei database relazionali le relazioni (anche denominate associazioni) tra tabelle vengono definite tramite le chiavi esterne. Una chiave esterna (FK, Foreign Key) è una colonna o combinazione di colonne utilizzata per stabilire e applicare un collegamento tra i dati di due tabelle. Sono disponibili tre tipi di relazioni: uno-a-uno, uno-a-molti e molti-a-molti. In una relazione uno-a-molti la chiave esterna viene definita nella tabella che rappresenta l'estremità molti della relazione. La relazione molti-a-molti interessa la definizione di una terza tabella (definita tabella di collegamento o di join) la cui chiave primaria è costituita dalle chiavi esterne di entrambe le tabelle correlate. In una relazione uno-a-uno la chiave primaria viene inoltre utilizzata come chiave esterna e non è presente alcuna colonna chiave esterna separata per nessuna tabella.

Nella figura seguente vengono illustrate due tabelle utilizzate in una relazione uno-a-molti. La tabella Course è quella dipendente poiché in essa è contenuta la colonna DepartmentID tramite cui viene collegata alla tabella Department.

Schermata di un vincolo di chiave esterna definito in un database

In Entity Framework un'entità può essere correlata ad altre tramite un'associazione (relazione). In ogni relazione sono contenute due estremità tramite cui vengono descritti il tipo di entità e la molteplicità del tipo (uno, zero-o-uno oppure molti) per le due entità della relazione. La relazione può essere regolata da un vincolo referenziale che descrive quale entità finale nella relazione riveste il ruolo principale e quale quello di dipendente.

Tramite le proprietà di navigazione viene fornito un modo per spostare un'associazione tra due tipi di entità. Ogni oggetto può disporre di una proprietà di navigazione per ogni relazione di cui fa parte. Le proprietà di navigazione consentono di spostare e gestire relazioni in entrambe le direzioni, restituendo un oggetto di riferimento, se la molteplicità è uno o zero-o-uno, oppure una raccolta, se la molteplicità è molti. È inoltre possibile scegliere di disporre di navigazione unidirezionale; in questo caso la proprietà di navigazione viene definita solo in uno dei tipi utilizzati nella relazione e non in entrambi.

Si consiglia di includere le proprietà del modello tramite cui viene eseguito il mapping alle chiavi esterne del database. Con le proprietà di chiave esterna incluse, è possibile creare o modificare una relazione cambiando il valore della chiave esterna in un oggetto dipendente. Questo tipo di associazione viene definito associazione di chiavi esterne. Le chiavi esterne sono maggiormente utili quando si utilizzano applicazioni a più livelli. Si noti che quando si utilizzano relazioni 1 a 1 o 1 a 0..1, non è presente alcuna colonna chiave esterna separata, la proprietà della chiave primaria viene utilizzata come chiave esterna ed è sempre inclusa nel modello.

Quando le colonne chiavi esterne non sono incluse nel modello, le informazioni sull'associazione vengono gestite come oggetto indipendente. Le relazioni vengono rilevate tramite i riferimenti a oggetti anziché le proprietà di chiave esterna. Questo tipo di associazione è denominato associazione indipendente. Il modo più comune per modificare un'associazione indipendente consiste nel cambiare le proprietà di navigazione generate per ogni entità utilizzata nell'associazione.

È possibile scegliere di utilizzare uno o entrambi i tipi di associazioni nel modello. Tuttavia, se si dispone di una pura relazione molti-a-molti connessa tramite una tabella di join in cui sono contenute solo chiavi esterne, in Entity Framework verrà utilizzata un'associazione indipendente per gestire questa relazione molti-a-molti.   

Nella figura seguente viene illustrato un modello concettuale creato con Entity Framework Designer. Nel modello sono contenute due entità utilizzate nella relazione uno-a-molti a cui sono associate proprietà di navigazione. Course è l'entità dipendente per cui è stata definita la proprietà di chiave esterna DepartmentID.

Schermata di una relazione definita in Entity Framework Designer

 

Nella figura seguente viene illustrato lo stesso modello creato con Code First.

Schermata di una relazione definita utilizzando Code First

 

Configurazione e mapping delle relazioni

Nella seconda parte di questa pagina viene illustrato come accedere ai dati e modificarli utilizzando le relazioni. Per informazioni sulla configurazione delle relazioni nel modello, vedere le pagine riportate di seguito.

 

Creazione e modifica delle relazioni

In un'associazione di chiavi esterne quando si modifica la relazione, un oggetto dipendente passa dallo stato EntityState.Unchanged a EntityState.Modified. In una relazione indipendente la modifica della relazione non determina l'aggiornamento dello stato dell'oggetto dipendente.

Negli esempi seguenti viene mostrato come utilizzare le proprietà di chiave esterna e di navigazione per associare gli oggetti correlati. Con le associazioni di chiavi esterne, è possibile utilizzare uno qualsiasi dei metodi per cambiare, creare o modificare relazioni. Con associazioni indipendenti, non è possibile utilizzare la proprietà di chiave esterna.

  • Assegnando un nuovo valore a una proprietà di chiave esterna, come nell'esempio riportato di seguito.

    course.DepartmentID = newCourse.DepartmentID;

 

  • Tramite il codice seguente viene rimossa una relazione impostando la chiave esterna su null. Si noti che per la proprietà di chiave esterna devono essere ammessi i valori Null.

    course.DepartmentID = null;

    Nota: se lo stato del riferimento è Added (in questo esempio l'oggetto Course), la proprietà di navigazione di riferimento non sarà sincronizzata con i valori di chiave di un nuovo oggetto finché non viene chiamato SaveChanges. La sincronizzazione non si verifica in quanto il contesto dell'oggetto non contiene chiavi permanenti per gli oggetti aggiunti fino quando questi non vengono salvati. Se è necessario disporre di nuovi oggetti completamente sincronizzati non appena si imposta la relazione, utilizzare uno dei metodi riportati di seguito.

 

  • Assegnando un nuovo oggetto a una proprietà di navigazione. Nel codice seguente viene creata una relazione tra un oggetto course e uno department. Se gli oggetti sono connessi al contesto, l'oggetto course viene anche aggiunto alla raccolta department.Courses e la proprietà di chiave esterna corrispondente nell'oggetto course viene impostata sul valore della proprietà chiave dell'oggetto department.

    course.Department = department;

 

  • Per eliminare la relazione, impostare la proprietà di navigazione su null. Se si utilizza Entity Framework basato su .NET 4.0, l'estremità correlata deve essere caricata prima di essere impostata su null. Ad esempio:

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;


    A partire da Entity Framework 5.0, che è basato su .NET 4.5, la relazione può essere impostata su null senza caricare l'estremità correlata. Anche il valore corrente può essere impostato su null utilizzando il metodo riportato di seguito.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;

 

  • Eliminando o aggiungendo un oggetto in una raccolta di entità. Ad esempio, è possibile aggiungere un oggetto di tipo Course alla raccolta department.Courses. Tramite questa operazione viene creata una relazione tra un particolare oggetto course e un determinato oggetto department. Se gli oggetti sono connessi al contesto, il riferimento all'oggetto department e la proprietà di chiave esterna nell'oggetto course verranno impostati sull'oggetto department appropriato.

    department.Courses.Add(newCourse);

 

  • Utilizzando il metodo ChangeRelationshipState per modificare lo stato della relazione specificata tra due oggetti entità. In genere, questo metodo viene utilizzato in caso di applicazioni a più livelli e di un'associazione indipendente (non può essere utilizzato con un'associazione di chiavi esterne). Inoltre, per utilizzare questo metodo è necessario scorrere fino a ObjectContext, come illustrato nell'esempio seguente.

    Nell'esempio seguente è illustrata una relazione molti-a-molti tra gli oggetti Instructors e Courses. Chiamando il metodo ChangeRelationshipState e passando il parametro EntityState.Added, all'oggetto SchoolContext è nota l'aggiunta di una relazione tra i due oggetti.

           ((IObjectContextAdapter)context).ObjectContext.
                     ObjectStateManager.
                      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
       

    Si noti che se si sta aggiornando (non solo aggiungendo) una relazione, è necessario eliminare la relazione precedente dopo l'aggiunta di quella nuova:

           ((IObjectContextAdapter)context).ObjectContext.
                      ObjectStateManager.
                      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);    

 

Sincronizzazione delle modifiche tra le chiavi esterne e le proprietà di navigazione

Quando si modifica la relazione degli oggetti connessi al contesto utilizzando uno dei metodi descritti in precedenza, è necessario mantenere sincronizzati le chiavi esterne, i riferimenti e le raccolte tramite Entity Framework. Mediante quest'ultimo la sincronizzazione (nota anche come correzione della relazione) viene gestita automaticamente per le entità POCO con proxy. Per ulteriori informazioni, vedere la pagina relativa all' utilizzo dei proxy.

Se si utilizzano entità POCO senza proxy, è necessario assicurarsi di chiamare il metodo DetectChanges per sincronizzare gli oggetti correlati nel contesto. Si noti che tramite le API seguenti viene generata automaticamente una chiamata a DetectChanges.

  • DbSet.Add
  • DbSet.Find
  • DbSet.Remove
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • Esecuzione di una query LINQ su un'API DbSet

 

Caricamento di oggetti correlati

In genere, in Entity Framework le proprietà di navigazione vengono utilizzate per caricare le entità che sono correlate a quella restituita dall'associazione definita. Per ulteriori informazioni, vedere la pagina relativa al caricamento di oggetti correlati.

Nota: in un'associazione di chiavi esterne quando si carica un'estremità correlata di un oggetto dipendente, l'oggetto correlato sarà caricato in base al valore della chiave esterna dell'oggetto dipendente attualmente in memoria:

// Get the course where currently DepartmentID = 1.
Course course2 = context.Courses.First(c=>c.DepartmentID == 2);

// Use DepartmentID foreign key property 
// to change the association.
course2.DepartmentID = 3;

// Load the related Department where DepartmentID = 3
context.Entry(course).Reference(c => c.Department).Load();

In un'associazione indipendente viene eseguita una query sull'entità finale correlata di un oggetto dipendente in base al valore della chiave esterna che è attualmente nel database. Tuttavia, se la relazione è stata modificata e la proprietà di riferimento nell'oggetto dipendente punta a un oggetto principale diverso caricato nel contesto dell'oggetto, tramite Entity Framework si tenterà di creare una relazione come definita nel client.

 

Gestione della concorrenza

Sia nell'associazione di chiavi esterne sia in quella indipendente i controlli della concorrenza sono basati sulle chiavi di entità e su altre proprietà dell'entità definite nel modello. Quando si utilizza Entity Framework Designer per creare un modello, impostare l'attributo ConcurrencyMode su fixed per specificare che è necessario verificare la concorrenza della proprietà. Quando si utilizza Code First per definire un modello, utilizzare l'annotazione ConcurrencyCheck nelle proprietà di cui si desidera verificare la concorrenza. Quando si utilizza Code First è anche possibile utilizzare l'annotazione TimeStamp per specificare che è necessario verificare la concorrenza della proprietà. In una determinata classe può essere presente una sola proprietà timestamp. Tramite Code First viene eseguito il mapping di questa proprietà a un campo in cui non sono ammessi i valori Null nel database.

Quando si utilizzano entità coinvolte nel controllo e nella risoluzione della concorrenza si consiglia di utilizzare sempre l'associazione di chiavi esterne.

Per ulteriori informazioni, vedere la pagina relativa ai modelli di concorrenza ottimistica.

 

Utilizzo di chiavi sovrapposte

Le chiavi sovrapposte sono chiavi composte in cui alcune proprietà nella chiave fanno parte anche di un'altra chiave nell'entità. Non è possibile disporre di una chiave sovrapposta in un'associazione indipendente. Per modificare un'associazione di chiavi esterne che include chiavi sovrapposte, si consiglia di modificare i valori della chiave esterna anziché utilizzare i riferimenti a un oggetto.