How to use .Where in generic list - vb.net

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.

Related

extract list of string from list of custom class

i have a list(of custom class)
and i want to extract a list of all 'name' String, from it, through linq
I know how to do with a loop, but i need to get it with a linear, brief linq instruction.
i've checked this help
C# Extract list of fields from list of class
but i have problem in linq correct syntax
in particular because i would like to extract a New List(Of String)
Class Student
Sub New(ByVal NewName As String, ByVal NewAge As Integer)
Name = NewName
Age = NewAge
End Sub
Public Name As String
Public Age As Integer
End Class
Public Sub Main
Dim ClassRoom as New List(Of Student) From {New Student("Foo",33), New Student("Foo2",33), New Student("Foo3",22)}
Dim OneStudent as Student = ClassRoom(0)
Dim AllStudentsNames As New List(Of String) From {ClassRoom.Select(Function(x) x.Name <> OneStudent.Name).ToList}
End Sub
But something wrong...
Any help?
P.S. Since c# it's close to vb.Net, also c# helps are well welcome.
First, you don't need to create a new list From the one returned by the LINQ method. It's already in a new list at that point, so you can just set AllStudentsNames equal directly to what the ToList method returns.
Second, you are not selecting the name. You are selecting the result of the equality test to see if the names are different. In other words, when you say Select(Function(x) x.Name <> OneStudent.Name), that returns a list of booleans, where they true if the names are different and false if the names are the same. That's not what you want. You want the list of names, so you need to select the name.
Third, if you need to filter the list so that it only returns ones where the name is different, then you need to add a call to the Where method.
Dim AllStudentsNames As List(Of String) = ClassRoom.
Where(Function(x) x.Name <> OneStudent.Name).
Select(Function(x) x.Name).
ToList()

variable is referenced from scope, but not defined LINQ expression tree

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.

Defining the type of a List in vb.net at runtime

I'm creating a class at run time using typebuilder and after I create this class I want to define its type for a list like
dim fooList as new List(of DynamicClassName)
Since this doesn't exist at compile time of course it throws an error. When I generate this type I return the type so I can't do something like
dim newType = createNewType(foobar)
dim fooList as new List(of getType(newType))
How do I assign the type of a List at runtime?
You can create a List(Of T), but AFAIK you won't be able to cast it to a typed object. I've used the String type in the following example.
Dim list As Object = Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {GetType(String)}))
Debug.WriteLine((TypeOf list Is List(Of String)).ToString())
Output
True
So in your case it would look like this:
Dim newType = createNewType(foobar)
'Creates a List(Of foobar):
Dim list As IList = Ctype(Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {newType})), IList)
'Creates a BindingList(Of foobar):
Dim bindingList As IBindingList = Ctype(Activator.CreateInstance(GetType(BindingList(Of )).MakeGenericType(New Type() {newType})), IBindingList)
This does not answer your question, but may solve your problem.
Another option would be to use an ArrayList (for which you don't have to assign a type). You can see the details here: http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=vs.110).aspx.
Here is a basic example:
Dim anyArrayList as new ArrayList()
anyArrayList.add("Hello")
anyArrayList.add("Testing")

VB.Net - how can i get the type of the object contained in an List

If I have a list...
dim l as List(of MyClass) = new List(of MyClass)
and I want to get the type of the objects contained in the list, how do I do that?
The obvious answer, that doesn't seem to be possible from my actual implementation, would be to do something like this...
public function GetType(byval AList as IList(of GenericType)) as System.Type
dim lResult as system.type = nothing
if AList.Count > 0 then lResult = AList(0).GetType
return lResult
end function
But what if the list is empty and I still want to know the type it contains?
There's a good article on this at MSDN, here
Basically you can use GetGenericArguments() to get an array of the types provided as arguments to your generic type. In the case of a List, there's only one argument so you will get what you need using eg
dim l as List(of MyClass) = new List(of MyClass)
dim t as Type = (l.GetGenericArguments())(0)

How do I append a 'where' clause using VB.NET and LINQ?

I am pretty new to VB.NET and am having a bit of trouble here with something I thought should be simple.
Keeping it simple, let's say I have a Document table with "Name" that I want to search on (in reality there are several other tables, joins, etc. ..). I need to be able to build the query using a where clause based on string values passed in.
Example - the user may pass in "ABC", "ABC DEF", "ABC DEF GHI".
The final query would be (the syntax is not correct, I know):
Select * from Documents Where Name Like %ABC% AND Name Like %DEF% AND Name like %GHI%
So, I thought I could do something like this.
Dim query = From document In _context.Documents
<< loop based on number of strings passed in >>
query = query.Where( ... what goes here?? )
For some reason, being brain-dead or something, I can't figure out how to make this work in VB.NET, or if I'm doing it correctly.
I believe this is how you would do it in VB (I'm a C# developer):
query = query.Where(Function(s) s = "ABC")
See LINQ - Sample Queries for some examples.
I think the tricky part here is the unknown number of query parameters. You can use the underlying LINQ IQueryable(Of T) here to help.
I think the following would work (it's not compiled, just notepad code here):
Public Function GetDocuments(criteria as String)
Dim splitCriteria = SplitTheCriteria(criteria)
dim query = from document in _context.Documents
For Each item in splitCriteria
Dim localItem = item
query = AddCriteriaToQuery(query, localItem)
Next
dim matchingDocuments = query.ToList()
End Function
Private Function AddCriteriaToQuery(query as IQueryable(Of Document), criteria as string) as IQueryable(Of Document)
return query.Where(Function(doc) doc.Name = criteria)
End Function
Since LINQ will delay-execute the query you can append where clauses onto your query in the loop and then call .ToList() at the end to execute the query.
In LINQ to SQL you can add WHERE clauses to your query using the .Where method of the query object, as you noted in your question. To use the LIKE operator, try using the .Contains method of the object you're querying in the Lambda expression of your call to the Where method.
Here's a simplified example in a console application. Hopefully it will lead you in the correct direction.
Public Class Doc
Private _docName As String
Public Property DocName() As String
Get
Return _docName
End Get
Set(ByVal value As String)
_docName = value
End Set
End Property
Public Sub New(ByVal newDocName As String)
_docName = newDocName
End Sub
End Class
Sub Main()
Dim Documents As New List(Of Doc)
Documents.Add(New Doc("ABC"))
Documents.Add(New Doc("DEF"))
Documents.Add(New Doc("GHI"))
Documents.Add(New Doc("ABC DEF"))
Documents.Add(New Doc("DEF GHI"))
Documents.Add(New Doc("GHI LMN"))
Dim qry = From docs In Documents
qry = qry.Where(Function(d) d.DocName.Contains("GHI"))
Dim qryResults As List(Of Doc) = qry.ToList()
For Each d As Doc In qryResults
Console.WriteLine(d.DocName)
Next
End Sub
Note the .Contains("GHI") call in the Lambda expression of the .Where method. I'm referencing the parameter of the expression, "d", which exposes the DocName property, which further exposes the .Contains method. This should produce the LIKE query you're expecting.
This method is additive, i.e. the call to the .Where method could be enclosed in a loop to make additional LIKE operators added to the WHERE clause of your query.
Dim query = From document In _context.Documents where document.name = 'xpto' select document
Or
Dim query = From document In _context.Documents where document.name.contains('xpto') select document
If you do this in a loop, you can do something like this:
.Where(Function(i as mytype) i.myfiltervar = WhatIWantToSelect)