Definizione e gestione delle relazioni (Entity Framework)

In Entity Framework un'entità può essere correlata ad altre tramite un'associazione. Questa relazione tra entità viene definita nel modello concettuale dall'elemento Association. Ogni relazione contiene due entità finali che descrivono il tipo di entità e la molteplicità del tipo (uno, zero-o-uno oppure molti). 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.

A partire da .NET Framework versione 4, è possibile includere chiavi esterne nel modello concettuale. L'opzione Includi colonne di chiavi esterne nel modello nella Procedura guidata Entity Data Model è selezionata per impostazione predefinita. Quando questa opzione è selezionata, gli oggetti entità generati dispongono di proprietà scalari con mapping alle colonne di chiavi esterne. 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.

Quando le colonne di chiavi esterne non sono incluse nel modello concettuale, le informazioni sull'associazione sono gestite come oggetto indipendente. Le relazioni vengono rilevate tramite riferimenti a un oggetto anziché proprietà di chiave esterna e vengono rappresentate come un oggetto ObjectStateEntry nell'oggetto ObjectStateManager. Questo tipo di associazione viene definito associazione indipendente. Il modo più comune per modificare un'associazione indipendente consiste nel cambiare le proprietà di navigazione generate per ogni entità che fa parte dell'associazione.

In entrambi i tipi di associazioni 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 di oggetti, se la molteplicità è molti.

Combinazione delle relazioni

È possibile scegliere di utilizzare uno o entrambi i tipi di associazioni nel modello. Tuttavia, se si include una tabella che contiene solo chiavi esterne (chiamata anche tabella di join pura) nel modello in uso, per gestire la relazione molti-a-molti pura verrà utilizzata un'associazione indipendente anche se nel modello è stato specificato di utilizzare un'associazione di chiavi esterne. La Procedura guidata Entity Data Model non consente di creare un'entità con mapping a una tabella di join pura.

Creazione e modifica delle relazioni

In Entity Framework è possibile creare e modificare relazioni in diversi modi.

  1. Assegnando un nuovo oggetto a una proprietà di navigazione. Nel codice riportato di seguito viene creata una relazione tra un oggetto order e l'oggetto customer. Se gli oggetti sono connessi al contesto dell'oggetto, l'oggetto order viene aggiunto alla raccolta customer.Orders e la proprietà di chiave esterna corrispondente sull'oggetto order viene impostata sul valore della proprietà chiave del cliente:

    order.Customer = customer 
    
  2. Eliminando o aggiungendo un oggetto in una raccolta di entità. Il metodo Add può essere utilizzato ad esempio per aggiungere un oggetto di tipo Order alla raccolta customer.Orders. Questa operazione consente di creare una relazione tra un determinato oggetto order e l'oggetto customer. Se gli oggetti sono connessi al contesto dell'oggetto, il riferimento al cliente e la proprietà di chiave esterna sull'oggetto order verranno impostati sull'oggetto customer appropriato:

    customer.Orders.Add(order)
    
  3. Nelle associazioni di chiavi esterne è possibile assegnare un nuovo valore a una proprietà di chiave esterna, come nell'esempio riportato di seguito. Se lo stato del riferimento è Added, la proprietà di navigazione di riferimento non sarà sincronizzata con i valori chiave di un nuovo oggetto fino a quando non viene chiamato il metodo 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. Per ulteriori informazioni, vedere Utilizzo di chiavi di entità (Entity Framework). Se è necessario disporre di nuovi oggetti completamente sincronizzati appena si imposta la relazione, utilizzare uno dei due metodi precedenti.

    order.CustomerID = newCustomer.CustomerID 
    order.CustomerID = null
    
  4. Creando la chiave di entità per un oggetto specifico. Se l'oggetto con questa chiave è già presente nel contesto dell'oggetto, il metodo CreateEntityKey restituirà l'oggetto EntityKey dell'oggetto esistente. Questo metodo viene fornito per supportare la compatibilità con le versioni precedenti di .NET Framework 3.5 SP1.

    order.CustomerReference.EntityKey = ctx.CreateEntityKey("EntitySetName", newObject)
    

Quando si modifica la relazione degli oggetti connessi al contesto dell'oggetto utilizzando uno dei metodi descritti in precedenza, in Entity Framework è necessario mantenere sincronizzati le chiavi esterne, i riferimenti e le raccolte. Entity Framework consentirà di gestire questa sincronizzazione per determinati tipi di oggetti idonei:

Se si utilizzano entità POCO senza proxy, è necessario chiamare il metodo DetectChanges per sincronizzare gli oggetti correlati nel contesto dell'oggetto. Se si utilizzano oggetti disconnessi è necessario gestire manualmente la sincronizzazione.

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

' The following example creates a new StudentGrade object and associates 
' the StudentGrade with the Course and Person by 
' setting the foreign key properties. 

Using context As New SchoolEntities()
    ' The database will generate the EnrollmentID. 
    ' To create the association between the Course and StudentGrade, 
    ' and the Student and the StudentGrade, set the foreign key property 
    ' to the ID of the principal. 
    Dim newStudentGrade = New StudentGrade With
    {
        .EnrollmentID = 0,
        .Grade = CDec(4.0),
        .CourseID = 4022,
        .StudentID = 17
    }

    ' Adding the new object to the context will synchronize 
    ' the references with the foreign keys on the newStudentGrade object. 
    context.StudentGrades.AddObject(newStudentGrade)

    ' You can access Course and Student objects on the newStudentGrade object
    ' without loading the references explicitly because
    ' the lazy loading option is set to true in the constructor of SchoolEntities.
    Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID)
    Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID)

    context.SaveChanges()
End Using

' The following example creates a new StudentGrade and associates 
' the StudentGrade with the Course and Person by 
' setting the navigation properties to the Course and Person objects that were returned 
' by the query. 
' You do not need to call AddObject() in order to add the grade object 
' to the context, because when you assign the reference 
' to the navigation property the objects on both ends get synchronized by the Entity Framework. 
' Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method 
' is called if your objects do not meet the change tracking requirements. 
Using context = New SchoolEntities()
    Dim courseID = 4022
    Dim course = (From c In context.Courses
                 Where c.CourseID = courseID
                 Select c).First()

    Dim personID = 17
    Dim student = (From p In context.People
                  Where p.PersonID = personID
                  Select p).First()

    ' The database will generate the EnrollmentID. 
    ' Use the navigation properties to create the association between the objects. 
    Dim newStudentGrade = New StudentGrade With
    {
        .EnrollmentID = 0,
        .Grade = CDec(4.0),
        .Course = course,
        .Person = student
    }
    context.SaveChanges()
End Using
// The following example creates a new StudentGrade object and associates
// the StudentGrade with the Course and Person by
// setting the foreign key properties. 

using (SchoolEntities context = new SchoolEntities())
{
    StudentGrade newStudentGrade = new StudentGrade
    {
        // The database will generate the EnrollmentID.
        EnrollmentID = 0,
        Grade = 4.0M,
        // To create the association between the Course and StudentGrade, 
        // and the Student and the StudentGrade, set the foreign key property 
        // to the ID of the principal.
        CourseID = 4022,
        StudentID = 17,
    };

    // Adding the new object to the context will synchronize
    // the references with the foreign keys on the newStudentGrade object.
    context.StudentGrades.AddObject(newStudentGrade);

    // You can access Course and Student objects on the newStudentGrade object
    // without loading the references explicitly because
    // the lazy loading option is set to true in the constructor of SchoolEntities.
    Console.WriteLine("Student ID {0}:", newStudentGrade.Person.PersonID);
    Console.WriteLine("Course ID {0}:", newStudentGrade.Course.CourseID);
    
    context.SaveChanges();
}

// The following example creates a new StudentGrade and associates
// the StudentGrade with the Course and Person by
// setting the navigation properties to the Course and Person objects that were returned
// by the query. 
// You do not need to call AddObject() in order to add the grade object
// to the context, because when you assign the reference 
// to the navigation property the objects on both ends get synchronized by the Entity Framework.
// Note, that the Entity Framework will not synchronize the ends untill the SaveChanges method
// is called if your objects do not meet the change tracking requirements. 
using (var context = new SchoolEntities())
{
    int courseID = 4022;
    var course = (from c in context.Courses
                 where c.CourseID == courseID
                 select c).First();

    int personID = 17;
    var student = (from p in context.People
                  where p.PersonID == personID
                  select p).First();

    StudentGrade grade = new StudentGrade
    {
        // The database will generate the EnrollmentID.
        Grade = 4.0M,
        // Use the navigation properties to create the association between the objects.
        Course = course,
        Person = student
    };
    context.SaveChanges();
}

Modifica dello stato

In un'associazione di chiavi esterne, quando si modifica la relazione, lo stato di un oggetto dipendente con uno stato Unchanged sarà modificato in Modified.

In una relazione indipendente la modifica della relazione non determinerà l'aggiornamento dello stato dell'oggetto dipendente.

Gestione della concorrenza

Sia nell'associazione di chiavi esterne che in quella indipendente i controlli della concorrenza sono basati sulle chiavi di entità e su altre proprietà dell'entità definite nel livello concettuale impostando l'attributo ConcurrencyMode su fixed.

In un'associazione indipendente, tuttavia, il controllo della concorrenza della relazione viene eseguito sempre sugli oggetti su entrambe le entità finali di una relazione. Questo controllo consente di verificare i valori chiave originali delle entità finali correlate. Se si modifica la relazione mentre l'oggetto è disconnesso dal contesto dell'oggetto, è necessario ricreare la relazione originale (eseguendo di nuovo una query o includendo i valori chiave originali), connettere l'oggetto al contesto dell'oggetto, quindi apportare le modifiche appropriate alla relazione in quel contesto dell'oggetto.

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.

Caricamento di oggetti correlati

In un'associazione di chiavi esterne, quando si carica un'entità finale 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 order where currently AddressID = 1.  SalesOrderHeader order = context.SalesOrderHeaders.First(o=>o.SalesOrderID == orderId);

// Use BillToAddressID foreign key property 
// to change the association.  order.BillToAddressID = 2
order.AddressReference.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 sull'oggetto dipendente punta a un oggetto principale diverso caricato nel contesto dell'oggetto, in Entity Framework si tenterà di creare una relazione come definita sul client.

Considerazioni sulle relazioni di identificazione e di non identificazione

Quando una chiave primaria dell'entità principale fa parte anche della chiave primaria dell'entità dipendente, la relazione è una relazione di identificazione. In questo tipo di relazione l'entità dipendente non può esistere senza l'entità principale. Questo vincolo determina i seguenti comportamenti in una relazione di identificazione:

  • L'eliminazione dell'oggetto principale comporta anche l'eliminazione dell'oggetto dipendente. Questo comportamento equivale a specificare <OnDelete Action="Cascade" /> nel modello per la relazione.

  • La rimozione della relazione comporta l'eliminazione dell'oggetto dipendente. Chiamando il metodo Remove sull'oggetto EntityCollection si contrassegnano per l'eliminazione sia la relazione sia l'oggetto dipendente.

  • Quando si crea un nuovo oggetto dipendente, l'oggetto principale deve essere presente nel contesto dell'oggetto o nell'origine dati prima di chiamare il metodo SaveChanges. Se l'oggetto principale non esiste, verrà generata un'eccezione InvalidOperationException.

In una relazione di non identificazione, se il modello è basato su associazioni di chiavi esterne, l'eliminazione dell'oggetto principale comporterà l'impostazione delle chiavi esterne di oggetti dipendenti su null se sono nullable. Per oggetti dipendenti che non possono esistere senza un oggetto principale, è necessario eliminare manualmente gli oggetti dipendenti o assegnare un nuovo oggetto principale agli oggetti dipendenti. In alternativa, è possibile specificare <OnDelete Action="Cascade" /> nel modello per assicurare che gli oggetti dipendenti vengano eliminati quando si elimina l'oggetto principale correlato.

In questa sezione

Proprietà di navigazione

Procedura: utilizzare la proprietà di chiave esterna per modificare le relazioni tra oggetti

Procedura: utilizzare un elemento EntityReference per modificare le relazioni tra oggetti (Entity Framework)

Procedura: modificare le relazioni tra le entità POCO (Entity Framework)

Vedere anche

Attività

Procedura: utilizzare un elemento EntityReference per modificare le relazioni tra oggetti (Entity Framework)
Procedura: utilizzare la proprietà di chiave esterna per modificare le relazioni tra oggetti

Concetti

Creazione, aggiunta, modifica ed eliminazione di oggetti (Entity Framework)
Utilizzo di entità POCO (Entity Framework)