Как оператор в LINQ to Objects
Я пытаюсь эмулировать оператор LIKE
в LINQ для объектов. Здесь мой код:
List<string> list = new List<string>();
list.Add("line one");
list.Add("line two");
list.Add("line three");
list.Add("line four");
list.Add("line five");
list.Add("line six");
list.Add("line seven");
list.Add("line eight");
list.Add("line nine");
list.Add("line ten");
string pattern = "%ine%e";
var res = from i in list
where System.Data.Linq.SqlClient.SqlMethods.Like(i, pattern)
select i;
Мне не удалось получить результат из-за System.Data.Linq.SqlClient.SqlMethods.Like
только для перевода в SQL.
Существует ли что-либо подобное оператору sql LIKE
в мире LINQ to Objects?
Ответы
Ответ 1
Я не знаю того, что легко существует, но если вы знакомы с регулярными выражениями, вы можете написать свой собственный:
using System;
using System.Text.RegularExpressions;
public static class MyExtensions
{
public static bool Like(this string s, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
{
return Regex.IsMatch(s, pattern, options);
}
}
И затем в вашем коде:
string pattern = ".*ine.*e";
var res = from i in list
where i.Like(pattern)
select i;
Ответ 2
Этот фрагмент будет имитировать поведение и синтаксис Sql LIKE. Вы можете обернуть его в свой собственный метод лямбда или расширения для использования в инструкции Linq:
public static bool IsSqlLikeMatch(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);
}
Комбинированный метод расширения, который будет работать как метод IEnumerable<T>.Where
:
public static IEnumerable<T> Like<T>(this IEnumerable<T> source, Func<T, string> selector, string pattern)
{
return source.Where(t => IsSqlLikeMatch(selector(t), pattern));
}
Это, в свою очередь, позволит вам форматировать ваш оператор следующим образом:
string pattern = "%ine%e";
var res = list.Like(s => s, pattern);
ИЗМЕНИТЬ
Улучшенная реализация, если кто-то наткнется и захочет использовать этот код. Он преобразует и компилирует регулярное выражение один раз для каждого элемента, а преобразование из LIKE в regex выше имеет некоторые ошибки.
public static class LikeExtension
{
public static IEnumerable<T> Like<T>(this IEnumerable<T> source, Func<T, string> selector, string pattern)
{
var regex = new Regex(ConvertLikeToRegex(pattern), RegexOptions.IgnoreCase);
return source.Where(t => IsRegexMatch(selector(t), regex));
}
static bool IsRegexMatch(string input, Regex regex)
{
if (input == null)
return false;
return regex.IsMatch(input);
}
static string ConvertLikeToRegex(string pattern)
{
StringBuilder builder = new StringBuilder();
// Turn "off" all regular expression related syntax in the pattern string
// and add regex begining of and end of line tokens so '%abc' and 'abc%' work as expected
builder.Append("^").Append(Regex.Escape(pattern)).Append("$");
/* Replace the SQL LIKE wildcard metacharacters with the
* equivalent regular expression metacharacters. */
builder.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. */
builder.Replace(@"\[", "[").Replace(@"\]", "]").Replace(@"\^", "^");
// put SQL LIKE wildcard literals back
builder.Replace("[.*]", "[%]").Replace("[.]", "[_]");
return builder.ToString();
}
}
Ответ 3
Вы должны использовать Regex для шаблона, а затем использовать метод расширения Where
для повторения и поиска совпадений.
Итак, ваш код должен выглядеть следующим образом:
string pattern = @".*ine.*e$";
var res = list.Where( e => Regex.IsMatch( e, pattern));
Если вы не знакомы с Regex, это гласит:
Первые 0 или более символов (. *), за которыми следует ine (ine), затем 0 или более символов (. *), затем и e (e), а e должен быть концом строки ($)
Ответ 4
1. Использование String.StartsWith или String.Endswith
Записывая следующий запрос:
var query = from c in ctx.Customers
where c.City.StartsWith("Lo")
select c;
will generate this SQL statement:
SELECT CustomerID, CompanyName, ...
FROM dbo.Customers
WHERE City LIKE [Lo%]
что мы и хотели. То же самое происходит с String.EndsWith.
Но что мы хотим запросить у клиента с таким названием города, как "L_n%"? (начинается с Капитала "L", чем какой-либо символ, чем "n", а остальное имя). Используя запрос
var query = from c in ctx.Customers
where c.City.StartsWith("L") && c.City.Contains("n")
select c;
generates the statement:
SELECT CustomerID, CompanyName, ...
FROM dbo.Customers
WHERE City LIKE [L%]
AND City LIKE [%n%]
что не совсем то, что мы хотели, и еще немного сложнее.
2. Использование метода SqlMethods.Like
Копаясь в пространстве имен System.Data.Linq.SqlClient
, я нашел небольшой вспомогательный класс под названием SqlMethods, который может быть очень полезен в таких сценариях. SqlMethods имеет метод Like, который может использоваться в запросе Linq to SQL:
var query = from c in ctx.Customers
where SqlMethods.Like(c.City, "L_n%")
select c;
Этот метод получает строковое выражение для проверки (город клиента в этом примере) и шаблоны для тестирования, против которых предоставляется то же самое, что вы должны написать предложение LIKE в SQL.
Используя приведенный выше запрос, генерируется требуемый оператор SQL:
SELECT CustomerID, CompanyName, ...
FROM dbo.Customers
WHERE City LIKE [L_n%]
Источник: http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/16/linq-to-sql-like-operator.aspx
Ответ 5
Я не знаю, существует ли он, но вот реализация метода расширения с использованием алгоритма Кнута-Морриса-Пратта, который я сделал.
public static IEnumerable<T> Like<T>(this IEnumerable<T> lista, Func<T, string> type, string pattern)
{
int[] pf = prefixFunction(pattern);
foreach (T e in lista)
{
if (patternKMP(pattern, type(e), pf))
yield return e;
}
}
private static int[] prefixFunction(string p)
{
int[] pf = new int[p.Length];
int k = pf[0] = -1;
for (int i = 1; i < p.Length; i++)
{
while (k > -1 && p[k + 1] != p[i])
k = pf[k];
pf[i] = (p[k + 1] == p[i]) ? ++k : k;
}
return pf;
}
private static bool patternKMP(string p, string t, int[] pf)
{
for (int i = 0, k = -1; i < t.Length; i++)
{
while (k > -1 && p[k + 1] != t[i])
k = pf[k];
if (p[k + 1] == t[i])
k++;
if (k == p.Length - 1)
return true;
}
return false;
}