Unable to cast FieldExpression to LambdaExpression - vb.net

I've written the following function to check an added/modified record for overlapping time periods in the database:
<Extension> Function ClashRecords(Of T)(Query As IQueryable(Of T), Record As T, _
KeySelector As Expression(Of Func(Of T, Integer)),
FromSelector As Expression(Of Func(Of T, DateTime)),
TillSelector As Expression(Of Func(Of T, DateTime))) As IQueryable(Of T)
Dim key = KeySelector.Invoke(Record)
Dim fromDate = FromSelector.Invoke(Record)
Dim tillDate = TillSelector.Invoke(Record)
Dim criteriaExpr As Expression(Of Func(Of T, Boolean)) = Function(x) KeySelector.Invoke(x) = key And FromSelector.Invoke(x) <= tillDate And TillSelector.Invoke(x) >= fromDate
Return Query.AsExpandable.Where(criteriaExpr.Expand)
End Function
When calling the function as follows:
Dim de As New DataEntities()
Dim w=New Work With {.WorkerID=-1,.FromDate=New DateTime(2014,3,20,7,0,0),.TillDate=New DateTime(2014,3,20,8,30,0)}
Dim clashing = de.Works.ClashRecords(w,Function(x) x.ActivistID, Function(x) x.FromDate, Function(x) x.TillDate)
I get the following error:
InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.FieldExpression' to type 'System.Linq.Expressions.LambdaExpression'.
I don't see that I am using any field expressions -- ActivistID, FromDate, TillDate are all properties.
I am using EF5 and generated POCO classes.
How can I resolve this?
Update
If I enter criteriaExpr.Expand in the Watch window, I don't get an exception. However, if I enter criteriaExpr.Expand.Compile I get the same exception.

See: How do closures work behind the scenes? (C#)
If a variable from the outer scope is accessed from an inner function, the compiler creates a hidden type to own the variables as fields. Your criteriaExpr accesses the outer function parameters, so it contains FieldExpressions.
I took a quick look at LinqKit's source code (which you seem to be using judging by the Expand and Compile), and it appears that it doesn't support Invoke on a FieldExpression.

Related

Call a generic function passed as parameter in recursive function

My desire is to run a given function by name through AddressOf with one input parameter, e.g. Function Foo(x as Integer) As Integer. The two inputs I need into the recursive function are the function name _name As String and an object of some type t _list As t (Integer, Double, List(Of Integer), etc). The goal is to process either an element or list of elements with the function name, as there are multiple times I need to process a list by a given function and I do not wish to replicate the list processing code in each location. The ways I've tried to call my best go at this type of function (below) that didn't crash completely resulted in this error:
Warning: List.Test operation failed. Overload resolution failed because no Public 'ProcessList' can be called with these arguments:
'Public Shared Function ProcessList(Of t)(_func As Func(Of Object,t), _list As System.Object) As IEnumerable(Of t)':
Type argument inference fails for argument matching parameter '_func'.
Iterator Function ProcessList(Of t)(_func As Func(Of Object, t), _list As Object) As IEnumerable(Of t)
If _list.GetType = GetType(List(Of t)) Then
Yield _list.SelectMany(Function(l) ProcessList(_func, l))
Else
Yield _func(_list)
End If
End Function
For reference, I found a snippet of Python code that effectively does what I need, but I'm a little rusty on translating in this direction (Python to VB.net), and I'm not as familiar with this type of programming in VB.net. The Python snippet is:
def ProcessList(_func, _list):
return map(lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list)
Any help as to how I need to call this function, or how to rework this function if my approach is flawed, would be greatly appreciated!
Update:
I re-examined how I was calling the function and a few other things based on #djv's info that my method is working. First, due to the nature of how I'm interfacing with these functions, I have to expose the above function with:
Public Shared Function Foo(ByVal _input As Object) As Object
Return Utilities.ProcessList(AddressOf Bar, _input)
End Function
I'm also now getting the error message:
Warning: List.Test operation failed.
Unable to cast object of type 'System.Int32' to type 'System.Collections.Generic.IList`1[System.Int32]'.
The issue at this point probably lies with the method in which I'm calling my ProcessList function, rather than the function itself as I thought. I'm interfacing with a GUI that is not happy with calling ProcessList on its own, so I need this intermediate "helper" function, which I am apparently not using correctly.
You will always get an IEnumerable(Of T) and T can either be a primitive (i.e. Integer) or list of primitive (i.e. List(Of Integer)). So when you try to call it with a List, you get a List(Of List(Of Integer)) for example.
We can see why by breaking ProcessList up into two methods. The difference between them is the type of the second argument which is either T or IEnumerable(Of T)
Sub Main()
Dim i As Integer = 1
Dim li As New List(Of Integer) From {1, 1, 1}
Dim ri As IEnumerable(Of Integer) = ProcessList(AddressOf foo, i).ToList()
Dim rli As IEnumerable(Of Integer) = ProcessList(AddressOf foo, li).ToList()
Dim d As Double = 1.0#
Dim ld As New List(Of Double) From {1.0#, 1.0#, 1.0#}
Dim rd As IEnumerable(Of Double) = ProcessList(AddressOf foo, d).ToList()
Dim rld As IEnumerable(Of Double) = ProcessList(AddressOf foo, ld).ToList()
Console.ReadLine()
End Sub
Function ProcessList(Of T)(f As Func(Of T, T), p As IEnumerable(Of T)) As IEnumerable(Of T)
Return p.Select(Function(i) ProcessList(f, i)).SelectMany(Function(i) i)
End Function
Iterator Function ProcessList(Of T)(f As Func(Of T, T), p As T) As IEnumerable(Of T)
Yield f(p)
End Function
Function foo(param As Integer) As Integer
Return param + 1
End Function
Function foo(param As Double) As Double
Return param + 1.0#
End Function
Previously, I could not even hit the line in your original code which did the SelectMany. Now, it is hit when the proper function is called. I also retooled that call to fit the new function signature.
The overloads are both called, based on the second argument passed them. However, you only need one foo method for each T (either a primitive or its IEnumerable).

Yield with delegate

Please see the code below:
Public Iterator Function Read(Of T)(ByVal sql As String, ByVal make As Func(Of IDataReader, T), ParamArray ByVal parms() As Object) As IEnumerable(Of T)
Using connection = CreateConnection()
Using command = CreateCommand(sql, connection, parms)
Using reader = command.ExecuteReader()
Do While reader.Read()
Yield make(reader) --line 7
Loop
End Using
End Using
End Using
End Function
Private Shared Make As Func(Of IDataReader, Member) =
Function(reader) _
New Member() With {
.MemberId = Extensions.AsId(reader("MemberId")),
.Email = Extensions.AsString(reader("Email")),
.CompanyName = Extensions.AsString(reader("CompanyName")),
.City = Extensions.AsString(reader("City")),
.Country = Extensions.AsString(reader("Country"))
}
Please see line 7. Make populates an object of type Member with values from the data reader row. I have read the following documentation: http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx. The documentation does not seem to explain what happens when you use a delegate i.e. Yield make(datareader), rather than Yield return datareader. Is control passed back to the calling function as well as the delegate (Make)?
Make(reader) is a shortcut for Make.Invoke(reader). I.e., you
invoke the delegate, passing reader as a parameter, which yields a value of type Member.
Then, you return that value using Yield.
It is equivalent to :
...
Do While reader.Read()
Dim myMember As Member = make(reader)
Yield myMember
Loop
...
PS: If you get a compile-time error in your code (you don't say so in your question): This is due to the fact that your method is declared to return an IEnumerable(Of T), when in fact it returns an IEnumerable(Of Member).

Can I specify Queryable.GroupBy (instead of Enumerable.GroupBy) in query expression syntax

When I write a Group By in query expression syntax, the compiler automatically picks Enumerable.GroupBy as my intended tareget method and I get an IEnumerable back instead of an IQueryable. That means a subsequent g.Sum call (inside of my Select) expects a Func(Of TSource, int) instead of an Expression(Of Func(Of TSource, int)). Is there a way to force the Group By to use Queryable.GroupBy instead and give me back an IQueryable?
Contrived Sample Code
Dim myQuery = From x In DataSource.Items
Group By x.Key Into g = Group
Select New With {
.Key = Key,
.RedItems = g.Sum(ItemsOfColor(Colors.Red)) '<== invalid because g.Sum expects a lambda
}
Private Function PurpleItems(color As Integer) As Expression(Of Func(Of Item, Integer))
Return Function(item) If(item.Color = color, 1, 0)
End Function
Why would I want to do this?
The compiler automatically converts between a lambda and an expression based on the target variable type (ie, both Dim f As Func(Of String, Integer) = Function(x) x.Length() and Dim e As Expression(Of Func(Of String, Integer)) = Function(x) x.Length() are valid) so there is no noticable difference in the code between an IEnumerable and IQueryable.
The problem is, LINQ to Entities (and I assume other db backed LINQ implementations) relies on expression trees to translate into SQL. That means the IEnumerable lambda version will not work against an IDbSet as I found in this old question.
The problem is that Queryable.GroupBy() returns IQueryable<IGrouping<TKey, TSource>>, where IGrouping<TKey, TSource> implemens IEnumerable<TSource>, but not IQueryable<TSource>.
And I believe your code wouldn't work anyway, because ItemsOfColor() wouldn't be actually called. Instead, the EF would get an expression that calls ItemsOfColor(). And since it doesn't know that method, it would throw an exception.

Expression filter on IEnumerable

i am trying to apply an expression filter dynamically and can't get it working. Would any of you guys know, how can apply the given expression filter inside the for each loop and then return the object of Type t when it matches?
Public Function FindByCondition( _
filter As Expressions.Expression(Of Func(Of T, Boolean))) As T Implements IRepository(Of T).FindByCondition
Dim metaData As New LinqMetaData
AutoMapper.Mapper.CreateMap(GetType(EntityType), GetEntityType)
Dim dataSource = TryCast(metaData.GetQueryableForEntity(CInt([Enum].Parse(GetType(EntityType), GetEntityType.Name))), IQueryable(Of EntityBase))
Dim q = (From p In dataSource _
Select p).ToList
Dim g = AutoMapper.Mapper.Map(Of IEnumerable(Of T))(q)
For Each k As T In g
k.Equals(filter)
Next
End Function
You need to compile the expression tree to a delegate, then call the delegate on each instance.
Dim compiled As Func(Of T, Boolean) = filter.Compile()
If compiled(k) Then
Or, more simply,
Return g.FirstOrDefault(compiled)
Or, much more simply,
Return AutoMapper.Map(Of T)(dataSource.FirstOrDefault(filter))
This will actually run the filter on the server (or whatever your IQueryable implementation does).
For all of the other cases, you should accept a Func(Of T, Boolean) rather than an expression tree, since you don't actually need the expression tree. Compile() is an expensive call.

OrderBy Linq.Expression as parameter = (Of Func(Of T,IComparable)) to perform LinqToEntity is not working

I'd like to get this working:
DBSet constructor:
dbset = DataContext().[Set](Of T)()
Call: (Count & Page are used for pagination, so Count = 20 and Page = 1 for example, for the first 20 values). Sorting should be by name
LeverancierService.GetLeveranciers(Function(el) el.Name, Count, Page)
Equivalent in c#:
LeverancierService.GetLeveranciers(el=> el.Name, Count, Page)
Method that gives an error (parameters shown above):
Public Overridable Function GetAllPaged(orderby As Expression(Of Func(Of T, IComparable)), ByVal Count As Integer, ByVal Page As Integer) As IEnumerable(Of T)
Return dbset.OrderBy(orderby).Skip((Page - 1) * Count).Take(Count).ToList()
End Function
Already tried changing it to this, but it gives the same error:
Public Overridable Function GetAllPaged(Of TOrderBy)(orderby As Expression(Of Func(Of T, TOrderBy)), ByVal Count As Integer, ByVal Page As Integer) As IEnumerable(Of T)
Return dbset.OrderBy(orderby).Skip((Page - 1) * Count).Take(Count).ToList()
End Function
Error:
Unable to cast the type 'System.String' to type 'System.IComparable'. LINQ to Entities only supports casting Entity Data Model primitive types.
Any idea how to do this?
Extra info:
I'm in a DDD-layered application, so the parameter should stay the same as the called method is an overridden interface (eg. if i change this, i have to do this for 200 times or so, because it's in VB.Net and not in C# (= 1 change) )
Here is my sample. I hope it solves your problem:
If type of your dbset is not defined:
Public Function Test(Of TEntity As Class, TKey) _
(keySelector As Expression(Of Func(Of TEntity, TKey))) As IList(Of TEntity)
Return DataContext.Set(Of TEntity).OrderBy(keySelector).ToList
End Function
Usage:
Repository.Test(Function(obj As MyEntity) obj.Name)
If type of your dbset is defined, you can use this other sample:
Public Function Test2(Of TKey) _
(keySelector As Expression(Of Func(Of MyEntity, TKey))) _
As IList(Of MyEntity)
Return DataContext.Set(Of MyEntity).OrderBy(keySelector).ToList
End Function
Used like this:
Repository.Test(Function(obj) obj.Name)