Comparing Strings in Linq to Entity - c#

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.

Related

Use a NaturalSortComparer in a LINQ Where Clause

Say I have a table Table1 with a string field [ProductString] with values:
Alpha, alphanumeric or numeric: eg ABC, B4, U2, C 5, 100, U1, U5, U6, U11
I want to be able to take a where clause like "ProductString >= U5", and pass this to a LINQ statement as a string so it evaluates
Table1.Where(t=> t.ProductString >= 'U5');
Normally this would return results U5 and U6.
However, this I want to be able to use a NaturalSortComparer somehow so that the results returned are U5, U6 and U11.
I know how to use the comparer in an OrderBy, by I wanted to be able to use it at the Where stage.
Using natural sort comparer:
var comparer = new NaturalComparer();
Table1.Where(t=>
comparer.Compare(t.ProductString, "U5") >= 0);
Presuming all your product strings is on the format U%number% then why not abuse that fact?
Table1.Where(t=> int.Parse(t.ProductString.Replace("U","")) >= 5);
If you're using LINQ to Entities I'm not certain this will compile to a store expression (i.e that SQL knows what to do with this - I guess it should).
I'm a little confused, given the accepted answer, about whether this question relates to LINQ to Entities or not. The accepted answer doesn't appear to be a solution that would work in the LINQ to Entities context, but the comments on the question by the OP seem to confirm that this is being executed in the database context. Anyway, this answer is specifically targeted toward LINQ to Entities.
I think doing this in SQL Server would be hard, but not impossible. The problem is that .NET knows what NaturalSortComparer is, but SQL Server (where you want the query to ultimately take place) has no such concept. The best idea I can think of would consist of 2 parts:
Create a UDF (User Defined Function) in SQL server that will give a product that is orderable via natural sort: CREATE FUNCTION Naturalize(#val as nvarchar(max)) RETURNS nvarchar(1000). There's a pretty cool answer here that creates a UDF wrapper around a CLR function to accomplish just that.
Next create a function mapping for your DbContext that maps the UDF above to a function that can be called inside an EF query against the DbContext. Something like this:
[DbFunction("MyContext", "Naturalize")]
public static string Naturalize(this string value)
{
throw new NotSupportedException("This function can only be invoked from LINQ to Entities.");
}
Once you've got these two pieces in place, you can readily use this new function inside an entity query to compare strings using the Naturalized value in the comparison:
Table1.Where(t=> t.ProductString.Naturalize() >= "U5".Naturalize());
Bear in mind that the UDF will be executed against every row contained in the query, which is the whole table in the above example. You'll want to make sure to pare down your query to something manageable before applying the function as a sub-query. Or you may want to try applying some type of UDF-based index on the table in question.
If you are going to be doing searches like this a lot, then what will be the best thing to do is add two new fields to your table, [ProductCode] & [ProductNumber] which separate the two parts of the [ProductString].
Then you comparison becomes:
Table1.Where(t=> t.ProductCode == "U" && t.ProductNumer > 5);

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).

Selecting Consecutive String Entries with LINQ to Entities

At first you might think this is duplicate of this question but hopefully you will see it is not.
I also want to select groups of rows that are consecutive but consider that this time the entries are telephone numbers, therefore, stored as string.
I have been trying somethink like:
var numbers = await (from a in context.Telephones
from b in context.Telephones
Convert.ToInt32(a.Number) < Convert.ToInt32(b.Number) &&
Convert.ToInt32(b.Number) < (Convert.ToInt32(a.Number) + numberQuantity)
group b by new { a.Number }
into myGroup
where myGroup.Count() + 1 == numberQuantity
select myGroup.Key.Number).ToListAsync();
But this fails with:
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
I understand that LINQ to Entities does not support Convert.ToInt32 but I am running out of ideas here to make it work.
So if my database has:
2063717608
2063717609
2063717610
2063717611
2063717613
2063717614
How can I select consecutive rows based on the string values? And when querying for 3 consecutive numbers get results like:
From 2063717608 to 2063717610
From 2063717609 to 2063717611
1- If you are aware of performance side effect of calling AsEnumerable() cast your query and do conversion in memory on the retrieved entities.
2- If you don't want solution #1, you have to look for a way to solve the conversion problem:
2-1- Either change the column type in the database to int
2-2- Or select one of the solution previously proposed by other developers such as:
Problem with converting int to string in Linq to entities

Convert a datetime in a subcollection of collection and use it in LINQ to SQL

I have a collection with subcollections in it, one of which is a date field stored in the DB as a string in the format yyyymmdd (which also contains a few random things such as "E" or 20085, etc.). This date is also part of one of the subcollections in the collection. This date field will now be used for searching, so I need to make it into a real date to use it in LINQ statements.
I since learned that LINQ to SQL doesn't support statements that it cannot translate into SQL, so I can't insert a function that returns a properly converted date and I haven't yet found a standard convert function that will transform the string into a valid date object.
I also tried layered converting, though this smelled bad, such as this:
search = from c in search
where c.Object.Any(p=> new DateTime(Convert.ToInt32(p.theDate, Substring(0,4))))... etc.
I just received conversion errors no matter what I did here.
My basic question is: does LINQ to SQL support anything inline for such a conversion? Another option is to change the datamodel or make a view, but I'd rather see if there are ways to handle this in code first. What I'm trying to do is something like the following:
search = from c in search
where c.subcollection.Any(p=>p.theDate >= min) && c.subcollection.Any(p=>p.theDate <= max)
select c;
Where min and max are passed in date values.
Thank you!
The SqlMethods class has some static helper methods that are translatable by LINQ to SQL. Most of these have to do with DateTime comparison. I'm not sure exactly which method you are looking for, but you should be able to find what you want at the documentation page for System.Data.Linq.SqlClient.SqlMethods
Alternatively, you could create a user-defined function and handle the conversion and comparison issue on the SQL side. The integration doesn't require much more than decorating the corresponding Object Model method with a FunctionAttribute, (which indicates how LINQ-to-SQL should process the method. Here is some documentation on this process:
How to: Call User-Defined Functions Inline
User-Defined Functions
If you wish to compare a date using LINQ but the SQL date column is a string value, use .CompareTo("2002-01-01") >= 0
This will translate and return any dates that match 2002-01-01 or occured later in time. Not sure how this works in with your subcollection but as an example:
search = from c in search
where c.stringDateColumn.CompareTo("2002-01-01") >= 0
select c;
Edit: I did some tests on a table with dates in strings like "2002-09-04E", "2004-04-01 34", "20050604e" and "2005-06-01444" and the results do return based on a string in format yyyy-mm-dd or yyyymmdd:
search = from c in search
where c.subcollection.Any(p=>p.theDate.CompareTo(min) >= 0) && c.subcollection.Any(p=>p.theDate.CompareTo(max) <= 0)
select c;

Advanced Linq sorting C#

I have an IQueryable that has a list of pages.
I want to do: Pages.OrderByDescending(o => CalculateSort(o.page));
the method calculate sort is similar to that here is a plain english version:
public int calculatesort(page p)
{
int rating = (from r in db.rating select r). sum();
int comments = //query database for comments;
float timedecayfactor = math.exp(-page.totalhoursago);
return sortscore = (rating +comments)* timedecayfactor;
}
when I run a code similar to the one above an error is thrown that the mothode calculatesort cannot be converted to sql.
How can I do a conver the function above to be understood by sql so that I can use it to sort the pages?
Is this not a good approach for large data? Is there another method used to sort sets of results other than dynamically at the database?
I havent slept for days trying to fix this one :(
your code is nowhere near compiling so I'm guessing a lot here but I hope this gives an idea none the less.
As several have posted you need to give Linq-2-Sql an expression tree. Using query syntax that's what happens (by compiler magic)
from p in pages
let rating = (from r in db.rating
where r.PageId == p.PageId
select r.Value).Sum()
let comments = (from c in db.Comments
where c.PageId == p.PageId
select 1).Count()
let timedecayfactor = Math.Exp(-(p.totalhoursago))
orderby (rating + comments)*timedecayfactor descending
select p;
I haven't actually tried this against a database, there's simply too many unknown based on your code, so there might still be stuff that can't be translated.
The error occurs because LINQ cannot convert custom code/methods into SQL. It can convert only Expression<Func<>> objects into SQL.
In your case, you have a complex logic to do while sorting, so it might make sense to do it using a Stored Procedure, if you want to do it in the DB Layer.
Or load all the objects into main memory, and run the calculate sort method on the objects in memory
EDIT :
I don't have the code, so Describing in english is the best I can do :
Have table with structure capable of temporarily storing all the current users data.
Have a calculated field in the Pages table that holds the value calculated from all the non-user specific fields
Write a stored procedure that uses values from these two sources (temp table and calc field) to actually do the sort.
Delete the temp table as the last part in the stored proc
You can read about stored procs here and here
var comments = db.comments.Where(...);
Pages.OrderByDescending(p=>(db.rating.Sum(r=>r.rate) + comments.Count()) * Math.Exp(-p.totalhoursago))
Linq is expecting Calculatesort to return a "queryable" expression in order to generate its own SQL.
In can embed your 'calculatesort' method in this lambda expression. (I replaced your variables with constants in order to compile in my environment)
public static void ComplexSort(IQueryable<string> Pages)
{
Pages.OrderByDescending(p =>
{
int rating = 99;//(from r in db.rating select r). sum();
int comments = 33;//query database for comments;
double timedecayfactor = Math.Exp(88);
return (rating + comments) * timedecayfactor;
});
}
Also, you can even try to run that in parallel (since .net 4.0) replacing the first line with
Pages.AsParallel().OrderByDescending(p =>
Yes, counting previous answers: the LINQ to SQL doesn't know how to translate CalculateSort method. You should convert LINQ to SQL to ordinary LINQ to Object before using custom method.
Try to use this in the way you call the CalculateSort by adding AsEnumerable:
Pages.AsEnumerable().OrderByDescending(o => CalculateSort(o.page));
Then you're fine to use the OrderByDescending extension method.
UPDATE:
LINQ to SQL will always translate the query in the code into Expression tree. It's quite almost the same concept as AST of any programming language. These expression trees are further translated into SQL expression specific to SQL Server's SQL, because currently LINQ to SQL only supports SQL Server 2005 and 2008.

Categories

Resources