Skip to main content
All docs
V22.1
.NET Standard 2.0+

Relationships Between Entities in Code and UI (EF Core)

  • 9 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 and EF 6, refer to the following help topics:

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 into one Department. In this example, Department entity contains a child ContactsCollection collection 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 one of 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 System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
// ...
[DefaultClassOptions]
public class Department : INotifyPropertyChanged {
    public Department() {
        ContactsCollection = new List<Contact>();
    }
    [Browsable(false)]
    public int DepartmentId { get; protected set; }
    private string name;
    public string Name {
        get { return name; }
        set {
            if (name != value) {
                name = value;
                OnPropertyChanged();
            }
        }
    }
    private IList<Contact> contactsCollection;
    public virtual IList<Contact> ContactsCollection {
        get { return contactsCollection; }
        set {
            if (contactsCollection != value) {
                contactsCollection = value;
                OnPropertyChanged();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

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 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 System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
// ...
[DefaultClassOptions]
public class Contact : INotifyPropertyChanged {
    public Contact() {
        NotesCollection = new List<Note>();
    }
    [Browsable(false)]
    public int ContactId { get; protected set; }
    private string firstName;
    public string FirstName {
        get { return firstName; }
        set {
            if (firstName != value) {
                firstName = value;
                OnPropertyChanged();
            }
        }
    }
    private string lastName;
    public string LastName {
        get { return lastName; }
        set {
            if (lastName != value) {
                lastName = value;
                OnPropertyChanged();
            }
        }
    }
    private IList<Note> notesCollection;
    [DevExpress.ExpressApp.DC.Aggregated]
    public virtual IList<Note> NotesCollection {
        get { return notesCollection; }
        set {
            if (notesCollection != value) {
                notesCollection = value;
                OnPropertyChanged();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

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.

The Unlink Action is also provided for the TasksCollection. This Action allows end-users 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 System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
// ...
[DefaultClassOptions]
public class Contact : INotifyPropertyChanged {
    public Contact() {
        TasksCollection = new List<Task>();
    }
    [Browsable(false)]
    public int ContactId { get; protected set; }
    private string firstName;
    public string FirstName {
        get { return firstName; }
        set {
            if (firstName != value) {
                firstName = value;
                OnPropertyChanged();
            }
        }
    }
    private string lastName;
    public string LastName {
        get { return lastName; }
        set {
            if (lastName != value) {
                lastName = value;
                OnPropertyChanged();
            }
        }
    }
    private IList<Task> tasksCollection;
    public virtual IList<Task> TasksCollection {
        get { return tasksCollection; }
        set {
            if (tasksCollection != value) {
                tasksCollection = value;
                OnPropertyChanged();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

One-to-One

If each Contact can have only one unique Address and one Address can’t be assign to many Contacts, this relationship is the One-to-One.

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.Runtime.CompilerServices;
// ...
[DefaultClassOptions]
public class Contact : INotifyPropertyChanged {
    public Contact() { }
    [Browsable(false)]
    public int ContactId { get; protected set; }
    private string firstName;
    public string FirstName {
        get { return firstName; }
        set {
            if (firstName != value) {
                firstName = value;
                OnPropertyChanged();
            }
        }
    }
    private string lastName;
    public string LastName {
        get { return lastName; }
        set {
            if (lastName != value) {
                lastName = value;
                OnPropertyChanged();
            }
        }
    }
    private Address address;
    public virtual Address Address {
        get { return address; }
        set {
            if (address != value) {
                address = value;
                OnPropertyChanged();
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

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.