VB.NET Linq to Entities Query with child objects - vb.net

Basically I have the follwing:
Dim ctx As New AdminCoreEntities
Dim roles = (From r In ctx.Roles where r.Name.StartsWith("cust") Select r) 'list of System.Linq.IQueryable(Of AdminCoreModel.Role)
Dim items = From i In ctx.QuickLinks.Include("Roles")
Where (i.TenantID = "470556ba-3574-4b01-a619-b85e9721b966" AndAlso i.Roles.Contains(roles))
Select New With {
i.ID,
i.Name,
.Roles = (From r In i.Roles Select New With {.Id = r.ID, .Name = r.Name})
}
The error i get when i run this is:
Unable to cast object of type System.Data.Objects.ObjectQuery`1[AdminCoreModel.Role] to type AdminCoreModel.Role
Basically I have a many to many situation and I try to get all the Quicklinks objects queried by their roles
and not quite sure why EF will cast to a single AdminCoreModel.Role when i.Roles is a collections of objects.
Any help greatly appreciated

Well, .Contains() expects one Role, and you're passing it a list. That's what the error says.
Try (apologies in advance if I mangle the VB.NET syntax; I don't usually write VB.NET):
Dim ctx As New AdminCoreEntities
Dim items = From i In ctx.QuickLinks ' You don't need Include because you're projecting.
Where (i.TenantID = "470556ba-3574-4b01-a619-b85e9721b966"
AndAlso i.Roles.Any(Function(role) role.Name.StartsWith("cust"))
Select New With {
i.ID,
i.Name,
.Roles = (From r In i.Roles Select New With {.Id = r.ID, .Name = r.Name})
}

Related

VB net & Mongo: Using where clause with LINQ causes error "Unsupported where clause: (Boolean)Operators.CompareObjectLess"

I have a collection in MongoDB and I'm using MongoDB Driver in VB net. I want to update several documents depending on a condition.
For this, I want to use LINQ, but the select causes an error and I don't know how to fix it.
Here's the code:
Dim update_for As UpdateBuilder
Dim query_for As IMongoQuery
Dim coll_for = db.GetCollection(Of MyClass)("collection_1")
Dim queryMun = (From a In coll_for _
Where (a.field_1 < 10000) _
Select a)
For Each emp In queryMun
query_for = Query.EQ("_id", emp.Id)
update_for = Update.Set("field_1", BsonValue.Create("0" + emp.field_1))
coll.Update(query_for, update_for, opts)
Next
When it executes de For Each sentence, it raises the exception: Unsupported where clause: (Boolean)Operators.CompareObjectLess(a.field_1, 10000, true).
What am I doing wrong?
Thank you very much for your help.
I think the error is clear:
You can't use a Less Than "<" operator in you WHERE clause because it's unsupported.
I have found a way to do this update based on the value of the attribute itself. What I want to do is add a "0" at the beginning of the attribute value, for example, if field_1=4567, after the update field_1='04567'.
Here's the code:
Dim update_for As UpdateBuilder
Dim query_for As IMongoQuery
Dim opts = New MongoUpdateOptions
opts.Flags = UpdateFlags.Multi
Dim coll_for = db.GetCollection(Of MyLINQClass)("collection_1")
Dim queryMun2 As New QueryDocument
Dim query_1 = Query.LT("field_1", MongoDB.Bson.BsonValue.Create(10000))
queryMun2.AddRange(query_1.ToBsonDocument)
Dim queryMun = coll_for.Find(queryMun2)
For Each emp In queryMun
query_for = Query.EQ("_id", emp.Id)
update_for = Update.Set("field_1", BsonValue.Create("0" + emp.FField_1.ToString))
coll.Update(query_for, update_for, opts)
Next
And here is the definition of MyLINQClass:
Public Class MyLINQClass
<BsonElementAttribute("_id")> _
Public Property Id() As ObjectId
<BsonElementAttribute("field_1")> _
Public Property FField_1() As Object
End Class

error in LINQ query using two contexts on Constant Value Type

I am getting the following error from the query below;
Unable to create a constant value of type 'tradedata.symbol'. Only primitive types or enumeration types are supported in this context.
Note: The query is broken up in two as I cannot query two seperate contexts and I cant join symbolIdList to the projections list in LINQ as its an object in memeory so I put projList in memory so both objects would be in memory.
Public Function FillRangeProjDropdown(ByRef ddl As DropDownList, includeEquities As Boolean) As DropDownList
Dim requestedDate As Date = MySqlDate(DateTime.Now.Date)
Dim symbolTypeList As New List(Of Integer)(New Integer() {17, 18, 19})
Dim symbolIdList As List(Of symbol)
Using symbCtx As New SymbolsEntities()
If Not includeEquities Then
symbolIdList = (From d In symbCtx.symbols
Where symbolTypeList.Contains(d.SymbolType)
Select d).ToList()
Else
symbolIdList = (From d In symbCtx.symbols
Where d.SymbolType = 20
Select d).ToList()
End If
End Using
Using projCtx As New ProjectionsEntities()
Dim dvpList As New List(Of DataValuePair)
Dim projList As List(Of projection) = (From d In projCtx.projections
Where d.Date = requestedDate
Select d).ToList()
'symbolIdList is a collection of objects in memory and you cannot join a set of data in the database with another set of data that is in memory.
dvpList = (From d In projList
Join e In symbolIdList On e.Id Equals d.SymbolId
Select New DataValuePair() With {
.Text = e.Name,
.Value = e.Id}).Distinct().OrderBy(Function(o) o.Text).ToList()
For x As Integer = 1 To dvpList.Count
ddl.Items.Add(New ListItem(dvpList(x - 1).Text, dvpList(x - 1).Value))
Next
ddl.Items.Insert(0, New ListItem("Select a Commodity", 0))
End Using
Return ddl
End Function
It's probably this part:
Where symbolTypeList.Contains(d.SymbolType)
I don't know about the latest versions of Entity Framework, but versions up to and including 4.0 are notoriously bad at handling Contains clauses. I've had a similar problem myself. If you only have 3 values to compare it to, try writing it out explicitly:
Where d.SymbolType = 17
Or d.SymbolType = 18
Or d.SymbolType = 19
I don't see anything else in your code that's likely to cause the error message you posted.
Since you need to have both context's variables in memory because you CANNOT query a database object when the other variable is in memory, in the current iteration, .Distinct() is not necessary, therefore removing .Distinct() solves the issue.
Updated code
Public Function FillRangeProjDropdown(ByRef ddl As DropDownList, includeEquities As Boolean) As DropDownList
Dim requestedDate As Date = MySqlDate(DateTime.Now.Date)
Dim symbolTypeList As New List(Of Integer)(New Integer() {17, 18, 19})
Dim symbolIdList As List(Of symbol)
Using symbCtx As New SymbolsEntities()
If Not includeEquities Then
symbolIdList = (From d In symbCtx.symbols
Where symbolTypeList.Contains(d.SymbolType)
Select d).ToList()
Else
symbolIdList = (From d In symbCtx.symbols
Where d.SymbolType = 20
Select d).ToList()
End If
End Using
Using projCtx As New ProjectionsEntities()
Dim projList As List(Of projection) = (From d In projCtx.projections
Where d.Date = requestedDate AndAlso d.ProjectionTypeId = 1
Select d).ToList()
' Since symbolIdList is a collection of objects in memory and you cannot join a set of data
' in the database with another set of data that is in memory, projList is set up in memory as well
Dim dvpList As List(Of DataValuePair) = (From d In projList
Join e In symbolIdList On e.Id Equals d.SymbolId
Select New DataValuePair() With {
.Text = e.Name,
.Value = e.Id}).OrderBy(Function(o) o.Text).ToList()
'no .Distinct() needed here as we are doing that when the two objects in memory are created
For x As Integer = 1 To dvpList.Count
ddl.Items.Add(New ListItem(dvpList(x - 1).Text, dvpList(x - 1).Value))
Next
ddl.Items.Insert(0, New ListItem("Select a Commodity", 0))
End Using
Return ddl
End Function

LINQ to XML - select to a strongly typed object (VB)

This is not a big issue but it's bugging me. I know how to select into a strongly typed collection (List(Of T), but I can't find a tidy way of doing this for an object that is not a List.
This works:
Dim x = From a In response...<artist> _
Select New MBArtistInfo With {.MBID = a.#id, .Name = a.<name>.Value, .Gender = a.<gender>.Value}
Return x(0)
but it is annoying to have to do that.
I have seen a C# solution elsewhere along the lines of:
var x = from a In response...<artist>
select new MBArtistInfo
{
MBID = etc
but I can't convert this to VB.
Has anyone done this?
Just skip class name:
Dim x = From a In response...<artist> _
Select New With { .MBID = a.#id, .Name = a.<name>.Value, .Gender = a.<gender>.Value }
But it will give you IEnumerable(Of T), where T is an anonymous type. You should not return objects like that from your methods:
For example, an anonymous type cannot be used to define a method
signature, to declare another variable or field, or in any type
declaration. As a result, anonymous types are not appropriate when you
have to share information across methods.
Edit
To get only one element from collection use First/FirstOrDefault methods:
Dim x = (From a In response...<artist> _
Select New With { .MBID = a.#id, .Name = a.<name>.Value }).FirstOrDefault();

Dynamic query Linq to xml VB.NET

Hey,
I want to write a query that the "where" in the query is a string something like"
Dim query as string= "Name =xxxx and Date > 10 "
Dim t = from book in doc.Descendants("books") Select _
[Name] = book..value, [Date] = book..value....
Where (query)
I build the query string on run time
Thanks...
I'm not saying this is your case but I see this a lot from people that came from ASP classic where we used to build dynamic SQL strings all of the time. We tend to hope that LINQ will give us some more power in part of the code but let us use strings elsewhere. Unfortunately this isn't true. Where takes a boolean argument and there's no way around that. You can write your own parser that uses reflection and eventually returns a boolean but you'd be writing a lot of code that could be error prone. Here's how you really should do it:
Assuming this is our data class:
Public Class TestObject
Public Property Name As String
Public Property Job As String
End Class
And here's our test data:
Dim Objects As New List(Of TestObject)
Objects.Add(New TestObject() With {.Name = "A", .Job = "Baker"})
Objects.Add(New TestObject() With {.Name = "B", .Job = "President"})
Objects.Add(New TestObject() With {.Name = "C", .Job = "Bus Driver"})
Objects.Add(New TestObject() With {.Name = "D", .Job = "Trainer"})
What you want to do is create a variable that represents the data to search for:
''//This variable simulates our choice. Normally we would be parsing the querystring, form data, XML values, etc
Dim RandNum = New Random().Next(0, 3)
Dim LookForName As String = Nothing
Select Case RandNum
Case 0 : LookForName = "A"
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
''//Query based on our name
Dim Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
If sometimes you need to search on Job and sometimes and sometimes you don't you just might have to write a couple of queries:
Dim Subset As List(Of TestObject)
Select Case RandNum
Case 0
Subset = (From O In Objects Select O Where (O.Name = "A" And O.Job = "Baker")).ToList()
Case Else
Select Case RandNum
Case 1 : LookForName = "B"
Case 2 : LookForName = "C"
End Select
Subset = (From O In Objects Select O Where (O.Name = LookForName)).ToList()
End Select
And just to explain writing your own query parser (which is a path that I recommend you DO NOT go down), here is a very, very, very rough start. It only supports = and only strings and can break at multiple points.
Public Shared Function QueryParser(ByVal obj As Object, ByVal ParamArray queries() As String) As Boolean
''//Sanity check
If obj Is Nothing Then Throw New ArgumentNullException("obj")
If (queries Is Nothing) OrElse (queries.Count = 0) Then Throw New ArgumentNullException("queries")
''//Array of property/value
Dim NameValue() As String
''//Loop through each query
For Each Q In queries
''//Remove whitespace around equals sign
Q = System.Text.RegularExpressions.Regex.Replace(Q, "\s+=\s+", "=")
''//Break the query into two parts.
''//NOTE: this only supports the equal sign right now
NameValue = Q.Split("="c)
''//NOTE: if either part of the query also contains an equal sign then this exception will be thrown
If NameValue.Length <> 2 Then Throw New ArgumentException("Queries must be in the format X=Y")
''//Grab the property by name
Dim P = obj.GetType().GetProperty(NameValue(0))
''//Make sure it exists
If P Is Nothing Then Throw New ApplicationException(String.Format("Cannot find property {0}", NameValue(0)))
''//We only support strings right now
If Not P.PropertyType Is GetType(String) Then Throw New ApplicationException("Only string property types are support")
''//Get the value of the property for the supplied object
Dim V = P.GetValue(obj, Nothing)
''//Assumming null never equals null return false for a null value
If V Is Nothing Then Return False
''//Compare the two strings, return false if something doesn't match.
''//You could use String.Compare here, too, but this will use the current Option Compare rules
If V.ToString() <> NameValue(1) Then Return False
Next
''//The above didn't fail so return true
Return True
End Function
This code would allow you to write:
Dim Subset = (From O In Objects Select O Where (QueryParser(O, "Name = A", "Job = Baker"))).ToList()
No, there is nothing directly like what you're looking for where you can pass in a string. As they say, when all you have is a hammer, everything looks like a nail...The real problem is that you need to learn what LINQ is good at and apply it to your code (if it is a good fit), rather than try and make it do what you could with a dynamically built SQL query string.
What you should probably be doing is making those "Where" clauses strongly typed anyway. Your current code has a lot of potential to blow up and be hard to debug.
What you could do instead is something like this (sorry, using C#, been a while since I've touched VB.NET):
var query = from book in doc.Descendants("books")
select book;
if(needsNameComparison)
{
query = query.where(book.Name == nameToCompare);
}
if(needsDateComparison)
{
query = query.Where(book.Date > 10);
}
List<book> bookList = query.ToList();
With LINQ, "query" isn't actually run until the "ToList()" call. Since it uses late execution, the query is dynamic in that it's being built on until it actually needs to run. This is similar to the code you were looking to use before since you were building a query string ahead of time, then executing it at a specific point.

how to group by over anonymous type with vb.net linq to object

I'm trying to write a linq to object query in vb.net, here is the c# version of what I'm trying to achieve (I'm running this in linqpad):
void Main()
{
var items = GetArray(
new {a="a",b="a",c=1}
, new {a="a",b="a",c=2}
, new {a="a",b="b",c=1}
);
(
from i in items
group i by new {i.a, i.b} into g
let p = new{ k = g, v = g.Sum((i)=>i.c)}
where p.v > 1
select p
).Dump();
}
// because vb.net doesn't support anonymous type array initializer, it will ease the translation
T[] GetArray<T>(params T[] values){
return values;
}
I'm having hard time with either the group by syntax which is not the same (vb require 'identifier = expression' at some places, as well as with the summing functor with 'expression required' )
Thanks so much for your help!
You can create an array of type Object in VB.NET that can contain objects of any type, including anonymous types. The compiler will correctly infer that the same anonymous type is to be used provided you keep the same field name, types, and order of fields.
I ran this code in LinqPad to get the results you are looking
Dim items As Object() = { _
New With {.a="a",.b="a",.c=1}, _
New With {.a="a",.b="a",.c=2}, _
New With {.a="a",.b="b",.c=1} _
}
Dim myQuery = From i In items _
Group By i.a, i.b into g = Group _
let p = New With { .k = g, .v = g.Sum(Function(i) i.c)} _
where p.v > 1 _
select p
myQuery.Dump