Collection Filter Lambda Expression

3 posts, 1 answers
  1. John Foley
    John Foley avatar
    4 posts
    Member since:
    Jul 2012

    Posted 01 Mar 2015 Link to this post

    I'm currently implementing a complex filter for handling a column bound to a collection item, and I'm having trouble creating a lambda expression that will work with the Telerik filters. 

    The collection item I am binding to is itself a Collection, and also has 2 properties- DistrictNo and ParentDistrictNo. The lambda expression I am creating works out to 

    {item => ((item.StoreTeams.Item[212].Employees.ParentDistrictNo == 0) And (item.StoreTeams.Item[212].Employees.DistrictNo == 0))}
    where "item" is the object bound to the row (SCTMStore). This compiles fine, but when it gets to adding my custom filter descriptor to the gridview's filterdescriptor's collection, I get this error (at the end of the message)

    Is there any reason why a lambda expression can't be handled by the telerik filter components? 

    Here's the CreateFilterExpression I'm overriding to create the expression (the code in question is in the "unassigned" if block)-

    Public Function CreateFilterExpression(instance As System.Linq.Expressions.Expression) As Expression Implements Telerik.Windows.Data.IFilterDescriptor.CreateFilterExpression
    ' Need to build the linq expression by each individual dereference
    Dim propertyName = Me.m_column.DataMemberBinding.Path.Path.Substring(0, Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("["))
    Dim iIndex = CInt(Me.m_column.DataMemberBinding.Path.Path.Substring(Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("[") + 1, Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("]") - (Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("[") + 1)))
    Dim GenericContainsMethod As MethodInfo = GetMethodInfo(Me.FilterOperator, "test")
    Dim GenericAnyMethod As MethodInfo = GetAnyMethodInfo(Me.FilterOperator, Me.Value)

    ' item.StoreTeams
    Dim collectionPropertyAccessor = Expression.Property(instance, propertyName)
    ' item.StoreTeams.Item(x)
    Dim ItemcollectionPropertyAccessor = Expression.Property(collectionPropertyAccessor, "Item", Expression.Constant(iIndex))
    ' item.StoreTeams.Item(x).Employees
    Dim EmployeeNosPropertyAccessor = Expression.Property(ItemcollectionPropertyAccessor, "Employees")

    If Me.Value = "unassigned" Then
    Dim collectionPropertyAccessor2 = Expression.Property(instance, propertyName)
    Dim ItemcollectionPropertyAccessor2 = Expression.Property(collectionPropertyAccessor2, "Item", Expression.Constant(iIndex))
    Dim EmployeeNosPropertyAccessor2 = Expression.Property(ItemcollectionPropertyAccessor2, "Employees")
    Dim ParentDistrictNoAccessor As Expression = Expression.Property(EmployeeNosPropertyAccessor2, "ParentDistrictNo")
    Dim DistrictNoAccessor As Expression = Expression.Property(EmployeeNosPropertyAccessor2, "DistrictNo")
    Dim ParentDistrictNoComparer As Expression = Expression.Equal(ParentDistrictNoAccessor, Expression.Constant(0))
    Dim DistrictNoComparer As Expression = Expression.Equal(DistrictNoAccessor, Expression.Constant(0))
    Dim AndComparer As Expression = Expression.And(ParentDistrictNoComparer, DistrictNoComparer)
    Dim returnresult As Expression = Expression.Lambda(Of Func(Of BusinessObjects.SCTMStore, Boolean))(AndComparer, DirectCast(instance, ParameterExpression))

    Return returnresult
    End If
    ' item.StoreTeams.Item(x).Employees.Items
    Dim ItemsPropertyAccessor = Expression.Property(EmployeeNosPropertyAccessor, "Items")
    If Me.Value = "unstaffed" Then
    ' item.StoreTeams.Item(x).Employees.Items.Any()
    Dim testresult As Expression = Expression.Call(GenericAnyMethod, ItemsPropertyAccessor)
    ' !item.StoreTeams.Item(x).Employees.Items.Any()
    Return Expression.[Not](testresult)
    End If
    ' item.StoreTeams.items.Item(x).Employees.Items.Cast<object>().Contains("the value")
    Dim genericCollectionPropertyAccessor = Expression.Call(Nothing, EnumerableCastMethod.MakeGenericMethod(GetType(String)), ItemsPropertyAccessor)


    Dim result As Expression = Expression.Call(GenericContainsMethod, genericCollectionPropertyAccessor, Expression.Constant(Me.Value))

    If Me.FilterOperator = FilterOperator.DoesNotContain Then
    '!item.StoreTeams.Item(x).Employees.Items.Cast<object>().Contains("the value")
    result = Expression.[Not](result)
    End If

    Return result
    End Function


    - Jay
    ===================================================================================
    System.ArgumentException occurred
      Message=The value "((RAMWare.Controls.ContactManagement.CollectionFilterDescriptor))" is not of type "Telerik.Windows.Data.IFilterDescriptor" and cannot be used in this generic collection.
    Parameter name: value
      StackTrace:
           at System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value, Type targetType)
           at System.Collections.ObjectModel.Collection`1.System.Collections.IList.Insert(Int32 index, Object value)
           at Telerik.Windows.Data.CollectionHelper.Insert(IList target, IEnumerable newItems, Int32 startingIndex, IEqualityComparer itemComparer)
           at Telerik.Windows.Data.ObservableCollectionManager.HandleCollectionChanged(IList sender, NotifyCollectionChangedEventArgs args)
           at Telerik.Windows.Data.ObservableCollectionManager.Telerik.Windows.Data.IWeakEventListener<System.Collections.Specialized.NotifyCollectionChangedEventArgs>.ReceiveWeakEvent(Object sender, NotifyCollectionChangedEventArgs args)
           at Telerik.Windows.Data.WeakEvent.WeakListener`1.Handler(Object sender, TArgs args)
           at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
           at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
           at Telerik.Windows.Data.RadObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
           at Telerik.Windows.Data.ObservableItemCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
           at System.Collections.ObjectModel.ObservableCollection`1.InsertItem(Int32 index, T item)
           at Telerik.Windows.Data.RadObservableCollection`1.InsertItem(Int32 index, T item)
           at Telerik.Windows.Data.FilterDescriptorCollection.InsertItem(Int32 index, IFilterDescriptor item)
           at System.Collections.ObjectModel.Collection`1.Add(T item)
           at RAMWare.Controls.ContactManagement.EmployeeCollectionFilterControl.PART_DistinctValuesList_SelectionChanged(Object sender, SelectionChangedEventArgs e)
      InnerException: 

    ===================================================================================
  2. Answer
    Maya
    Admin
    Maya avatar
    4062 posts

    Posted 02 Mar 2015 Link to this post

    Hello John,

    Usually, such exception can be observed when the item you are trying to insert in the collection is not IFilterDescriptor. But based on the files you sent, it seems that it is. However, since I am not able to debug it and can only see the files, I am not able to guess any further. Could you try working with the project from this blog post or the one from this example and verify that you get the same exception ? 



    Regards,
    Maya
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
  3. DevCraft banner
  4. John Foley
    John Foley avatar
    4 posts
    Member since:
    Jul 2012

    Posted 05 Mar 2015 in reply to Maya Link to this post

    I followed the example and creating my own functions to handle my logic worked out just fine. It's not the neatest or probably the most correct, but here's the code i ended up creating. Thanks very much

    Private Shared ReadOnly UnassignedMethodInfo As MethodInfo = GetType(CollectionFilterDescriptor).GetMethod("Unassigned")
    Private Shared ReadOnly UnStaffedMethodInfo As MethodInfo = GetType(CollectionFilterDescriptor).GetMethod("UnStaffed")
    Private Shared ReadOnly IsDeadMethodInfo As MethodInfo = GetType(CollectionFilterDescriptor).GetMethod("IsDead")

    Public Function CreateFilterExpression(instance As System.Linq.Expressions.Expression) As Expression Implements Telerik.Windows.Data.IFilterDescriptor.CreateFilterExpression
    ' Need to build the linq expression by each individual dereference
    Dim propertyName = Me.m_column.DataMemberBinding.Path.Path.Substring(0, Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("["))
    Dim iIndex = CInt(Me.m_column.DataMemberBinding.Path.Path.Substring(Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("[") + 1, Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("]") - (Me.m_column.DataMemberBinding.Path.Path.ToString.IndexOf("[") + 1)))
    Dim GenericContainsMethod As MethodInfo = GetMethodInfo(Me.FilterOperator, "test")
    Dim GenericAnyMethod As MethodInfo = GetAnyMethodInfo(Me.FilterOperator, Me.Value)

    Me.CreateDelegates(instance, propertyName, iIndex)

    ' item.StoreTeams
    Dim collectionPropertyAccessor = Expression.Property(instance, propertyName)
    ' item.StoreTeams.Item(x)
    Dim ItemcollectionPropertyAccessor = Expression.Property(collectionPropertyAccessor, "Item", Expression.Constant(iIndex))
    ' item.StoreTeams.Item(x).Employees
    Dim EmployeeNosPropertyAccessor = Expression.Property(ItemcollectionPropertyAccessor, "Employees")
    ' item
    Dim parameter As ParameterExpression = DirectCast(instance, ParameterExpression)

    ' Me
    Dim thisExpression As ConstantExpression = Expression.Constant(Me, GetType(CollectionFilterDescriptor))
    If Me.Value = "unassigned" Then
    ' Me.unassigned(SCTMStore)
    Dim filteringExpression As MethodCallExpression = Expression.[Call](thisExpression, UnassignedMethodInfo, parameter)

    If filteringExpression IsNot Nothing Then
    Return filteringExpression
    End If
    ElseIf Me.Value = "unstaffed" Then

    ' this.unstaffed(SCTMStore)
    Dim filteringExpression As MethodCallExpression = Expression.[Call](thisExpression, UnStaffedMethodInfo, parameter)

    If filteringExpression IsNot Nothing Then
    Return filteringExpression
    End If
    ElseIf Me.Value = " " Then
    ' this.isDead(SCTMStore)
    Dim filteringExpression As MethodCallExpression = Expression.[Call](thisExpression, IsDeadMethodInfo, parameter)

    If filteringExpression IsNot Nothing Then
    Return filteringExpression
    End If
    End If

    ' item.StoreTeams.Item(x).Employees.Items
    Dim ItemsPropertyAccessor = Expression.Property(EmployeeNosPropertyAccessor, "Items")
    ' item.StoreTeams.items.Item(x).Employees.Items.Cast<object>().Contains("the value")
    Dim genericCollectionPropertyAccessor = Expression.Call(Nothing, EnumerableCastMethod.MakeGenericMethod(GetType(String)), ItemsPropertyAccessor)


    Dim result As Expression = Expression.Call(GenericContainsMethod, genericCollectionPropertyAccessor, Expression.Constant(Me.Value))

    If Me.FilterOperator = FilterOperator.DoesNotContain Then
    '!item.StoreTeams.Item(x).Employees.Items.Cast<object>().Contains("the value")
    result = Expression.[Not](result)
    End If

    Return result
    End Function

    Private Sub CreateDelegates(ByRef instance As Expression, ByVal propertyName As String, ByVal iIndex As Integer)

    Dim itemType As Type = Me.Column.ItemType
    Dim storeteamstype As Type = GetType(RAMWare.BusinessObjects.SCTMStoreTeam)
    ' WorkingDays property

    Dim oparameterexpression As ParameterExpression = DirectCast(instance, ParameterExpression)
    'item.StoreTeams
    Dim collectionPropertyAccessor2 = Expression.Property(instance, propertyName)
    'item.StoreTeams.items[xx]
    Dim ItemcollectionPropertyAccessor2 = Expression.Property(collectionPropertyAccessor2, "Item", Expression.Constant(iIndex))
    ' employee parameter for the memeber access to work

    ' now get DistrictNO
    Dim propertyInfo As PropertyInfo = storeteamstype.GetProperty("DistrictNo")
    ' (item) => item.storeteams.DistrictNo
    Dim getMemberAccessExpression As MemberExpression = Expression.MakeMemberAccess(ItemcollectionPropertyAccessor2, propertyInfo)
    Dim getDistrictNoPropertyLambda As LambdaExpression = Expression.Lambda(getMemberAccessExpression, oparameterexpression)
    ' now get ParentDistrictNo
    propertyInfo = storeteamstype.GetProperty("ParentDistrictNo")
    ' (item) => item.storeteams.ParentDistrictNo
    getMemberAccessExpression = Expression.MakeMemberAccess(ItemcollectionPropertyAccessor2, propertyInfo)
    Dim getParentDistrictNoPropertyLambda As LambdaExpression = Expression.Lambda(getMemberAccessExpression, oparameterexpression)
    ' now get employees
    propertyInfo = storeteamstype.GetProperty("Employees")
    ' (item) => item.storeteams.Employees
    getMemberAccessExpression = Expression.MakeMemberAccess(ItemcollectionPropertyAccessor2, propertyInfo)
    ' (item) => item.storeteams

    Dim getEmployeePropertyLambda As LambdaExpression = Expression.Lambda(getMemberAccessExpression, oparameterexpression)
    ' This is a method that we will invoke in order to read the value of the enum property of each item,
    ' Create delegates for functions to access properties
    Me.getParentDistrictNopropertyValueDelegate = getParentDistrictNoPropertyLambda.Compile()
    Me.getDistrictNopropertyValueDelegate = getDistrictNoPropertyLambda.Compile()
    Me.getEmployeespropertyValueDelegate = getEmployeePropertyLambda.Compile()


    End Sub

    Public Function UnStaffed(item As Object) As Boolean

    Dim DistrictNo As Integer = CInt(Me.getDistrictNopropertyValueDelegate.DynamicInvoke(item))
    Dim Employees As RAMWare.BusinessObjects.SCTMDistrictEmployeeCollection = Me.getEmployeespropertyValueDelegate.DynamicInvoke(item)
    If DistrictNo > 0 And Employees.Items.Count = 0 Then
    Return True
    End If

    Return False
    End Function

    Public Function Unassigned(item As Object) As Boolean

    Dim ParentDistrictNo As Integer = CInt(Me.getParentDistrictNopropertyValueDelegate.DynamicInvoke(item))
    Dim DistrictNo As Integer = CInt(Me.getDistrictNopropertyValueDelegate.DynamicInvoke(item))
    Dim Employees As RAMWare.BusinessObjects.SCTMDistrictEmployeeCollection = Me.getEmployeespropertyValueDelegate.DynamicInvoke(item)
    If ParentDistrictNo > 0 And DistrictNo = 0 AndAlso Employees.Items.Count = 0 Then
    Return True
    End If

    Return False
    End Function


    Public Function IsDead(item As Object) As Boolean

    Dim DistrictNo As Integer = CInt(Me.getDistrictNopropertyValueDelegate.DynamicInvoke(item))
    Dim ParentDistrictNo As Integer = CInt(Me.getParentDistrictNopropertyValueDelegate.DynamicInvoke(item))
    Dim Employees As RAMWare.BusinessObjects.SCTMDistrictEmployeeCollection = Me.getEmployeespropertyValueDelegate.DynamicInvoke(item)
    If ParentDistrictNo = 0 And DistrictNo = 0 And Employees.Items.Count = 0 Then
    Return True
    End If

    Return False
    End Function
Back to Top