Skip to main content
.NET 6.0+

How to: Implement Cascading Filtering for Lookup List Views

  • 14 minutes to read

Most applications have Detail Views with Lookup Property Editors that need to be filtered. To make the Lookup List View’s data source of these editors dependent on values of other Property Editors located in the same Detail View, you can use DataSourcePropertyAttribute and DataSourceCriteriaAttribute.

This topic demonstrates how to use these attributes in code and in the Application Model.

Tip

A complete sample project is available in the DevExpress Code Examples database at https://supportcenter.devexpress.com/ticket/details/e218/xaf-how-to-filter-lookup-list-views.

Implement Business Classes

  1. In the YourSolutionName\Module\BusinessObjects folder, create the following classes: Order, Product, and Accessory. Replace the autogenerated code with the following class declarations:

    • YourSolutionName.Module\BusinessObjects\Order.cs

      using DevExpress.Persistent.Base;
      using DevExpress.Persistent.BaseImpl.EF;
      
      namespace YourSolutionName.Module.BusinessObjects {
         [DefaultClassOptions]
         public class Order : BaseObject {
            public virtual Product Product { get; set; }
            public virtual Accessory Accessory { get; set; }
         }
      }
      
      // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
      
    • YourSolutionName.Module\BusinessObjects\Product.cs

      using DevExpress.Persistent.Base;
      using DevExpress.Persistent.BaseImpl.EF;
      using System.Collections.ObjectModel;
      
      namespace YourSolutionName.Module.BusinessObjects {
         [DefaultClassOptions]
         public class Product : BaseObject {
            public virtual String ProductName { get; set; }
            public virtual IList<Accessory> Accessories { get; set; } = new ObservableCollection<Accessory>();
         }
      }
      
      // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
      
    • YourSolutionName.Module\BusinessObjects\Accessory.cs

      using DevExpress.Persistent.Base;
      using DevExpress.Persistent.BaseImpl.EF;
      
      namespace YourSolutionName.Module.BusinessObjects {
         [DefaultClassOptions]
         public class Accessory : BaseObject {
            public virtual String AccessoryName { get; set; }
            public virtual bool IsGlobal { get; set; }
            public virtual Product Product { get; set; }
         }
      }
      
      // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
      

      Tip

      If your application uses Entity Framework Core, register the newly created business classes in the DbContext and update the database schema. For more information, refer to the following topic: Implement a Data Model: Basics.

  2. Run the application. Create several Product objects and link each of them with an Accessory object. For some Accessory objects, select the Is Global checkbox.

  3. Open the Order Detail View and look at the contents of the Product and Accessory Lookup List Views. Both display the collections of corresponding types:

    ASP.NET Core Blazor
    XAF ASP.NET Core Blazor Unfiltered Lookup List View, DevExpress
    Windows Forms
    XAF Windows Forms Unfiltered Lookup List View, DevExpress

Scenario 1 - Populate the Lookup Property with Objects from the Specified Collection Property

This scenario shows how to make the Accessory Lookup List View in the Order Detail View display only the Accessory objects related to the selected Product.

  1. Navigate to the Accessory property of the Order class and specify DataSourcePropertyAttribute as displayed in the code snippet below:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Order : BaseObject {
          // ...
          [DataSourceProperty("Product.Accessories")]
          public virtual Accessory Accessory { get; set; }
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    

    DataSourcePropertyAttribute accepts a collection property as parameter. The property’s values become the data source for the corresponding Lookup List View. In this scenario, it is the Accessories property of the Product class instance that lies in the the Order.Product property.

  2. Run the application. Open the Order Detail View and select a Product object. Now the Accessory Lookup List View only displays the Accessory objects linked to this Product object:

    ASP.NET Core Blazor
    XAF ASP.NET Core Blazor Lookup List View Filtered By DataSourcePropertyAttribute, DevExpress
    Windows Forms
    XAF Windows Forms Lookup List View Filtered By DataSourcePropertyAttribute, DevExpress

    Tip

    Alternatively, use DataSourceProperty of the Application Model’s BOModel | Class | Member node.

    Note

    • An independent server-mode collection is created as a data source for a lookup ListView when the IModelListView.DataAccessMode option is set to Server, ServerView, InstantFeedback, or InstantFeedbackView for that ListView model. However, when the DataSourcePropertyAttribute is applied for the lookup property, the property pointed in the attribute is used as a lookup data source, and the standalone independent server-mode collection is not created and is not used at all. The reason is that the data source property getter contains logic calculated on the client side and the data source is populated by the client application at the moment when the lookup editor requests to display objects. So, the Server, ServerView, InstantFeedback, or InstantFeedbackView mode option and the DataSourceProperty attribute do not work simultaneously.

    • To apply the technique demonstrated above to Entity Framework Core objects with a Many-to-Many relationship implemented through an intermediate object, use the Map method to specify the join table, its columns names, and to apply additional configuration. For more information, refer to the Entity Framework Core - Relationships topic.

Scenario 2 - Apply Criteria to the Lookup Property Collection

This scenario shows how to make the Accessory Lookup List View in the Order Detail View display only the Accessory objects whose IsGlobal property set to true.

  1. Navigate to the Accessory property of the Order class and specify DataSourceCriteriaAttribute attribute as displayed in the following code snippet:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Order : BaseObject {
          // ...
          [DataSourceCriteria("IsGlobal = true")]
          public virtual Accessory Accessory { get; set; }
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    
  2. Run the application. Open the Order Detail View and select a Product object. Now the Accessory Lookup List View displays the Accessory objects whose IsGlobal property is set to true:

    ASP.NET Core Blazor
    XAF ASP.NET Core Blazor Lookup List View Filtered By DataSourceCriteriaAttribute, DevExpress
    Windows Forms
    XAF Windows Forms Lookup List View Filtered By DataSourceCriteriaAttribute, DevExpress

    Tip

    Alternatively, use the DataSourceCriteria property of the Application Model’s BOModel | Class | Member node.

Scenario 3 - Use Alternate Criteria if the Specified Data Source Property is Empty

The sample code below first attempts to populate the lookup with Accessory objects linked to the selected Product. If no Product is selected, the code loads Accessory objects whose IsGlobal property set to true.

  1. Navigate to the Accessory property of the Order class and specify DataSourcePropertyAttribute as described above. For details, see Scenario 1 - Populate the Lookup Property with Objects from the Specified Collection Property.

  2. Display Accessory objects with IsGlobal property set to true if no Product object is selected. Use the DataSourcePropertyIsNullMode and DataSourcePropertyIsNullCriteria parameters of DataSourcePropertyAttribute as displayed in the following code snippet:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Order : BaseObject {
          // ...
          [DataSourceProperty("Product.Accessories",
          DataSourcePropertyIsNullMode.CustomCriteria, "IsGlobal = true")]
          public virtual Accessory Accessory { get; set; }
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    
  3. Run the application. Navigate to the Order Detail View and see how the Accessories Lookup List View contents change depending on whether you select a Product object.

    ASP.NET Core Blazor
    XAF ASP.NET Core Blazor Lookup List View Filtered By DataSourcePropertyAttribute With Alternate Data Sources, DevExpress
    Windows Forms
    XAF Windows Forms Lookup List View Filtered By DataSourcePropertyAttribute With Alternate Data Sources, DevExpress

    The DataSourcePropertyIsNullMode parameter of DataSourcePropertyAttribute also has the SelectAll and SelectNothing values. They do not require that you specify the DataSourcePropertyIsNullCriteria parameter.

    Tip

    Alternatively, you can specify the DataSourceProperty, DataSourcePropertyIsNullMode, and DataSourcePropertyIsNullCriteria properties of the Application Model’s BOModel | Class | Member node.

Scenario 4 - Populate the Lookup Property Manually

This scenario shows how to populate the Accessory Lookup List View in the following conditions:

  • If an IncludeGlobalAccessories property is set to true, the Accessory Lookup List View data source includes the Accessory objects linked to the selected Product and the Accessory objects whose IsGlobal property is set to true. Otherwise, the data source includes only Accessory objects linked to the selected Product.
  1. Navigate to the Order class and replace its declaration with the following code snippet that introduces an additional collection for the Accessory property. This collection is refreshed every time you change the Product or IncludeGlobalAccessories property’s value.

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations.Schema;
    
    public class Order : BaseObject {
       public virtual int OrderId { get; set; }
       public virtual Product Product { get; set; }
       // Specify that AvailableAccessories must be used as a data source property,
       // and the Product and IncludeGlobalAccessories properties are used in calculations.
       [DataSourceProperty(nameof(AvailableAccessories), nameof(Product), nameof(IncludeGlobalAccessories))]
       public virtual Accessory Accessory { get; set; }
    
       [NotMapped, Browsable(false)] // Prohibits showing the AvailableAccessories collection separately 
       public virtual IList<Accessory> AvailableAccessories {
          get {
             IQueryable<Accessory> available;
             if (Product == null) {
                // Show only Global Accessories when the Product is not specified 
                available = ObjectSpace.GetObjectsQuery<Accessory>().Where(t => t.IsGlobal == true);
             }
             else {
                // Leave only the current Product's Accessories in the availableAccessories collection 
                if (IncludeGlobalAccessories == false) {
                   available = ObjectSpace.GetObjectsQuery<Accessory>().Where(t => t.Product == Product);
                }
                else {
                   available = ObjectSpace.GetObjectsQuery<Accessory>().Where(t => t.Product == Product || t.IsGlobal == true);
                }
             }
             return available.ToList();
          }
       }
       public virtual bool IncludeGlobalAccessories { get; set; }
    }
    

    Note that the above code samples use the DataSourcePropertyAttribute.UsedProperties setting to specify that the Product and IncludeGlobalAccessories properties are used in the data source property calculations so that the Accessory Lookup List View items are automatically updated when these properties change their values.

  2. Run the application. Navigate to the Order Detail View and see how the the Accessories Lookup List View contents change depending on whether you set the IncludeGlobalAccessories property to true.

ASP.NET Core Blazor
XAF ASP.NET Core Blazor Manually Populated Lookup List View Data Sources, DevExpress
Windows Forms
XAF Windows Forms Manually Populated Lookup List View Data Sources, DevExpress

Note

This technique is helpful when you cannot write the required criteria as a string to pass it as an attribute’s parameter. In code, you can use any criteria operator and operands (including Function Criteria Operators).

Scenario 5 - Filter the Lookup Property Collection Based on the Current Object Properties

This scenario modifies the Order Detail View to add an Alternative Offer Lookup. This lookup displays all Order objects with the matching Product, except the edited object.

  1. Navigate to the Order class and add a new property to the class declaration as displayed in the following code sample:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Order : BaseObject {
          // ...      
          public virtual Order AlternativeOffer { get; set; }
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    
  2. Navigate to the Product class and add the Orders collection property as displayed in the following code snippet where it establishes a One-to-Many relationship between the Order and Product classes:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Product : BaseObject {
          // ...
          public virtual IList<Order> Orders { get; set; } = new ObservableCollection<Order>();
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    

    Tip

    In an XPO-based project, remember to add AssociationAttribute with the same unique association name to the Product property.

  3. Navigate to the Order class. To filter the Lookup List View, add DataSourcePropertyAttribute and DataSourceCriteriaAttribute to the AlternativeOffer property as displayed in the following code snippet. Use the Current Object Parameter (@This) to access the edited object in the criteria passed to DataSourceCriteriaAttribute.

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace YourSolutionName.Module.BusinessObjects {
       [DefaultClassOptions]
       public class Order : BaseObject {
          // ...
          [DataSourceProperty("Product.Orders", DataSourcePropertyIsNullMode.SelectAll)]
          [DataSourceCriteria("ID != '@This.ID'")]
          public virtual Order AlternativeOffer { get; set; }
       }
    }
    
    // Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
    
  4. Run the application. Create several Order objects for each Product object. Open an Order Detail View to show the contents of the AlternativeOffer Lookup List View:

    ASP.NET Core Blazor
    XAF ASP.NET Core Blazor Lookup List View Filtered By Current Object Property, DevExpress
    Windows Forms
    XAF Windows Forms Lookup List View Filtered By Current Object Property, DevExpress

    Note

    This code snippet is not included in the linked How to Filter Lookup List Views example. See the following sources for more examples of this technique:

    • The_%PUBLIC%\Documents\DevExpress Demos 23.2\Components\XAF\MainDemo\CS\MainDemo.Module\BusinessObjects\Contact.cs_ file of the Main Demo solution shipped with XAF.
    • The Current Object Parameter topic.
See Also