I'm using SQL Server 2005, with a case sensitive database..
In a search function, I need to create a Linq To Entities (L2E) query with a "where" clause that compare several strings with the data in the database with these rules :
The comparison is a "Contains" mode, not strict compare : easy as the string's Contains() method is allowed in L2E
The comparison must be case insensitive : I use ToLower() on both elements to perform an insensitive comparison.
All of this performs really well but I ran into the following Exception :
"Argument data type ntext is invalid for argument 1 of lower function" on one of my fields.
It seems that the field is a NText field and I can't perform a ToLower() on that.
What could I do to be able to perform a case insensitive Contains() on that NText field ?
Never use .ToLower() to perform a case-insensitive comparison. Here's why:
It's possibly wrong (your client collation could be, say, Turkish, and your DB collation not).
It's highly inefficient; the SQL Emitted is LOWER instead of = with a case-insensitive collation.
Instead, use StringComparison.OrdinalIgnoreCase or StringComparison.CurrentCultureIgnoreCase:
var q = from f in Context.Foos
where f.Bar.Equals("hi", StringComparison.OrdinalIgnoreCase)
select f;
But for Contains() there's a problem: Unlike Equals, StartsWith, etc., it doesn't have an overload for a StringComparison argument. Why? Good question; ask Microsoft.
That, combined with SQL Server's limitation on LOWER means there's no simple way to do what you want.
Possible workarounds might include:
Use a full text index, and do the search in a procedure.
Use Equals or StartsWith instead, if possible for your task
Change the default collation of the column?
Use a lambda expression here and create an intermediary list that can handle the lower clause.
var q = Context.Foos.ToList().Where(s => s.Bar.ToLower().Contains("hi"));
Not terribly efficient, but it does work. If you have additional predicates in your where clause then it works to your advantage:
var q = Context.Foos.Where(p => p.f1 == "foo" && p.f2 == "bar").
ToList().Where(s => s.Bar.ToLower().Contains("hi"));
as we known , this is a very "bugged" situation.
and it bugs me a lot.
Today, i decide to create a view as:
select * from tableName
where theColumn like '%key%'
then load this view into EF.
life is getting easy!
Related
I am working on a Linq query expression statement, and want to use a let statement to set a constant value as part of the query to reduce potential errors.
In my example... (this is total mock up). the let statement "validateCount" is the question.
from referenceRow in context.SomeTable
// We want 8 in the Take and the results to still be 8 after the where.
// instead of typing in the value twice (in this example), i wanted to use a constant value here
let validateCount = 8
// sub query expression in my example
let subQuery = from sub in context.SomeTable
where sub.SomeColumn == rowReference.SomeColumn
orderby sub.SomeColumn descending
select sub
// use the sub query, take X amount, apply filter, check to see if Count() is the same as Take()
where subQuery.Take(validateCount) // this is where we fail. if we put in the constant 8, we are ok.
.Where(x => x.SomeOtherColumn == "Some value")
.Count() == validateCount // this is fine
select referenceRow
Unfortunately it seems that the let expression "validateCount" which has a single value of 8 in this example, can only work in the comparison part of .Count() but cannot be passed into the .Take() without throwing an exception.
Limit must be a DbConstantExpression or a DbParameterReferenceExpression.
Parameter name: count
Looking for a solution to use some user-defined constant in a single code location that can be used in the rest of the query expression, both in the .Take() and .Count() without having to be updated several spots in the code.
our application allows users to supply their own linq query expression to build their queries. I can't define anything outside the scope of this query, and must be within, using something like a 'let' statement.
let statement generates intermediate anonymous type projection (Select call) in the query expression tree. EF query provider (as indicated by the exception message) requires Skip and Take arguments to be resolved to constant or variable values (i.e. to be able to be evaluated locally), hence the let cannot be used for that purpose.
Instead, the constants/variables used in Skip / Take expressions should be defined outside of the query and used inside.
To define a constant value you would use:
const int validateCount = 8;
var query = (from .... Take(validateCount) ...);
To define a variable value (SQL query parameter):
int validateCount = 8;
var query = (from .... Take(validateCount) ...);
Here the C# compiler will turn validateCount into closure and EF query provider will be happy to bind a parameter (with that value).
our application allows users to supply their own linq query expression to build their queries. I can't define anything outside the scope of this query, and must be within, using something like a 'let' statement.
When supplying their own queries, the users should follow the same Skip / Take argument rules as above, i.e. define their constants and variables outside of their queries.
I have a mvc project which I use linq in it.
In my database there is some records, for example "Someth ing","SOmeTH ing","someTh ing","SOMETH ING","someTH ING"
I want to do this:
SELECT * FROM dbo.doc_dt_records WHERE name LIKE '%' + #records.Name + '%'
However if I run this code, list.Count returns 0. What should I do?
records.Name = "someth ing"; //for example
var rec = db.Records.ToList();
var lists = rec.Where(p => p.Name.Contains(records.Name)).ToList();
if (lists.Count > 0)
{
// do sthng
}
Thanks for your helps...
the easy way is to use ToLower() method
var lists = rec.Where(p => p.Name.ToLower().Contains(records.Name.ToLower())).ToList();
a better solution (based on this post: Case insensitive 'Contains(string)')
var lists = rec.Where(p =>
CultureInfo.CurrentCulture.CompareInfo.IndexOf
(p.Name, records.Name, CompareOptions.IgnoreCase) >= 0).ToList();
That is totally not a LINQ issue.
Case sensitiivty on the generated SQL depends on the collation relevant for the table. Which in your case likely is case insensitive.
You would get the same result from any SQL you emit.
use IndexOf and StringComparison.OrdinalIgnoreCase:
p.Name.IndexOf(records.Name, StringComparison.OrdinalIgnoreCase) >= 0;
You can create an extension function like this:
public static bool Contains(this string src, string toCheck, StringComparison comp)
{
return src.IndexOf(toCheck, comp) >= 0;
}
To my understanding, this question does not have an unambiguous answer. The matter is that the best way of doing this depends on details which aren't provided in the question. For instance, what exact ORM do you use and what precise DB server you are connected to. For example, if you use Entity Framework against MS SQL Server, you better do not touch your LINQ expression at all. All you need to do is to set the case-insensitive collation on the database/table/column you compare your string with. That will do the trick much better than any change of your LINQ expression. The matter is that when LINQ is translated to SQL, it better be the straight comparison of the column having case-insensitive collation to your string than anything else. Just because it usually works quicker and it is the natural way to do the trick.
You do not want the final query to be something like:
SELECT *
FROM AspNetUsers U
WHERE UPPER(U.Name) LIKE '%SOMETHING%';
It is much better to come up with something like:
SELECT *
FROM AspNetUsers U
WHERE U.Name LIKE '%SOMETHING%';
But with a case-insensitive collation of [Name] column. The difference is that if you have let's say index containing [Name] column, the second query might use it, the first one would do the full scan of the table anyway.
So if let's say records references to DBSet<T> and the record is just one object of type T. You code would be like this:
var lists = records.Where(p => p.Name.Contains(record.Name)).ToList();
And you do the rest on SQL-server. Or if all you need to know is there any value in the list and do not need these values, it would be even better to do like this:
if (records.Any(p => p.Name.Contains(record.Name)))
{
// do something
}
Generally speaking, if you use any sort of ORM connected to any sort of SQL server, you better do case-insensitivity by setting up appropriate parameters of your server/database/table/column. And only if it is impossible or by far too expensive, you consider other possibilities. Otherwise, you might bang into some unexpected and very unpleasant behaviour. For instance, Entity Framework Core 2.x if it cannot translate your LINQ expression straightway into SQL query, is doing different tricks replacing server-side operations with client-side ones. So you can end up with a solution which fetches all data from the table to the client and filter it there. It might be quite a problem if your table is big enough.
As for the situation when LINQ query is processed locally, there are a lot of ways to do the trick. My favourite one is the next:
var lists = records.Where(p => p.Name
.Contains(record.Name, StringComparison.InvariantCultureIgnoreCase))
.ToList();
try this
var lists = rec.Where(p => String.Equals(p.Name,records.Name,StringComparison.OrdinalIgnoreCase)).ToList();
refer here for documentation
I'm using SQL Server 2005, with a case sensitive database..
In a search function, I need to create a Linq To Entities (L2E) query with a "where" clause that compare several strings with the data in the database with these rules :
The comparison is a "Contains" mode, not strict compare : easy as the string's Contains() method is allowed in L2E
The comparison must be case insensitive : I use ToLower() on both elements to perform an insensitive comparison.
All of this performs really well but I ran into the following Exception :
"Argument data type ntext is invalid for argument 1 of lower function" on one of my fields.
It seems that the field is a NText field and I can't perform a ToLower() on that.
What could I do to be able to perform a case insensitive Contains() on that NText field ?
Never use .ToLower() to perform a case-insensitive comparison. Here's why:
It's possibly wrong (your client collation could be, say, Turkish, and your DB collation not).
It's highly inefficient; the SQL Emitted is LOWER instead of = with a case-insensitive collation.
Instead, use StringComparison.OrdinalIgnoreCase or StringComparison.CurrentCultureIgnoreCase:
var q = from f in Context.Foos
where f.Bar.Equals("hi", StringComparison.OrdinalIgnoreCase)
select f;
But for Contains() there's a problem: Unlike Equals, StartsWith, etc., it doesn't have an overload for a StringComparison argument. Why? Good question; ask Microsoft.
That, combined with SQL Server's limitation on LOWER means there's no simple way to do what you want.
Possible workarounds might include:
Use a full text index, and do the search in a procedure.
Use Equals or StartsWith instead, if possible for your task
Change the default collation of the column?
Use a lambda expression here and create an intermediary list that can handle the lower clause.
var q = Context.Foos.ToList().Where(s => s.Bar.ToLower().Contains("hi"));
Not terribly efficient, but it does work. If you have additional predicates in your where clause then it works to your advantage:
var q = Context.Foos.Where(p => p.f1 == "foo" && p.f2 == "bar").
ToList().Where(s => s.Bar.ToLower().Contains("hi"));
as we known , this is a very "bugged" situation.
and it bugs me a lot.
Today, i decide to create a view as:
select * from tableName
where theColumn like '%key%'
then load this view into EF.
life is getting easy!
The SQL server doing queries based COLLATE option, so you can define how comparision will be performed (case sensitive or not). You can do it when you creating table or during query execution.
How can I control collation during my LINQ to SQL queries? Will my queries be allways case insensitive when I will do table.Column == stringValue comparison?
I don't work with the COLLATE option much, but will take my best stab at this question.
According to this article:
LINQ to SQL does not consider server settings when it translates queries.
If COLLATE is a database/table/column setting, it should just be set in the database and be ready to go when you connect.
If COLLATE is a connection setting, you can acquire the connection of your datacontext and run the command to set it. A good place to do this might be in the partial void OnCreated method.
You have to remember that L2S is a Object Relational Mapping system, so its trying to to compare objects, and translate to SQL. In L2S, if you want to compare two strings you have to ToLower() both of them for comparison.
Another thing that was a 'gotcha' for me was that in L2S, string comparison will not evaluate correctly if the comparison value you supply is null. So, in your example, if table.Column is null and stringValue is too, your query will not return the correct results (I am basing this on the assumption that stringValue is a variable defined in your code). In order to compare a string to null in L2S, you have to compare it explicitly to null: table.Column == null.
check out this article
Consider the following criteria query:
var x = SomeCriteria.AddOrder(new Order("Name", true)).List();
This will order the result set by the Name property, but case sensitive:
"A1"
"B1"
"a2"
Any ideas how to add the order case insensitive so result "a2" will end up before "B1"?
You should be able to accomplish this by ordering on a projection that normalizes the case for you. For example, Oracle has a "lower" function that will lower case string data types like varchar2 and nvarchar2; so I will use this sql function to form a projection that will order appropriately.
var projection = Projections.SqlFunction("lower",
NHibernateUtil.String,
Projections.Property("Name"));
var x = SomeCriteria.AddOrder(Orders.Asc(projection)).List()
If you're using SQL Server, I'd recommend using the "upper" function instead of "lower" for efficiency. Microsoft has optimized its native code for performing uppercase comparisons, where the rest of the world seems to have optimized on lowercase.
Hibernate (Java) has an "ignoreCase()" method on the "Order" class, but it looks like NHibernate does not have this method on its "Order."
This is how I was thinking you could do it:
var x = SomeCriteria.AddOrder(new Order("Name", true).IgnoreCase()).List();
But unfortunately, there is no IgnoreCase().
As a workaround, you could use an HQL or SQL query - either of those should allow you to order case-insensitive.
This probably depends on a case-sensitivity setting on your database server. I suspect that NHibernate just issues an "ORDER BY" clause; at least, I can't imagine what else it would do. For SQL Server, the default sort order (collation) is dictionary order, case insensitive.
This article gives some techniques for performing case sensitive searches in SQL Server. However, my advice is to sort the list that is returned by the query in code. That solution preserves the database independence of NHibernate and let's you customize the sort order per your needs.
As I know the responses to my query are always fairly small, I ended up querying the data as normal and sorting them afterwards using Linq. It works, so why bother tweaking NHibernate ;) (Using SQLite, btw)