Relationships Between Entities in Code and UI

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 application created with Entity Framework Code First and demonstrates how these relationships will be organized in a UI.

Tip

To learn about the relationships between persistent objects in XPO, refer to the Relationships Between Persistent Objects in Code and UI topic.

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

This topic includes the following sections.

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_Web

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.

[DefaultClassOptions]
public class Contact {
    [Browsable(false)]
    public Int32 ContactId { get; protected set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public virtual Department Department { get; set; }
}
[DefaultClassOptions]
public class Department {
    [Browsable(false)]
    public Int32 DepartmentId { get; protected set; }
    public String Name { get; set; }
    public Department() {
        ContactsCollection = new List<Contact>();
    }
    public virtual IList<Contact> ContactsCollection { get; set; }
}

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, 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_Web

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.

[DefaultClassOptions]
public class Contact {
    [Browsable(false)]
    public Int32 ContactId { get; protected set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public Contact() {
        NotesCollection = new List<Note>();
    }
    [DevExpress.ExpressApp.DC.Aggregated]
    public virtual IList<Note> NotesCollection { get; set; }
}
[DefaultClassOptions]
public class Note {
    [Browsable(false)]
    public Int32 NoteId { get; protected set; }
    public String Text { get; set; }        
    public virtual Contact Contact { get; set; }
}

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_Web

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.

[DefaultClassOptions]
public class Contact {
    [Browsable(false)]
    public Int32 ContactId { get; protected set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public Contact() {
        TasksCollection = new List<Task>(); 
    }
    public virtual IList<Task> TasksCollection { get; set; }
}
[DefaultClassOptions]
public class Task {
    [Browsable(false)]
    public Int32 TaskId { get; protected set; }
    public String Subject { get; set; }
    public DateTime DueDate { get; set; }    
    public Task() {
        ContactsCollection = new List<Contact>();
    }
    public virtual IList<Contact> ContactsCollection { get; set; }
}

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

Address_Web

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.

[DefaultClassOptions]
public class Contact {
    [Browsable(false)]
    public Int32 ContactId { get; protected set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }
    public Contact() { }
    public virtual Address Address { get; set; }
}
[DefaultClassOptions]
public class Address {
    [Browsable(false)] 
    [Key, ForeignKey("Contact")]
    public Int32 ContactId { get; protected set; }
    public String FullAddress { get; set; }
    public String ZipPostal { get; set; }
    public virtual Contact Contact { get; set; }
}

Cascade Deletion for Aggregated Entities

In applications with Entity Framework, 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 DbContext.OnModelCreating method. Use Fluent API to call the WillCascadeOnDelete(true) method as shown below.

public class MySolutionDbContext : DbContext {
    //...
    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        //...
        modelBuilder.Entity<Contact>()
            .HasMany(r => r.AggregatedOneToManyCollection)
            .WithRequired(x => x.Contact)
            .WillCascadeOnDelete();
    }
}

Also, you can use the [System.ComponentModel.DataAnnotations.Required] attribute as demonstrated below.

[DefaultClassOptions]
public class Contact {
    //...
    [DevExpress.ExpressApp.DC.Aggregated]
    public virtual IList<Note> NotesCollection { get; set; }
}
[DefaultClassOptions]
public class Note {
    //...
    [System.ComponentModel.DataAnnotations.Required]
    public virtual Contact Contact { get; set; }
}
See Also