Greater than comparison for strings in EF - c#

First off, I don't have any controll over the database so I can't change anything there.
The field in the database is a varchar(50) and contains (for the records I'm interested in) a number. I'd like to get all of them with a number over 50. In SQL I just type:
SELECT Field FROM MyTable WHERE Field > '50'
That works fine even though I can agree the datatype should be something else.
In EF I'm trying:
query = query.Where(t => t.MyTable.Field > string);
There error is: Operator '>' cannot be applied to operands of type 'string' and 'string'

use int.Parse(str) C# is not implicitly typed - ergo you have to convert it to an integer, double etc befoe using the '>' operator. If you are not sure if the string will be a number then you can use int.tryparse(str)

Originally, I suggested the below code, which I tried and worked but not using EF entity set:
query = query.Where(t => int.TryParse(t.Mytable.Field, out number) && int.Parse(t.MyTable.Field) > int.Parse(string));
And then, after getting some good comments, I tried it on EF set, and that line itself worked, but after that line executes, I tried to so anything on the results of the query above (e.g. Count()), I got the below exception:
LINQ to Entities does not recognize the method 'Boolean TryParse(System.String, Int32 ByRef)' method, and this method cannot be translated into a store expression.

Related

LINQ to SQL query succeeds or fails based on using foreign key versus using a built in navigation property

I have a query against a SQL server database that throws an Error converting data type nvarchar to numeric. exception. I was trying to use Convert.ToDecimal on a varchar field, however I'll eyeballed the data as best I could and couldn't find an invalid value.
The query is filtering the table by a 'Group' by using the foreign key of p.pgKey=#. However, if I use the navigation property and filter by the navigation property instead, p.Group.gName='ABC' the query works.
Here are the queries (note, originally, I didn't know if error was happening in Where translation or the Select processing, so that is why query looks weird, but as you can guess, when it works, it should just return a single distinct row of true):
Profiles
.Where(p =>
p.pgKey == 237
&& !p.pPlanProfile.Value
&& Convert.ToDecimal(p.pSearch08 ?? "0") > 0
).Select(p =>
Convert.ToDecimal(p.pSearch08 ?? "0") > 0
)
.Distinct()
.Dump();
The above query fails, while this query succeeds:
Profiles
.Where(p =>
p.Groups.gName == "ABC"
&& !p.pPlanProfile.Value
&& Convert.ToDecimal(p.pSearch08 ?? "0") > 0
).Select(p =>
Convert.ToDecimal(p.pSearch08 ?? "0") > 0
)
.Distinct()
.Dump();
Below is a full LINQPad screen dump showing:
Proves the gKey for ABC is 237.
Proves that the counts of simplying counting the Profile records is identical when using pgKey or Group.gName.
Shows the query working correctly when using the Group.gName processing.
Shows the query failing when using the pgKey processing.
Obviously I've used the Group.gName method to fix my problem, but I stumbled on that solution by accident. Anyone know why LINQ to SQL would behave this way?
Note: I get the same behavior using the generated DataContext from LINQPad or if I run against a compiled .dbml DataContext.
The two queries will be generating different TSQL and hence the query plan will be different.
I would suspect that the former query is attempting to convert some values of pSearch08 to decimal before it rejects them based on the other selection criteria, whereas the latter query is performing the other selection criteria first and hence is attempting to convert a smaller number of pSearch08 values to decimal and hence does not attempt to convert the invalid value.
If this is the case, then it could be dangerous to assume that the second query will always work and it would be best to fix the invalid data.
Rather than eyeballing the data you can try
SELECT * from Profile where ISNUMERIC(pSearch08) = 0

Comparing Strings in Linq to Entity

I am storing ZIP codes in my database and trying to convert the following T-SQL to Linq to Entity:
SELECT *
FROM Regions
WHERE EndZip >= '12345'
I'm trying something like the following code:
var result = this.DbContext.Regions.Where(e => e.EndZip.CompareTo("12345") >= 0);
but I get an EntityCommandExecutionException: "The binary operator GreaterThanOrEqual is not defined for the types 'System.String' and 'System.String'."
I was able to do this if I convert my IQueryable to an IEnumerable, but I would like this query to be executed in SQL for performance reasons.
Does anyone know how I can compare strings in Linq to Entity?
There is a restricted set of Functions supported in Linq to Entities.
String comparisons are limited.
Linq to entity Docu
then see inside links
Supported Linq to Entity functions
The intro says most of it
This section provides information about the Language-Integrated Query (LINQ) standard query operators that are supported or unsupported in LINQ to Entities queries. Many of the LINQ standard query operators have an overloaded version that accepts an integer argument. The integer argument corresponds to a zero-based index in the sequence that is being operated on, an IEqualityComparer, or IComparer. Unless otherwise specified, these overloaded versions of the LINQ standard query operators are not supported, and attempting to use them will throw an exception.
While I would, in general, consider treating a ZipCode as a number (instead of as a string), being a particular heinous thing to do, in this case, it seems to work:
from e in DbContext.Regions
where Convert.ToInt32(a.EndZip) >= 12345
select e
UPDATE:
Since there will be Canadian postal code (which we'll assume we do not want in our ">= 12345" search:
from e in DbContext.Regions
where DbContext.IsNumeric(a.EndZip) && Convert.ToInt32(a.EndZip) >= 12345
select e
To get that to work, you'll need let Linq know about IsNumeric
partial class MyDataContext
{
[Function(Name = "ISNUMERIC", IsComposable = true)]
public int IsNumeric(string input)
{
throw new NotImplementedException(); // this won't get called
}
}
Now, of course, if you want to involve the Canadian codes in the search (e.g. >= 'H0H 0H0'), then you have a whole new problem, which I don't see any obvious answer to, but if you can limit yourself to segmenting just by the first character (i.e. >= '20000'), then you could do something like this:
from e in DbContext.Regions
where e.EndZip[0] >= '2'
select e;
Using string.CompareTo does work, at least with the setup I used (Linq v4.0.30319, Microsoft SQL Server Standard v12.0.5540, Entity Framework v6.1.3):
from e in DbContext.Regions
where e.EndZip.CompareTo("12345") <= 0
select e
However, in my experience, it only works if you use CompareTo with the entire field (at least, it doesn't work together with SubString).
Therefore, this method will not work with British zip codes.

LINQ issue with Equals statement

I'm getting an error for the following:
var answerList = (from answer in db.QuestionAnswers
where answer.tLoginID.Equals(tId) && answer.pLoginID.Equals(pId)
&& answer.Submitted.Equals(submittedVal)
select answer).ToList();
The error is:
Unable to create a constant value of type 'System.Object'. Only
primitive types or enumeration types are supported in this context.
However, when I change it to:
var answerList = (from answer in db.QuestionAnswers
where answer.tLoginID.Equals(tId) && answer.pLoginID.Equals(pId)
select answer).ToList();
Then I do this:
answerList.Where(x => x.Submitted.Equals(submittedVal));
It works... What am I missing? To me these statements are doing the same thing. But I'm not sure why it is working like this.
UPDATE:
I figured out after looking at the link that #SergeyLitvinov provided about .Equals that the column I was checking Submitted was actually a Boolean instead of an Integer once I made the types the same my statements worked. Although I did change it from .Equals to ==.
The error is from the LINQ to Entities query provider which is attempting to transform your query expression into a SQL statement. Your second example enumerates your LINQ to Entities query prior to testing for Submitted.Equals(submittedVal), which mean you're using standard LINQ to Objects in local memory (i.e. it is not converted to SQL).

Access database with LINQ, datatype conflict

I am trying to access data from my database with LINQ but I am running into a data type error.
Here is my code:
public static String GetCheckIfCsIsRunning()
{
using (Entities db = new Entities())
{
Stringl status = (from stat in db.Messenger_Settings
where stat.Id == 1
select stat.SettingValue);
return status;
}
}
I am currently getting an error at
where stat.Id == 1
saying
Cannot implicitly convert type 'int' to 'bool'
I am trying to select from my table the ID of the row but the line of code is saying the Id is type bool? Perhaps the 'status' variable of the code creates 'stat' into a bool type. But how would I select the row according to Id?
my table structure is
ID(int) | Name(varchar) | SettingValue(varchar)
EDIT
I forgot to add an extra '=' ('=' --> '==')
EDIT-2
replaced int's with bool's new error occurs
- data I want to access is varchar (select stat.SettingValue)
Cannot implicitly convert type 'System.LINQ.IQueryable,string.' to 'string'
You're mistakenly using an assignment operator (=) in place of an equality operator (==).
Your line
where stat.Id = 1
actually assigns 1 to stat.ID, not what you want at all; then, the assignment returns the value assigned, in this case 1. Since C# won't implicitly convert an int to bool, you get the error you see. What you want to do is instead use ==, the equality operator, which will do what you expect: check if the value of stat.Id is 1 and return true or false.
Also, for future reference, you can avoid accidental assignment errors like this by using a different programming idiom: put the constant first, i.e.
1 = stat.Id
While you'd still get an error either way here, in other cases where you do an accidental assignment, or in languages that will implicitly convert int to bool (which would be a runtime error that's hard to track down), you'll instead get an error, since you can't assign a value to a numeric literal like 1.
Per your edit, LINQ always returns a "lazy-loaded" query. As soon as you try to access any elements in that query, it enumerates it. An IQueryable is the object representation of that query. Since we know that the query represents a sequence of strings (0 or more), we can call one of a few methods against IQueryable<string> to get at the results:
.First() will return the first element in the sequence, and throw if the sequence has no elements.
.FirstOrDefault() will return the first element in the sequence, or a default value if the sequence contains no elements.
.Single() will return the element if there is only one element in the sequence, and throw otherwise (if there are zero or more than one element).
.SingleOrDefault() will return the element if there is only one element in the sequence, a default value if there are no elements, or throw if there are more than one.
Which one to choose depends on your database schema as well as your application's desired behavior. If you know there will only ever be one and exactly one value for a setting, use .Single(), so that you can error out and detect if you get several results. If there might be a value for a setting (or it might not be defined), use .SingleOrDefault(). And if a setting could have several values in the table, use .First() or .FirstOrDefault().
In any case, what you'll get is either a string or a runtime exception in the case of unexpected results.
= is an assignment operator.
You want to compare two values using the == comparison operator.
where stat.Id = 1 needs to be where stat.Id == 1
You're attempting to set the value rather than doing a comparison.
I can see two issues here.
First of all you are trying to pass a string (varchar) to an int.
Second the LINQ you have will produce something like IEnumerable or IQueryable which won't be able to go to an int.
You should probably consider getting First, FirstOrDefault, Sinle, SingleOrDefault

Subsonic SimpleRepository NullReferenceException in Contains with cast

I have a problem with this subsonic3.0.0.4 find statement:
rep = new SimpleRepository(" ... ");
rep.Find<MyObject>( x => x.Code.ToString("00000").Contains("023") );
The Code field is Long value and the sql query that I need is:
*SELECT * FROM ... WHERE convert(varchar, Code) LIKE '%023%'*
When I execute it, NullReferenceException. Then problem is the Cast to string for the LIKE filter. But i don't kwnon how to resolve it.
The stack trace:
at SubSonic.SqlGeneration.ANSISqlGenerator.GenerateConstraints()
at SubSonic.SqlGeneration.ANSISqlGenerator.BuildSelectStatement()
at SubSonic.Query.SqlQuery.BuildSqlStatement()
at SubSonic.Query.SqlQuery.GetCommand()
at SubSonic.Query.SqlQuery.ExecuteTypedListT
at SubSonic.Repository.SimpleRepository.Find[T](Expression`1 expression)
at agf.FormMain.BindGrid() in C:\dev\localhost\AGF\trunk\AGF\FormMain.cs:line 351
Thank you!!
This is a "bug" - SubSonic tries to convert your expression in the where clause to an SQL statement. SubSonic gives it's best to convert the x.Code statement to a string but is not aware - and cannot guarantee for multiple database types - that your toString("00000") will be executed successfully.
The exception could be a bit more clear.
Try to convert your value in the "Contains" statement to something like "00023" and omit the format expression in your ToString - that should be functional equivalent and will work.

Categories

Resources