This question already has answers here:
How to make LINQ execute a (SQL) LIKE range search
(3 answers)
Closed 2 years ago.
I have a textbox that allows a user to specify a search string, including wild cards, for example:
Joh*
*Johnson
*mit*
*ack*on
Before using LINQ to Entities, I had a stored procedure which took that string as parameter and did:
SELECT * FROM Table WHERE Name LIKE #searchTerm
And then I would just do a String.Replace('*', '%') before passing it in.
Now with LINQ to Entities I am trying to accomplish the same thing. I know there is StartsWith, EndsWith and Contains support, but it won't support it in the way that I need.
I read about "SqlMethods.Like" and tried this:
var people = from t in entities.People
where SqlMethods.Like(t.Name, searchTerm)
select new { t.Name };
However I am getting the following exception:
LINQ to Entities does not recognize the method 'Boolean Like(System.String,
System.String)' method, and this method cannot be translated into a store
expression.
How would I get this same functionality using LINQ to Entities?
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/6529a35b-6629-44fb-8ea4-3a44d232d6b9/
var people = entities.People.Where("it.Name LIKE #searchTerm", new ObjectParameter("searchTerm", searchTerm));
How to get it to work seamlessly:
in your EDMX model, add:
<Function Name="String_Like" ReturnType="Edm.Boolean">
<Parameter Name="searchingIn" Type="Edm.String" />
<Parameter Name="lookingFor" Type="Edm.String" />
<DefiningExpression>
searchingIn LIKE lookingFor
</DefiningExpression>
</Function>
just after the sections that start:
<edmx:ConceptualModels>
<Schema Namespace="Your.Namespace"...
Then, anywhere in your code, add this extension method:
//prior to EF 6 [System.Data.Objects.DataClasses.EdmFunction("Your.Namespace", "String_Like")]
//With EF 6
[System.Data.Entity.DbFunction("Your.Namespace", "String_Like")]
public static bool Like(this string input, string pattern)
{
/* Turn "off" all regular expression related syntax in
* the pattern string. */
pattern = Regex.Escape(pattern);
/* Replace the SQL LIKE wildcard metacharacters with the
* equivalent regular expression metacharacters. */
pattern = pattern.Replace("%", ".*?").Replace("_", ".");
/* The previous call to Regex.Escape actually turned off
* too many metacharacters, i.e. those which are recognized by
* both the regular expression engine and the SQL LIKE
* statement ([...] and [^...]). Those metacharacters have
* to be manually unescaped here. */
pattern = pattern.Replace(#"\[", "[").Replace(#"\]", "]").Replace(#"\^", "^");
return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
}
And there you have it.
Now you can do:
(from e in Entities
where e.Name like '%dfghj%'
select e)
or
string [] test = {"Sydney", "Melbourne", "adelaide", "ryde"};
test.Where(t=> t.Like("%yd%e%")).Dump();
Well, your choices are:
Use Contains. I know you don't like it, but it could probably be made to work.
Pick a function from SqlFunctions. They're all supported in L2E.
Map your own function.
+1 to #Yury for ESQL.
You can do this:
using System.Data.Entity; // EntityFramework.dll v4.3
var queryResult=db.Accounts.AsQueryable().Where(x => x.Name.Contains(queryKey));
because Linq to Entity can't transform the method Contains() to the SQL, but Linq to SQL can do this. I tried to find a method that can doing a cast, at last, AsQueryable(), also a generic version AsQueryable<T>(). I found I can do this using it this way in my case, but any side effect it has I don't know, maybe it will lose some feature at Entity.
the solution is to use SQLFunctions.PatIndex
var result = from c in items
where SqlFunctions.PatIndex(searchstring.ToLower(), c.fieldtoSearch) > 0
select c;
where 'searchstring' is the pattern to search
'fieldtoSearch' is the field to search
Patindex() supports search using string pattern search. The search is case insensitive.
Now, EF supports "LIKE" usage and you can use all sql wildcards. Check this out.
var people = from t in entities.People
select new { t.Name };
people = people.Where(x => DbFunctions.Like(x.Name, searchTerm));
You can do all these statements with LINQ like this
string _search = "johnson";
// joh* OR joh%
items.Where(i => i.Name.StartsWith(_search, StringComparison.OrdinalIgnoreCase));
// *son OR %son
items.Where(i => i.Name.EndsWith(_search, StringComparison.OrdinalIgnoreCase));
// *hns* OR %hns%
items.Where(i => i.Name.ToLower().Contains(_search));
var people = from t in entities.People
where t.Name.ToLower().Contains(searchTerm.ToLower())
select new { t.Name };
EDIT- I might be mixing syntax. I usually use extension methods; but contains will work.
It is easily achieved by following methods
var people = from t in entities.People
where t.Name.Contains(searchTerm)
select new { t.Name };
Use the following specifications to achieve wildcards
LIKE 'a%' => StartsWith("a")
LIKE '%a' => EndsWith("a")
LIKE '%a%' => Contains("a")
LIKE 'a%b' => StartsWith("a") && EndsWith("b")
LIKE '%a%b%' => StartsWith("a") && Contains("b")
You do not need to use percent sign while filtering. e.g;
if I want to check ItemName does not contain '-' I will do it like this
!Item.ItemName.Contains("-")
In SQL it will convert to NOT LIKE '%-%'
We use Database First and the EntityFramework.
The "Map your own function." approach works for us together with the nuget EntityFramework.CodeFirstStoreFunctions.
1 Step: Create a function in the db like this:
CREATE FUNCTION [dbo].[StringLike]
(
#a nvarchar(4000),
#b nvarchar(4000)
)
RETURNS bit
AS
BEGIN
RETURN
(SELECT CASE
WHEN (SELECT 1 WHERE #a LIKE #b) = 1 THEN 1
ELSE 0
END)
END
2 Step: Install nuget EntityFramework.CodeFirstStoreFunctions
3 Step: Create a method in your code like this (I create mine in the DbContext class):
[DbFunction("CodeFirstDatabaseSchema", "StringLike")]
public static bool Like(string input, string pattern)
{
throw new NotSupportedException("Direct calls are not supported.");
}
4 Step: Initalize EntityFramework.CodeFirstStoreFunctions.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(new FunctionsConvention("dbo", this.GetType()));
}
5 Step: Now you can use this method in your linq query.
Related
I tried to use DbFunctions.Like with EF 6.2 and got run-time error:
LINQ to Entities does not recognize the method 'Boolean
Like(System.String, System.String)' method, and this method cannot be
translated into a store expression.
Code:
list=list.Where(p=> DbFunctions.Like(p.Master_Bill,"somestring%")); where list is IQueryable<SomeView>
It compiles OK. I thought it can be used with EF 6.2. I know there is also EF Core, did not look at it
Any ideas?
thanks
What about
list = list.Where( p => p.Master_Bill.StartsWith(someString));
?
If the user can enter a wildcard(s) in their search string you can evaluate the string for legal combinations I.e. "?somestring", "somestring?" or "?somestring?" then choose the appropriate where condition.
wildcardResult = evaluateWildcard(someString);
switch(wildcardResult.Result)
{
case WildcardResult.NoWildcard:
list = list.Where( p => p.Master_Bill == wildcardResult.SearchString);
break;
case WildcardResult.StartsWith:
list = list.Where( p => p.Master_Bill.StartsWith(wildcardResult.SearchString));
break;
case WildcardResult.EndsWith:
list = list.Where( p => p.Master_Bill.EndsWith(wildcardResult.SearchString));
break;
case WildcardResult.Contains:
list = list.Where( p => p.Master_Bill.Contains(wildcardResult.SearchString));
break;
}
Where the result class contains an enum for the detected search expression pattern, and the search expression with the wildcard characters stripped to use as the SearchString.
It would also be advisable to evaluate the length of the search string for a minimum viable length when using wildcards. Users could trigger rather expensive queries by using expressions like "?" or "?e?".
Edit: DbFunctions.Like does work as well with SQL Server. Any error you are getting is likely due to an assumption about the IQueryable you are running or the field you are comparing. (I.e. not a mapped column, or a particular data type?)
For instance: Something like this works just fine..
var data = _context.People.Where(p => DbFunctions.Like(p.Name, "s%")).ToList();
Which would return all People with a Name starting with "S". (case insensitive)
I'd look at what your entire IQueryable looks like, as well as that Master_Bill is both a mapped column and a regular NVARCHAR/VARCHAR column.
I am building up a query in C#. For integer and string fields, case is quite simple. For date fields, I am using following query:
list.Where("myDateColumn >= DateTime(2017,1,20)");
How I can perform following SQL LIKE query in LINQ?
select * from table where myTextColumn LIKE '%abc%';
There are lots of possibilities for Like in Linq:
For LIKE '%abc%';
list.Where(x => x.myTextColumn.Contains('abc'));
For LIKE 'abc%';
list.Where(x => x.myTextColumn.StartWith('abc'));
For LIKE '%abc';
list.Where(x => x.myTextColumn.EndsWith('abc'));
Updates : If you need to add Date comparison as well means you can do like the following:
DateTime date2Compare = new DateTime(2017, 1, 20);
list.Where(x => myDateColumn >= date2Compare && x.myTextColumn.Contains('abc'));
Placement of the Wildcard '%' in a LIKE clause makes a difference, and C# has the methods to back this up. Below is what the placements of the Wildcard means.
LIKE '%abc'
Meaning: Find any word ending with 'abc'.
C# equivalent: EndsWith
LIKE 'abc%'
Meaning: Find any word starting with 'abc', and you don't care about the text after.
C# equivalent: StartWith
LIKE '%abc%'
Meaning: Find any word that contains 'abc', and you don't care where in the word it appears.
C# equivalent: Contains
You can use Contains with myTextColumn field
var date = new DateTime(2017,1,20);
list.Where(x => x.myDateColumn >= date && x.myTextColumn.Contains('abc'));
While other answers (including the accepted one) are 80% correct, there are some caveats.
T-SQL allows the Like statement to have wildcards not just at the start and end of a string, but also in the middle. And % is not the only special char in the syntax, see docs for details.
.NET Core has EF.Functions.Like() method that is literally translated into LIKE statement. See the below example.
//assuming there is a db context with a table of Foos
await using var dbContext = new SqlContext();
//assuming table of Foos has Ipsum, Iptum, Ipuum
var result = dbContext.Foos.Where(x
=> EF.Functions.Like(x.Bar, "Ip_um"));
Caveats:
This only works with a server-side evaluation. An attempt to use this feature in client-side evaluation will throw NotSupportedException.
There is a noticeable difference in SQL translation between EF.Functions.Like and Contains or StartsWith, which may impact performance.
If you are certain you are using server-side evaluation and performance is important, use EF.Functions.Like.
Let's try solving the problem in general case. Suppose we're given something like
select ...
where ... MyField like '%abc%'
we can try convert like expression into corresponding regular one:
Like | Description |Regular
-------------------------------------------------
_ | any character (one and only one) | .
% | any characters (zero or more) | .*
Implementation
// If you want to implement both "*" and "?"
private static String LikeToRegular(String value) {
return "^" + Regex.Escape(value).Replace("_", ".").Replace("%", ".*") + "$";
}
usage:
var result list
.Where(x => Regex.IsMatch(x.myTextColumn, LikeToRegular("%abc%")));
you may want to convert the data into string before matching:
var result list
.Where(x => Regex.IsMatch(x.myDate.ToString(), LikeToRegular("%abc%")));
I have this already
if (options.English != null) query = query
.Where(w => w.English.Contains(options.English));
What I would like to do is to extend this (maybe with another if clause) to make it so that if:
a user enters ^abc then my query would check if the word starts with "abc".
a user abc then it would check if the column contains "abc"
I am using a SQL Server back-end database. Could anyone give me a suggestion as to how I could implement this functionality.
Assuming that you're using Entity Framework, you can use the StartsWith() and EndsWith() methods, to achieve the same results as Contains() except only at the beginning or the end of a string. It will generate the code for you.
Then simply create conditional statements in your code, in order to determine which one of the methods you should use.
Some word of advice:
There might be a bug with EF Core, in which it turns StartsWith("string") into LIKE "string%" which might yield incorrect results with strings, containing wildcard characters such as "_".
So I'd advise you to use plain SQL with EF Core, and given that you're using SQL Server as a DBMS, query like that:
if (searchText.StartsWith("^"))
{
var result = query.FromSql($"SELECT something FROM table WHERE PATINDEX({searchText.Substring(1)}, something) = 1");
}
else
{
var result = query.FromSql($"SELECT * FROM table WHERE PATINDEX({searchText.Substring(1)}, something ) <> 0");
}
With PATINDEX() you will get correct results even if your pattern string contains wildcard characters - escaping potential bugs with relying on StartsWith() and EndsWith() to generate proper SQL code.
But that's only for EF Core, EF 6 works like a charm the way other people answered :)
You can put that choice in a conditional statement:
IQueryable<Whatever> query = ...;
if (searchText.StartsWith("^"))
{
query = query.Where(w => w.English.StartsWith(searchText.Substring(1)));
}
else
{
query = query.Where(w => w.English.Contains(searchText));
}
You can also do the same comparison inline, but that'll generate very ugly SQL, if it even works:
query = query.Where(w =>
searchText.StartsWith("^")
? w.English.StartsWith(searchText.Substring(1))
: w.English.Contains(searchText));
Do note that you generally don't want to search text using SQL, as that results in a pretty poor user experience. Take a look at full-text indexing.
if (options.English != null)
{
bool englishStartsWith = options.English.StartsWith("^");
if(englishStartsWith)
{
query = query.Where(w => w.English.StartsWith(options.English.Substring(1)));
}
else
{
query = query.Where(w => w.English.Contains(options.English));
}
}
I would like to concatenate a string using lambda to compare that concatenated value against a certain condition.
Invoices = Invoices.Where(f => ((string)f.invoice_prefix + String.Format("{0:0000}", Convert.ToInt32(f.invoice_number))).ToLower().Equals(condition7));
But I get an error message :
The name 'f' does not exist in the current context
Tried several String.Format and String.Concat variants like
Invoices = Invoices.Where(f => (String.Format("{0}{1}",f.invoice_prefix,String.Format("{0:0000}", Convert.ToInt32(f.invoice_number)))).ToLower().Equals(condition7));
but no success... Can somebody help me with the syntax?
Thanks in advance!
Linq to Entities doesn't understand all of the .NET framework methods.
In order to run this as a SQL statement on the database, you need to only use operators that can be converted to SQL. That means you need to re-write your predicate using primitive data types.
So something like this:
string prefixCondition = ...
int invoiceNumberCondition = ...
Invoices.Where( f =>
f.invoice_prefix == prefixCondition
&&
f.invoice_number == invoiceNumberCondition
)
I recommend using LinqPad to test with, as it shows you the generated SQL statement.
I am slowly porting over an app from MySQL to use Linq2Sql - but one query has stumped me a bit.
SELECT * FROM Pages WHERE DomainID = #reportid AND (PageContent REGEXP 'display:[ \t]*none') > 0 ORDER BY URL ASC
Any ideas on how I would write something like this with Linq2SQL? Its the REGEXP bit thats got me stumped?
There is no way built in to LINQ to SQL, but you have a couple of other choices. The first is to load your strings in as in-memory objects which you can apply Regex functions to. I'm not a big fan of this since it looks like you're potentially getting some very big strings to match against.
The second option is to leverage SQL CLR as described here. This effectively lets you create a stored procedure that gets linked to a CLR method that you create. Whenever you call the method in a LINQ to SQL context, it gets converted to a stored procedure call. Then you use a query like this:
var q = from p in context.Pages
where p.DomainId == reportId &&
RegExMatch(p.PageContent, "display\:[ \t]*none")
select p;
Why not use LINQ to return items that match on reportid and that contain 'display:', to minimise the amount of data being returned from the server, and then use regex on client side to filter that list down?
var query = Pages.Where( p => p.DomainId == 1 && p.PageContent.IndexOf("display:") > 0).OrderBy( o => o.URL );
var regex = new Regex(#"display\:[\t]*none");
foreach (var page in query)
{
if( regex.IsMatch(page.PageContent) )
{
// Do whatever...
}
}