Manjar
Manjar

Reputation: 3268

Linq with reflexion error: Expression LINQ 'Invoke' node is not supported in LINQ to Entities

I'm having troubles dealing with lambda expressions. I'm working with MVC under EF, and I trying to create a "MetaModel" of which every Model will inherits and with it a method to List elements with Linq.

Because this is a "MetaModel" I don't know the definition of class.

I'm trying to do this, assume that we have a Model class "Customer"

    Public Function List(searchText As String) As List(Of Customer)
        With EFContext
            Return .Customers.AsQueryable().Where((Function(Customer) Customer.Name.Contains(searchText)).ToList()
       End With
   End Function

But I want it to be generic, so I passed the property name of an inherit class and I want to be able to search (and sort) by this property.

This is my best approach:

    Public MustInherit Class BaseBL

    Public Function AutoList(Of T)(conf As ConfigurationBL) As List(Of T)
        Dim items As List(Of T)
        Dim sortParam = Expression.Parameter(GetType(T), "param")
        Dim sortFunc = Expression.Convert(Expression.Property(sortParam, conf.SortField), GetType(Object))
        Dim sortExpr = Expression.Lambda(Of Func(Of T, Object))(sortFunc, sortParam).Compile()

        Dim searchFunc As Func(Of T, Boolean) = Function(param)
                                                    Dim prInfo As PropertyInfo = param.GetType.GetProperty(conf.SearchField)
                                                    Dim str As String = prInfo.GetValue(param, Nothing)
                                                    Return str.Contains(conf.SearchText)
                                                End Function

        Dim searchExpr As Expression(Of Func(Of T, Boolean)) = Function(param) searchFunc(param)

        Dim query As IQueryable(Of T) = GetQueryable(Of T)()

        items = query.Where(searchExpr).OrderBy(sortExpr).ToList() '.Skip(conf.PageSize * (conf.PageNumber - 1)).Take(conf.PageSize).ToList()

        Return items
    End Function

    Public MustOverride Function GetQueryable(Of T)() As IQueryable(Of T)
End Class

This class is the Parent of generic class T, so the GetQueriable(Of T) method will be implemented on child classes.

ConfigurationBL class contains the sortField, searchField and searchText which will provides the proper configuration.

This almost works. I execute the orderBy with sortExpr it works, if I execute the where with searchExpr it works too.

The problem comes when I try to do all toghether. The it raises an excepction:

"Expression LINQ 'Invoke' node is not supported in LINQ to Entities"

I'm pretty sure that this is because I use

Dim searchExpr As Expression(Of Func(Of T, Boolean)) = Function(param) searchFunc(param)

Instead of a proper Expression Tree, but I dont know how to translate searchFunc to an expression tree.

I really stuck with this, I would really appreciate any help and ideas, Thank you :)

Upvotes: 2

Views: 138

Answers (1)

Manjar
Manjar

Reputation: 3268

Finally I was able to fix it myself, I replaced the searchExpr with this

Dim searchExpr = getSearchExpression(Of T)(conf.SearchField, conf.SearchText).Compile()

where...

Private Function getSearchExpression(Of T)(propName As String, search As String) As Expression(Of Func(Of T, Boolean))
        Dim element As ParameterExpression = Expression.Parameter(GetType(T), "element")
        Dim propertyValue As ParameterExpression = Expression.Variable(GetType(String), "propertyValue")
        Dim searchText As ParameterExpression = Expression.Variable(GetType(String), "searchText")

        Dim asgPropertyValue As Expression = Expression.Assign(propertyValue,
            Expression.Convert(Expression.Property(element, propName), GetType(String))
        )

        Dim asgSearchText As Expression = Expression.Assign(searchText,
           Expression.Constant(search)
           )

        Dim callContains As Expression = Expression.Call(propertyValue, GetType(String).GetMethod("Contains"), searchText)


        Dim block As Expression = Expression.Block(GetType(Boolean),
                                   New List(Of ParameterExpression)({propertyValue, searchText}),
                                   asgPropertyValue,
                                   asgSearchText,
                                   callContains
                               )      

        Return Expression.Lambda(Of Func(Of T, Boolean))(block, element)
    End Function

I hope this may help somebody, Greetings :)

Upvotes: 1

Related Questions