This is a migrated thread and some comments may be shown as answers.

Collection Filter Lambda Expression

2 Answers 127 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
John Foley
Top achievements
Rank 1
John Foley asked on 02 Mar 2015, 05:54 AM
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 Answers, 1 is accepted

Sort by
0
Accepted
Maya
Telerik team
answered on 02 Mar 2015, 09:12 AM
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.

 
0
John Foley
Top achievements
Rank 1
answered on 05 Mar 2015, 05:46 PM
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
Tags
General Discussions
Asked by
John Foley
Top achievements
Rank 1
Answers by
Maya
Telerik team
John Foley
Top achievements
Rank 1
Share this question
or