variable is referenced from scope, but not defined LINQ expression tree - vb.net

I'm trying to get this LINQ expression:
Result = Result.Where(Function(Row) _WhereExpressions(0).InElements.Contains(Convert.ToString(Row(0))))
I have this code for it:
convertMethod = GetType(System.Convert).GetMethod("ToString", New Type() {GetType(Object)})
containsMethod = GetType(System.Collections.Generic.List(Of String)).GetMethod("Contains", New Type() {GetType(String)})
Dim listParameter = Expression.Parameter(GetType(List(Of String)), "_WhereExpressions(0).InElements")
expr = Expression.Call(whereMethod, Result.AsQueryable.Expression,
Expression.Lambda(Expression.Call(listParameter, containsMethod,
Expression.Call(convertMethod, Expression.ArrayAccess(rowParameter, Expression.Constant(index)))), rowParameter))
I get the desired expression, but if I compile, I get the error:
variable '_WhereExpressions(0).InElements' of type 'System.Collections.Generic.List`1[System.String]' referenced from scope '', but it is not defined
The _WhereExpressions(0).InElements is of course declared.
How can I fix it?
Thanks.
EDIT: here are all the declarations:
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Where").MakeGenericMethod(GetType(Object()))
Dim convertMethod As MethodInfo = Nothing
Dim containsMethod As MethodInfo = Nothing
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
The _WhereExpressions(0).InElements is a simple list of string, like this here:
Dim idlist As New List(Of String)
idlist.Add("1")
idlist.Add("2")
I read the linked post, but I can't really figure out, how I should solve my problem.
Expression trees have a lot capability, but looks a bit difficult for me.
EDIT2:
This is an example, what exactly I would like to achieve. Just copy and paste in vs:
Dim dt As New DataTable
dt.Columns.Add("f1", Type.GetType("System.String"))
dt.Columns.Add("f2", Type.GetType("System.Int32"))
For i = 0 To 100
dt.Rows.Add(i.ToString, i * 2)
Next
Dim indexes As New List(Of Integer)
indexes.Add(0)
indexes.Add(1)
Dim lst As New List(Of String)
lst.Add("10")
lst.Add("11")
Dim datarows As New List(Of DataRow)
For i = 0 To dt.Rows.Count - 1
datarows.Add(dt.Rows(i))
Next
Dim result As IEnumerable(Of Object())
result = datarows.Select(Function(row) indexes.Select(Function(index) row(index)).ToArray)
'I would like this as an expression:
result = result.Where(Function(row) lst.Contains(Convert.ToString(row(0))))
EDIT3: I got it:
Dim lst As Expression = Expression.Constant(list, GetType(System.Collections.Generic.List(Of String)))

It is hard to replicate code without knowing the full variables. I think your mistake though is in your understanding of Expression.Parameter. This is mainly used as a way to pass explicit parameters into the lambda: In your example, Row is a good example for when Expression.Parameter should be used. _WhereExpressions isn't an explicit parameter, it is a variable that I assume is in scope that you want to create a closure around.
You should also note that the second variable of the Expression.Parameter method is optional, and for debug purposes only: If you changed it to say: Expression.Parameter(GetType(List(Of String)), "Nonsense.nonsense"), you would probably see the error message change accordingly.
It sounds like you're trying to introduce a closure around _WhereExpressions. Using raw expression trees, this is fairly hard to do. The closest thing to an easy way to do this is to wrap Expression.Constant around _WhereExpressions.InElements. However, you're going to run into trouble if you're executing the compiled expression when _WhereExpressions is out of scope. See Issue with closure variable capture in c# expression.

Related

Public member 'Where' on type 'PlcBlockUserGroupComposition' not found [duplicate]

Using VB.NET, I'm trying to clean up a code base following ReSharper's guidelines. I currently have the following code:
'oSearchInput is defined outside this question
Dim oSearchRoutines As New SearchClient
Dim oSearchResults As List(Of SearchResult)
oSearchRoutines = 'WcfCallThatReturnsSearchClient
oSearchResults = oSearchRoutines.getSearchResults(oSearchInput).ToList
Now this works completely fine, but ReSharper warns that As New SearchClient has 'Value assigned is not used in any execution path'. So I removed that part to get this code:
'oSearchInput is defined outside this question
Dim oSearchRoutines
Dim oSearchResults As List(Of SearchResult)
oSearchRoutines = 'WcfCallThatReturnsSearchClient
oSearchResults = oSearchRoutines.getSearchResults(oSearchInput).ToList
If I'm understanding this correctly, everything should work exactly the same. However, an error is thrown when calling ToList:
Public member 'ToList' on type 'SearchResult()' not found.
I'm not exactly sure why there's any difference between the two snippets I have here.
Because you're not assinging the type SearchClient in your second example, oSearchRoutines will automatically be of type Object.
An expression of type Object is mainly not allowed to use Extension methods, like for example the ToList-method. For further information see here
The following example illustrates this behaviour:
Dim x As Object
Dim y As String = "ABC"
x = y
Dim a As List(Of Char) = y.ToList() 'This will work
Dim b As List(Of Char) = x.ToList() 'This will throw a System.MissingMemberException
The message Value assigned is not used in any execution path, appears because you're declaring oSearchRoutines with New in your first example.
This is unnecessary because you're assinging a new value to it on the line...
oSearchRoutines = 'WcfCallThatReturnsSearchClient
...before using it anywhere.
Thus you can just declare it without the keyword New
Dim oSearchRoutines As SearchClient
Related question: VB.NET: impossible to use Extension method on System.Object instance

Reflection Optimization

I've recently implemented reflection to replace the more tedious aspects of our data retrieval from a SQL database. The old code would look something like this:
_dr = _cmd.ExecuteReader (_dr is the SQLDataReader)
While _dr.Read (_row is a class object with public properties)
_row.Property1 = Convert.ToInt16(_dr("Prop1"))
_row.Property2 = Convert.ToInt16(_dr("Prop2"))
_row.Property3 = Convert.ToInt16(_dr("Prop3"))
If IsDBNull(_dr("Prop4")) = False Then _row.Prop4 = _dr("Prop4")
...
Since my code base has a lot of functionality like this, reflection seemed like a good bet to simplify it and make future coding easier. How to assign datareader data into generic List ( of T ) has a great answer that was practically like magic for my needs and easily translated into VB. For easy reference:
Public Shared Function GenericGet(Of T As {Class, New})(ByVal reader As SqlDataReader, ByVal typeString As String)
'Dim results As New List(Of T)()
Dim results As Object
If typeString = "List" Then
results = New List(Of T)()
End If
Dim type As Type = GetType(T)
Try
If reader.Read() Then
' at least one row: resolve the properties
Dim props As PropertyInfo() = New PropertyInfo(reader.FieldCount - 1) {}
For i As Integer = 0 To props.Length - 1
Dim prop = type.GetProperty(reader.GetName(i), BindingFlags.Instance Or BindingFlags.[Public])
If prop IsNot Nothing AndAlso prop.CanWrite Then
props(i) = prop
End If
Next
Do
Dim obj = New T()
For i As Integer = 0 To props.Length - 1
Dim prop = props(i)
If prop Is Nothing Then
Continue For
End If
' not mapped
Dim val As Object = If(reader.IsDBNull(i), Nothing, reader(i))
If val IsNot Nothing Then SetValue(obj, prop, val)
Next
If typeString = "List" Then
results.Add(obj)
Else
results = obj
End If
Loop While reader.Read()
End If
Catch ex As Exception
Helpers.LogMessage("Error: " + ex.Message + ". Stacktrace: " + ex.StackTrace)
End Try
Return results
End Function
The only caveat with this is that it is somewhat slower.
My question is how to optimize. Sample code I find online is all in C# and does not convert neatly into VB. Scenario 4 here seems like exactly what I want, but converting it to VB gives all kinds of errors (Using CodeFusion or converter.Telerik.com).
Has anyone done this in VB before? Or can anyone translate what's in that last link?
Any help's appreciated.
Couple ideas for you.
Don't use the DataReader when reading ALL records at once, it is slower than using a DataAdapter.
When you use the DataAdapter to fill a DataSet, you can iterate through the rows and columns which does NOT use reflection and will be much faster.
I have a program I created (and many other programmers do this too) that generate the code from a database for me. Each table and row is a class that is specifically named an generated in such a way that I can use intellisense and prevents many run-time errors by making them compile-time errors when data changes. This is very much like the EntityFramework but lighter because it fits MY specific needs.

ArgumentException calling Expression.IfThenElse

I'm trying to build this LINQ query:
Result = Result.Where(Function(Row) If(IsDBNull(Row(7)), False, Convert.ToInt32(Row(7)) > 10))
Result is a IEnumerable(Of Object()).
I manage to build the expression with this code, but at the last line, I get an error message.
The code I have is this:
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).First(Function(m) m.Name = "Where").MakeGenericMethod(GetType(Object()))
Dim convertMethod As MethodInfo = Nothing
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim isdbnullMethod As MethodInfo = GetType(System.Convert).GetMethod("IsDBNull", New Type() {GetType(Object)})
Dim expr As Expression = Nothing
Dim tempexpr As Expressions.LambdaExpression = Nothing
convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
tempexpr = Expression.Lambda(Expression.IfThenElse(
Expression.Call(isdbnullMethod,
Expression.ArrayAccess(rowParameter, Expression.Constant(7))),
Expression.Constant(False),
Expression.GreaterThan(
Expression.Call(
convertMethod,
Expression.ArrayAccess(rowParameter, Expression.Constant(7))),
Expression.Constant(10))),
rowParameter)
Then I call:
expr = Expression.Call(whereMethod, Result.AsQueryable.Expression, Expression.Lambda(tempexpr.Body, rowParameter))
And at this line I get this error:
What can be the problem? Without the IfThenElse it works. Also this:
Result = Result.Where(Function(Row) Convert.ToInt32(Row(7)) > 10)
EDIT
Is this because the If operator is an "Action" method and doesn't returns a value?
Btw. the Expression.IfThenElse uses the IIf function. How could I use the If function?
EDIT II
I think, I found it: Expression.Condition. It uses IIf too, but with this, I don't get an exception.
Your Edit II is correct: Expression.IfThenElse returns void, making the whole expression an Action. Expression.Condition returns whatever the type is in the ifTrue parameter, making your expression Expression(Of Func(Of Boolean)), which is what you want.
As an aside, I don't believe it's really calling the IIf function. That's simply a debug view of what is going on. I don't think it's really calling either of those VB.NET-only methods

expression.call value cannot be null

I'm trying to code this LINQ query with expression trees:
Result = Result.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10)
Result is declared as Dim Result As IEnumerable(Of Object()).
I have this code so far:
Dim queryiabledata As IQueryable(Of Object()) = Result.AsQueryable
Dim pe As ParameterExpression = Expression.Parameter(GetType(String), "Row(2)")
Dim left As expression = Expression.Call(pe, GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes))
Dim right As Expression = Expression.Constant(10)
Dim e1 As Expression = Expression.LessThanOrEqual(left, right)
Dim predicatebody As Expression = e1
Dim wherecallexpression As MethodCallExpression = Expression.Call(
GetType(Queryable), "Where", New Type() {queryiabledata.ElementType}, queryiabledata.Expression,
Expression.Lambda(Of Func(Of Object(), Boolean))(predicatebody, New ParameterExpression() {pe}))
Result = queryiabledata.Provider.CreateQuery(Of Object())(wherecallexpression)
But if I run the query, I get an ArgumentNullException (Value cannot be null. Parameter name: method) at Expression.Call.
I tried to change "Convert.ToInt32" to "Value", but I got the same error.
How can I fix it?
Are the another code lines right to get the desired result?
I'm more used to C#, though I do VB.NET occasionally. Reflection in VB.NET is downright ugly. Getting the Where method is a bit of a hack. Here's the code:
shortForm and longForm should be identical.
Dim result As IEnumerable(Of Object()) = New List(Of Object())()
Dim queryiabledata As IQueryable(Of Object()) = result.AsQueryable()
Dim shortForm As Expression = queryiabledata.Where(Function(Row) Convert.ToInt32(Row(2)) <= 10).Expression
Dim whereMethod = GetType(Queryable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
First(Function(m) m.Name = "Where").
MakeGenericMethod(GetType(Object()))
Dim convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
Dim rowParameter = Expression.Parameter(GetType(Object()), "Row")
Dim longform As Expression =
Expression.Call(
whereMethod,
queryiabledata.Expression,
Expression.Lambda(
Expression.LessThanOrEqual(
Expression.Call(
convertMethod,
Expression.ArrayAccess(
rowParameter,
Expression.Constant(2)
)
),
Expression.Constant(10)
),
rowParameter
)
)
result = queryiabledata.Provider.CreateQuery(longform)
This line seems suspect to me:
GetType(String).GetMethod("Convert.ToInt32", System.Type.EmptyTypes)
GetType(String) returns the runtime type String. You then try to get a method called "Convert.ToInt32" which doesn't exist on the string type. I suspect that is returning null which is the source of your exception.
Perhaps you need to use something like this:
GetType(Convert).GetMethod("ToInt32", new Type() {GetType(Object)})
Since there are multiple overloads of the ToInt32 method of the Convert class, you need to specify which of the overloads you want by providing an array of Type as the second parameter. In other words, you a saying "give me the overload that takes an Object type as it's parameter".

How to use .Where in generic list

I have a List(Of MyType) and I would like to use LINQ to get a subset of the list.
On MyType there's a field called AccountNumber. Can I use LINQ to say soemthing like this?
Dim t As List(Of MyType)
t = GetMyTypes()
t = t.Where(AccountNumber = "123")
Thanks
You're almost there. The argument of Where needs to be a function, so your code should look like this:
Dim t As List(Of MyType)
t = GetMyTypes()
Dim result = t.Where(Function(x) x.AccountNumber = "123")
Alternatively, you can use the verbose LINQ syntax:
Dim result = From t In GetMyTypes() Where t.AccountNumber = "123"
The data type returned is not a List(Of MyType) but an IEnumerable(Of MyType), so you cannot directly assign it to a variable declared as List(Of MyType) . If you want to create a list, you can "convert" it by using result.ToList(). This would also cause the list to be evaluated immediately.