KB Article A669
Visible to All Users

How to define a one-to-many relation without introducing new properties on the "one" side

Description:
I have a common library with common objects and several libraries that are specific to applications. How can I define a collection property in my specific classes which references objects from the common library without changing the common objects?

Answer:
Suppose you have a Role class and it is the most common class which will be used by many other classes, for instance, by Contact, Company, Customer, and User. Each of these should have a collection of linked roles. With XPO you must define every relation in your code, i.e. Role must have the Contact, Company, Customer, and User properties. In addition, you may be planning to introduce several other classes, which should have a collection of Roles. This means that the Role class should be extended with new properties.
To avoid the Role class growing in this way, you may introduce a new object to define a one-to-many association, for instance, for the Customer class it may be a CustomerToRole class with two properties: Customer and Role. Thus, the customer class should have a collection of CustomerToRole objects.
Note. This solution is not quite good. If an association property cannot be added to the class definition, then it's best to add it to the metadata. For more information, see "XPClassInfo.CreateMember Method" topic at http://www.devexpress.com/Help/?document=XPO/DevExpressXpoMetadataXPClassInfo_CreateMembertopic262.htm.

C#
using System; using DevExpress.Xpo; using DevExpress.Data.Filtering; public class Role : XPObject { public string Name = string.Empty; public string Description = string.Empty; public byte[] PermissionData; public Role() {} public Role(string name) { Name = name; } public Role(Session session) : base(session) {} } public class Customer : XPObject { public string Name; public Customer() {} public Customer(string name) { Name = name; } public Customer(Session session) : base(session) {} [Association("CustomerRoles", typeof(CustomerToRole)), Aggregated] internal XPCollection RoleLinks { get { return GetCollection("RoleLinks"); } } RolesCollection roles; public RolesCollection Roles { get { if(roles == null) roles = new RolesCollection(this); roles.Reload(); return roles; } } } public class CustomerToRole : XPObject { [Association("CustomerRoles")] public Customer Customer; public Role Role; public CustomerToRole() : this(null) {} public CustomerToRole(Session session) : base(session) {} } public class RolesCollection : XPCollection { Customer customer; public RolesCollection(Customer customer): base(customer.Session, typeof(Role)) { this.customer = customer; LoadingEnabled = false; } bool isInternalLoading; public override void Reload() { base.Reload(); if(isInternalLoading) return; isInternalLoading = true; if(customer.RoleLinks.Count > 0) { object[] roles = new object[customer.RoleLinks.Count]; for(int i = 0; i < customer.RoleLinks.Count; i++) roles[i] = ((CustomerToRole)customer.RoleLinks[i]).Role; this.AddRange(roles); } isInternalLoading = false; } protected override void OnCollectionChanged(XPCollectionChangedEventArgs args) { if(!isInternalLoading && args.ChangedObject is Role) { if(args.CollectionChangedType == XPCollectionChangedType.BeforeAdd) { CustomerToRole link = new CustomerToRole(Session); link.Customer = customer; link.Role = (Role)args.ChangedObject; customer.RoleLinks.Add(link); customer.Save(); } if(args.CollectionChangedType == XPCollectionChangedType.BeforeRemove) { CustomerToRole link = Session.FindObject(typeof(CustomerToRole), new GroupOperator( new BinaryOperator("Customer", customer), new BinaryOperator("Role", args.ChangedObject))) as CustomerToRole; if(link != null) { customer.RoleLinks.Remove(link); customer.Save(); link.Delete(); } } } base.OnCollectionChanged(args); } }
Visual Basic
Imports System Imports DevExpress.Xpo Imports DevExpress.Data.Filtering Public Class Role Inherits XPObject Public Name As String = String.Empty Public Description As String = String.Empty Public PermissionData() As Byte Public Sub New() End Sub Public Sub New(ByVal session As Session) MyBase.New(session) End Sub End Class Public Class Customer Inherits XPObject Public Name As String Public Sub New() End Sub Public Sub New(ByVal session As Session) MyBase.New(session) End Sub <Association("CustomerRoles", GetType(CustomerToRole)), Aggregated()> _ Friend ReadOnly Property RoleLinks() As XPCollection Get Return GetCollection("RoleLinks") End Get End Property Private rolesValue As RolesCollection Public ReadOnly Property Roles() As RolesCollection Get If rolesValue Is Nothing Then rolesValue = New RolesCollection(Me) End If rolesValue.Reload() Return rolesValue End Get End Property End Class Public Class CustomerToRole Inherits XPObject <Association("CustomerRoles")> _ Public Customer As Customer Public Role As Role Public Sub New() MyClass.New(Nothing) End Sub Public Sub New(ByVal session As Session) MyBase.New(session) End Sub End Class Public Class RolesCollection Inherits XPCollection Private customer As customer Public Sub New(ByVal customer As customer) MyBase.New(customer.Session, GetType(Role)) Me.customer = customer LoadingEnabled = False End Sub Private isInternalLoading As Boolean Public Overrides Sub Reload() MyBase.Reload() If isInternalLoading Then Return End If isInternalLoading = True If customer.RoleLinks.Count > 0 Then Dim roles() As Object = New Object(customer.RoleLinks.Count - 1) {} For i As Integer = 0 To customer.RoleLinks.Count - 1 roles(i) = CType(customer.RoleLinks(i), CustomerToRole).Role Next Me.AddRange(roles) End If isInternalLoading = False End Sub Protected Overrides Sub OnCollectionChanged(ByVal args As XPCollectionChangedEventArgs) If Not isInternalLoading AndAlso (TypeOf args.ChangedObject Is Role) Then If args.CollectionChangedType = XPCollectionChangedType.BeforeAdd Then Dim link As CustomerToRole = New CustomerToRole(Session) link.Customer = customer link.Role = CType(args.ChangedObject, Role) customer.RoleLinks.Add(link) customer.Save() End If If args.CollectionChangedType = XPCollectionChangedType.BeforeRemove Then Dim link As CustomerToRole = Session.FindObject(GetType(CustomerToRole), _ New GroupOperator(New BinaryOperator("Customer", customer), New BinaryOperator("Role", args.ChangedObject))) If Not link Is Nothing Then customer.RoleLinks.Remove(link) customer.Save() link.Delete() End If End If End If MyBase.OnCollectionChanged(args) End Sub End Class

See Also:
How to create an empty XPCollection of a specified type
How to define a strongly typed collection property

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.