DateTime comparison in ObjectQuery.Where - vb.net

I'm using Entity Framework, and I have a COMMENT entity. A COMMENT has a DATEMODIFIED property, which is a Nullable Date. I'm trying to build a query that will filter COMMENTs by date, so I create a startDate object, and do the following:
Dim q As ObjectQuery(Of COMMENT) = _
(From c In model.COMMENT Select c)
If startDate.HasValue Then
q = q.Where(Function(c) startDate.Value <= c.DATEMODIFIED)
End If
The problem is that q.toList() is not returning any comments, even though I think it should. All comments in the database have DATEMODIFIED values, and even if I pass in DateTime.MinValue as the startDate, the query still doesn't match any entities.
I set a breakpoint before the If-Statement and used the Visual Studio Watch Window to try and see what's going on:
q.ToList()(0).DATEMODIFIED 'Returns the expected date
startDate.Value 'Returns the expected date
startDate.Value <= q.ToList()(0).DATEMODIFIED 'Returns True...
But once once it hits the q = q.Where(predicate) part, q.ToList() no longer returns any entries. I'm stumped.

UPDATE: Oops, I forgot that, with LINQ to Entities, all WHERE expressions are translated into SQL calls instead of being post-processed in code-- so the debugging suggestions below won't necessarily work.
Therefore, I'd start by running the same generated SQL statement against your database, and validating whether the SQL generated by your Entity Framework provider is actually returning the data you expect. #Craig Stuntz's comment above is definitely on the right track here to help you do this. Once you have the parameterized SQL, I'd try executing that SQL directly from your code (using System.Data.OracleClient) and validating that you actually get results back from that query. Remember to inject the same parameter values that you get from ObjectQuery.Parameters. Alternatively, you could stick the parameters in yourself and execute the query from your Oracle client app of choice.
If you don't get results from that SQL, then it's possible that devArt's provider is building the query incorrectly.
you can ignore what's below here, since it applies to troubleshooting LINQ-to-Objects but not LINQ-to-Entities
Some ideas to diagnose this:
first, try this in your watch window:
q.Where(Function(c) startDate.Value <= c.DATEMODIFIED).Count()
I'm assuming this will return zero, but it's worth eliminating as many other variables to make sure you're really not getting any results.
Next, I'd try is to define your LINQ query a bit differently-- instead of appending the Where() separately, try using two queries, like this:
Dim q As ObjectQuery(Of COMMENT)
If startDate.HasValue Then
q = (From c In model.COMMENT Where startDate.Value <= c.DATEMODIFIED Select c)
Else
q = (From c In model.COMMENT Select c)
End If
If this works, then there's something wrong with how the Where clause is being attached to your existing LINQ query-- perhaps a bug in your DBMS's entity-framework provider?
If that still doesn't work, the next step I'd take to diagnose would be to verify that the code inside the where clause is being called, and checking the values passed into that code. I couldn't figure out how to set intra-line breakpoints in VB like one can do in C#, but you can easily (temporarily) refactor your lambda into a separate function and set the breakpoint there. Like this:
Sub Main()
Dim testDate As Date = New Date(2005, 1, 1)
Dim x = New List(Of Date?)
x.Add(New Date(2009, 1, 1))
x.Add(New Date(2008, 1, 1))
x.Add(New Date(2007, 1, 1))
x.Add(New Date(2006, 1, 1))
x.Add(New Date(2005, 1, 1))
x.Add(New Date(2004, 1, 1))
x.Add(New Date(2003, 1, 1))
x.Add(New Date(2002, 1, 1))
x.Add(New Date(2001, 1, 1))
Dim y = From n In x Select n
y = y.Where(Function(val) test(val, testDate))
Dim z = y.ToArray()
End Sub
Function test(ByVal date1 As Date, ByVal date2 As Date) As Boolean
test = date1 >= date2
End Function
Check the values being sent into your comparison function-- are they valid? Does the comparison return what you expect it to?

Related

LINQ Select Syntax VB.NET

I have a list of Tuples I am trying to run a Select and Where query on to return a list of Objects from the Tuple.Item5 parameter. In my where clause I am looking to match Tuple.Item4 to a local variable.
I'm not sure what the VB.NET syntax is for the Select portion, I only know the c# syntax.
Essentially I am trying to select Tuple.Item5 from my list of tuples where Tuple.Item4 = sCurID. I'm unsure as to what should go in the Select section although in c# I believe it would be Select(t => t.Item5)
This is what I have:
listObj = listTuples.Select( Unsure What Goes Here ).Where(Function(w) w.Item4 = sCurID)
Once you apply the Select in C# or VB, you have reduced the Tuple to the Item5 value and can't access Item4. Do the Select last:
Dim listObj = listTuples.Where(Function(t) t.Item4 = sCurId).Select(Function(t) t.Item5)
listObj = listTuples.Select(Function(t) t.Item5).Where(Function(w) w.Item4 = sCurID).ToList()

How do I extract a value from a Linq IQueryable object?

I am new to Linq and have been stumped for several days. I have searched this and several other boards to find the answer but cannot. I have a Linq query that is returning 1 value of int.
Public Function myV()
Dim MR As New MainEntities()
Dim mV = From AspNetUsers In MR.AspNetUsers
Where Context.User.Identity.Name() = AspNetUsers.Email.ToString Select AspNetUsers.VB
Return mV
End Function
If myV = 1 Then
perform other code
End If
But when I try to compare to an Int it says the object cannot be compared. I have tried to convert the IQueryable(of Interger) to Int and cannot. How do I get to the Interger value in the IQueryable object?
The LINQ statement returns an IEnumerable, which is like an array in that it can hold multiple values, even it only happens to have one value in it.
The best way to return a single value is .First(), or .Single() if you want to throw an exception if more than one value exists. (Which one you choose depends on your context.)
Public Function myV()
Dim MR As New MainEntities()
Dim mV = From AspNetUsers In MR.AspNetUsers
Where Context.User.Identity.Name() = AspNetUsers.Email.ToString Select AspNetUsers.VB.First()
Return mV
End Function
Because mV is an IQueryable, you can consider it as a type of list or an array. So you need to get the first value:
If myV.First() = 1 Then
'...
End If
Or more likely you want to do that inside the mV function:
Dim mV = From AspNetUsers In MR.AspNetUsers
Where Context.User.Identity.Name() = AspNetUsers.Email.ToString
Select AspNetUsers.VB
Return mV.First()
You misunderstood the query. Your LINQ executes a SQL Statement that is the follows:
SELECT AspNetUsers.VB
FROM AspNetUsers
WHERE ApsNetUsers.Email = "here the name"
Therefore the query does not return only one VB but a table consisting of one column and multiple rows.
In order to retrieve the first value you have to call .First()
If myV.First() = 1 Then [...]

Using contains in Linq query produces error

I have a pretty straight forward query that is producing this error at runtime: Only arguments that can be evaluated on the client are supported for the String.Contains method.
The query is supposed to find only the categories that have transfers assigned to them. The transfers can be listed in several categories so there is no table relationship. The Categoryidhash contains data like "7~34~25~42~47". I just realized while writing this that searching for '7' will return multiple results, "7" & '47' Etc. Thats ok i'll just change id's to all double digits. meanwhile...
How can I fix this?
Private Function GetCategoryList() As List(Of Category)
Dim lst As List(Of Category) = New List(Of Category)
Using db As New IPCDataDataContext
lst = (From c In db.Categories
From t In db.Transfers
Where t.CategoryIDhash.Contains(c.ID.ToString)
Select c).ToList()
Return lst
End Using
End Function
The exception means that Contains only accepts arguments that can be converted to fixed variable in SQL. So something like Where t.CategoryIDhash.Contains(someVariable.ToString) would be possible, because someVariable.ToString can be evaluated client-side.
I don't really understand this restriction, because in SQL it is perfectly possible to use a LIKE clause with a string that is built in the SQL statement itself. This is demonstrated by the statement that fixes your problem:
lst = (From c In db.Categories
From t In db.Transfers
Where SqlMethods.Like(t.CategoryIDhash, "%" + c.ID.ToString "%")
Select c).ToList()
This generates (and executes) SQL like
...
WHERE [t1].[CategoryIDhash] LIKE (#p0 + (CONVERT(NVarChar,[t0].[ID]))) + #p1
(where #p0 and #p1 are the % characters.
Although you could do this, I wonder if you're on the right track by using this CategoryIDhash. I think you should convert it to a FK relationship (if it's in your hands to modify the database).

How can I produce a join on a substring and an integer in Entity Framework?

Using Entity Framework, I'm trying to join two tables like this.
...
join f in ent.FTypes on Int32.Parse(c.CourseID[0].ToString()) equals f.FTypeID
...
The first character of the string CourseID is a digit, and FTypeID is an int.
This doesn't seem to work though. The exception message I get is:
LINQ to Entities does not recognize the method 'Int32 Parse(System.String)' method, and this method cannot be translated into a store expression."} System.Exception {System.NotSupportedException}
What I want to replicate is the SQL string equivalent (which works fine):
join FType f on SUBSTRING(c.CourseID, 1, 1) = f.FTypeID
Does anyone have the solution to have to do this in LINQ to Entities?
This is a rather nasty join, but I did some testing in Entity Framework with similar data and arrived at something you can test on yours.
It uses string.Substring to grab the first character from your string operand, and then uses a combination of the EF-only method SqlFunctions.StringConvert (these methods are found in System.Data.Objects.SqlClient) with a cast to double1 and finally a string.Trim2 for your integer operand.
I have tested this and confirmed that all functions are supported at least in EF 4. Several other methods proposed or featured in the question do not work, because Entity Framework does not know how to tranlsate them to the appropriate SQL equivalent.
join f in ent.FTypes
on c.CourseID.Substring(0, 1)
equals SqlFunctions.StringConvert((double)f.FTypeID).Trim()
It produces a SQL join that looks like the following:
INNER JOIN [dbo].[FTypes] AS [Extent2]
ON ((SUBSTRING([Extent1].[CourseID], 0 + 1, 1)) = (LTRIM(RTRIM(STR( CAST( [Extent2].[FTypeID] AS float))))))
OR ((SUBSTRING([Extent1].[CourseID], 0 + 1, 1) IS NULL) AND (LTRIM(RTRIM(STR( CAST( [Extent2].[FTypeID] AS float)))) IS NULL))
So based on that, you might want to do some additional filtering as necessary.
Give it a shot and see if that helps solve the problem.
1 The cast to double is necessary because SqlFunctions.StringConvert does not have an overload for integer and there is no other single best match, so I force one.
2 The resultant string needs to be trimmed because the string conversion generates some excess padding.
I'm not sure this worked 8 years ago, but modern EF6 implementation allows you to add anonymous types, so you can add the conversion to your initial select statements, and then you can just compare that new properties directly in the join.
from c in ent.Courses
select new { typeId = c.CourseId.Substring(0, 1), c }
join f in ent.FTypes.Select(t => new { stringId =
t.FTypeId.ToString(), t }
on c.typeId equals f.stringId into ...
NOTE: The join-statement does not appear to support ToString() but the select-statements do.
You could also move the CouresId.Substring into the join statement, but that may run less efficiently there.
Results in SQL like this:
INNER JOIN [ent].[FTypes] AS [Extent2] ON
(LTRIM(RTRIM(SUBSTRING([Extent1].[nvarchar(max)], 3 + 1, 1)))) =
CAST([Extent2].[Id] AS nvarchar(max))

Operator '=' is not defined for types 'Integer' and 'IQueryable(Of Integer)'

This is giving me a headache. I have this link query here that grabs an ID
Dim mclassID = From x In db.SchoolClasses Where x.VisitDateID = _visitdateID Select x.ClassID
And then later on I have this linq query
ViewData("Staff") = From t In db.Staffs Where t.ClassID = mclassID Select t
Any help would be much appreciated. I've tried quite a few things but to no avail. I've attempted casting, converting, Is operand, etc.
The problem is that myClassID is an anonymous IQueryable. You need to force it into another type (List is my favorite), and then pull it out of that type. So if you were to select it into a List(Of Integer) you could then extract the First() one since it would be the only. You could try something like this:
Dim myClassIDList As List(Of Integer) = New List(Of Integer)( _
From x In db.SchoolClasses Where x.VisitDateID = _visitdateID Select x.ClassID)
Dim myClassID as Integer = myClassIDList.First()
Just a guess, but would it help to wrap the right-side of the equation in parenthesis? I.e.:
ViewData("Staff") = (From t In db.Staffs Where t.ClassID = mclassID Select t)
Using the Select operator, you can have multiple results returned. If you are only expecting one result (i.e. you are selecting by a primary key), then you can use Single or SingleOrDefault (depending on whether there is guaranteed to be a result) to get just that one.
Dim mclassID = (From x In db.SchoolClasses _
Where x.VisitDateID = _visitdateID _
Select x.ClassID).SingleOrDefault()
You should change this
Dim mclassID = From x In db.SchoolClasses Where x.VisitDateID = _visitdateID Select x.ClassID
to select a single instance or the first instance, otherwise it does as its reporting, returning an IEnumerable, which causes your error later.
Or you could change your second statement to something like
ViewData("Staff") = From t In db.Staffs Where mclassID.Contains(t.ClassID) Select t
taking advantage of the mclassID as an IEnumerable of int.
You can see the errors in Output if you set appropriate options: -
Navigate VS2010 menus to Tools/Options/Projects and solutions/Build and Run/
Set MSBuild Project build output verbosity to "Detailed"
I had a similar error that wasn't showing in the Error list, but with this options setting I saw: -
error BC30452: Operator '=' is not defined for types 'System.Nullable(Of Integer)' and 'Integer'.
from this statement: -
If tqGDBChart.UserIsGroupAdmin(Userid, Groupid) = 0 Then 'User is not a group admin for this group
Easily fixed it with an intermediate variable.
Dim GroupAdminCount As Integer = tqGDBChart.UserIsGroupAdmin(Userid, Groupid)
If GroupAdminCount = 0 Then 'User is not a group admin for this group
have you tried:
ViewData("Staff") = From t In db.Staffs Where t.ClassID.equals(mclassID) Select t