Skip to main content
All docs
V23.2
.NET 6.0+

Relationships Between Entities in Code and UI (EF Core)

  • 7 minutes to read

When designing a business model, it can be necessary to set specific relationships between business objects. This topic describes how to set relationships between entities that are available in the WinForms and ASP.NET Core Blazor applications created with Entity Framework Core and demonstrates how these relationships will be organized in a UI.

Tip

To learn about the relationships between objects in XPO, refer to the following help topic: Relationships Between Persistent Objects in Code and UI (XPO).

The “Many” side is a collection property and displayed in the UI using the DevExpress.ExpressApp.Editors.ListPropertyEditor in the WinForms and ASP.NET Core Blazor applications. To show the “One” side, DevExpress.ExpressApp.Win.Editors.LookupPropertyEditor and DevExpress.ExpressApp.Blazor.Editors.LookupPropertyEditor are used. If ExpandObjectMembersAttribute is applied to the reference property with the ExpandObjectMembers.Never parameter, DevExpress.ExpressApp.Win.Editors.ObjectPropertyEditor and DevExpress.ExpressApp.Blazor.Editors.ObjectPropertyEditor are used instead. Each entity collection has an individual Actions set, which depends on the collection type.

One-to-Many (Non Aggregated)

The relationship between the Department and Contacts illustrates the One-to-Many type, when many Contacts can be included in one Department. In this example, the Department entity contains a child ContactsCollection and is the “One” side of its One-to-Many relationship.

NonAggregatedOneToManyObject_Win

NonAggregatedOneToManyObject_Blazor

The List View that displays the ContactsCollection is accompanied by a New Action. This Action allows end users to add new Contact entities to an existing Department (including the current entity). In addition, the Link and Unlink Actions are available and allow you to add and remove a reference to a Contact object from another collection.

The following code demonstrates how you can implement this type of relationship:

using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Component.DataAnnotations;
// ...
[DefaultClassOptions]
public class Department : BaseObject {
    public virtual string Name { get; set; }
    public virtual IList<Contact> ContactsCollection { get; set; } = new ObservableCollection<Contact>();
}

// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.

One-to-Many (Aggregated)

Let’s assume that a Contact has a collection of Notes, which are aggregated with their parent Contact. In this case, the Note entity declares the “One” aggregated side of the One-to-Many relationship with the Contact entity.

Note

In Entity Framework Core, the aggregation mechanism doesn’t support cascade deletion; however, you can implement this functionality as described in the Cascade Deletion for Aggregated Entities section.

AggregatedOneToManyObject_Win

AggregatedOneToManyObject_Blazor

The List View that displays the NotesCollection is accompanied by the New Action. This Action allows end users to add new Note entities. Note that in this instance, the Contact property of a new Note will be automatically set to the current Contact.

A collection is aggregated if it is decorated with the AggregatedAttribute. The following code demonstrates how you can implement this type of relationship:

using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
// ...
[DefaultClassOptions]
public class Contact {
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    [DevExpress.ExpressApp.DC.Aggregated]
    public virtual IList<Note> NotesCollection { get; set; } = new ObservableCollection<Note>();
}

// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.

Many-to-Many

For example, each Contact can have a collection of Tasks and each Task can be assigned to a number of Contacts. Thus, the relationship between the Contact and Task entities is named Many-to-Many.

NonAggregatedManyToManyObject_Win

NonAggregatedManyToManyObject_blazor

The List View that displays the TasksCollection is accompanied by the Link Action. This action allows end users to add references to existing Task objects. The New Action is not applied to this collection due to unique conceptual properties of the Many-to-Many relationship. However, you can create a new Task in the Link Action’s pop-up window.

End users can also use the Unlink Action to remove references to Task objects from the collection.

The following code demonstrates how you can implement this type of relationship:

using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
// ...
[DefaultClassOptions]
public class Contact : BaseObject {
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual IList<Task> TasksCollection { get; set; } = new ObservableCollection<Task>();
}

// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.

Note

Note that XAF does not support usage scenarios where a many-to-many relationship is only defined on one side of a relation. Always define collection navigation properties on both sides of a relation as demonstrated in the code sample above.

One-to-One

If each Contact can have only one unique Address and one Address cannot be assigned to many Contacts, this is a One-to-One relationship.

Address_Win

OneToOneObject_Blazor

This relationship doesn’t provide a collection side. Note that in this instance, the Contact property of the new Address objects will be automatically set to the current Contact.

The following code demonstrates how you can implement this type of relationship. In this relationship type, it is important to explicitly declare a Primary Key of a parent entity as Primary and a Foreign Key in the related entity.

using DevExpress.Persistent.Base;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
// ...
[DefaultClassOptions]
public class Contact {
    [Key, Browsable(false)]
    public virtual Guid ID { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual Address Address { get; set; }
}

// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.

Cascade Deletion for Aggregated Entities

In applications with Entity Framework Core, aggregation doesn’t use a nested IObjectSpace, thus a cascade deletion mechanism is not organized with this attribute. To implement this mechanism, enforce it in the model builder in the OnModelCreating method. To do this, call the OnDelete(DeleteBehavior.Cascade) method as shown below.

public class MySolutionDbContext : DbContext {
    //...
    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.Entity<Contact>()
            .HasMany(r => r.NotesCollection)
            .WithOne(x => x.Contact)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

For more information, refer to the following article: Cascade Delete.

See Also