Как имитировать регулярные выражения в LINQ-to-SQL
У меня есть таблица базы данных с номерами учетных записей. Внутри той же таблицы находятся тестовые учетные записи, которые не соответствуют форматированию продукции: например, "A1111" - это производство, но "JTest" - нет. У меня есть Regex, который вытащит только мои производственные счета. Мне нужен конкретный скомпилированный запрос, чтобы вытащить только производственные учетные записи. Запрос дает мне количество клиентов по регионам и дате; и подсчет понятий в каждом регионе:
getCustomerDistribution = CompiledQuery.Compile<DataContext, String, DateTime, IEnumerable<ServerLoad>>(
(context, region, processDate) => (from cust in context.GetTable<tbl_CustomerDistro>()
where cust.ProcessedDate.Date == processDate.Date
where cust.Region == region
where Regex.IsMatch(cust.AcctNum, ProductionMask)
group cust by new
{
cust.Region,
cust.Concept
} into custDistro
orderby custDistro.Key.Region
select new CustomerDistro
(
custDistro.Key.Region,
custDistro.Key.Concept,
custDistro
.Where(c => c.Concept == custDistro.Key.Concept)
.Select(c => c.Concept).Count()
)));
Проблема в том, что во время выполнения я получаю следующее сообщение:
Метод 'Boolean IsMatch (System.String, System.String) 'не поддерживается перевод на SQL.
Я смотрел на определенную пользователем функцию:
static Func<striing, bool> IsProduction = (AcctNum) => Regex.IsMatch(AcctNum, ProductionMask);
Это тоже не работает. Я не хочу перебирать записи, которые извлекаются для дальнейшего фильтрации, если только нет другого способа сделать это.
Есть ли способ сделать это с помощью Predicate Builder?
Обновление:
Другой вариант, который, я думаю, будет использовать:
where SqlMethods.Like (cust.AcctNum, ProductionMask)
Однако моя ProductionMask написана для Regex:
^[B,G]\d{4}$
Есть ли способ сделать это с помощью SqlMethods.Like(...)?
Обновление 2:
Это очень медленный запрос. У меня есть 3 региона, с которыми выполняется этот запрос, и количество записей и время возврата:
263: 903ms
342: 822ms
146: 711 мс
Ответы
Ответ 1
Я изменил запрос, используя вместо Regex.IsMatch
следующее:
where SqlMethods.Like(cust.Acct, ProductionMask)
где ProductionMask = "[bBgG][0-9][0-9][0-9][0-9]"
эквивалентный RegEx: ^[B,G]\d{4}$
Если кто-нибудь увидит, что 2 маски не должны давать одинаковые результаты, сообщите мне...
Ответ 2
Используете ли вы LINQ-to-SQL? Если да, форумы MSDN сообщают следующее:
LINQ to SQL не может переводить регулярные выражения для SQL, потому что нет поддержка Regex на конце SQL.
Однако он дает 3 варианта.
Ответ 3
Особая благодарность Роману Храмцову и db_developer за справочную информацию,
и благодаря Microsoft: P
Расширение RegExpLike для сервера Sql
Ссылки:
http://www.codeproject.com/Articles/42764/Regular-Expressions-in-MS-SQL-Server-2005-2008
http://msdn.microsoft.com/en-us/library/dd456847.aspx
Шаг1: Скомпилируйте SqlRegularExpressions.cs для генерации SqlRegularExpressions.dll
// SqlRegularExpressions.cs
// © Copyright 2009, Roman Khramtsov / Major League - SqlRegularExpressions
using System;
using System.Data.SqlTypes; //SqlChars
using System.Collections; //IEnumerable
using System.Text.RegularExpressions; //Match, Regex
using Microsoft.SqlServer.Server; //SqlFunctionAttribute
/// <summary>
/// Class that allows to support regular expressions in MS SQL Server 2005/2008
/// </summary>
public partial class SqlRegularExpressions
{
/// <summary>
/// Checks string on match to regular expression
/// </summary>
/// <param name="text">string to check</param>
/// <param name="pattern">regular expression</param>
/// <returns>true - text consists match one at least, false - no matches</returns>
[SqlFunction]
public static bool Like(string text, string pattern, int options)
{
return (Regex.IsMatch(text, pattern, (RegexOptions)options));
}
/// <summary>
/// Gets matches from text using pattern
/// </summary>
/// <param name="text">text to parse</param>
/// <param name="pattern">regular expression pattern</param>
/// <returns>MatchCollection</returns>
[SqlFunction(FillRowMethodName = "FillMatch")]
public static IEnumerable GetMatches(string text, string pattern, int options)
{
return Regex.Matches(text, pattern, (RegexOptions)options);
}
/// <summary>
/// Parses match-object and returns its parameters
/// </summary>
/// <param name="obj">Match-object</param>
/// <param name="index">TThe zero-based starting position in the original string where the captured
/// substring was found</param>
/// <param name="length">The length of the captured substring.</param>
/// <param name="value">The actual substring that was captured by the match.</param>
public static void FillMatch(object obj, out int index, out int length, out SqlChars value)
{
Match match = (Match)obj;
index = match.Index;
length = match.Length;
value = new SqlChars(match.Value);
}
}
Шаг 2: Запустите DbInstall.sql SQL в базе данных
DbInstall.sql
sp_configure 'clr enabled', 1
reconfigure
go
--needs full path to DLL
create assembly SqlRegularExpressions
from '..\SqlRegularExpressions.dll'
with PERMISSION_SET = SAFE
go
create function RegExpLike(@Text nvarchar(max), @Pattern nvarchar(255), @Options int = 0)
returns bit
as external name SqlRegularExpressions.SqlRegularExpressions.[Like]
go
create function RegExpMatches(@text nvarchar(max), @pattern nvarchar(255), @Options int = 0)
returns table ([Index] int, [Length] int, [Value] nvarchar(255))
as external name SqlRegularExpressions.SqlRegularExpressions.GetMatches
go
DbUninstall.sql
drop function RegExpLike
drop function RegExpMatches
drop assembly SqlRegularExpressions
go
sp_configure 'clr enabled', 0
reconfigure
go
Шаг 3: В правой части диаграммы модели выберите "Обновить модель из базы данных...", используйте мастер обновления, чтобы добавить хранимые функции в модель.
![Model diagram context menu]()
![Update wizard]()
![Model browser]()
Шаг 4: Создайте импортированные функции в классе контекста объекта.
public class TheCompanyContext : Entities
{
// Please check your entity store name
[EdmFunction("TheCompanyDbModel.Store", "RegExpLike")]
public bool RegExpLike(string text, string pattern, int options)
{
throw new NotSupportedException("Direct calls are not supported.");
}
}
Шаг 5: Наконец, вы можете использовать регулярные выражения для LINQ to Entities:)
User[] qry = (from u in context.Users
where u.ApplicationName == pApplicationName
&& context.RegExpLike(u.Username, usernameToMatch, (int)RegexOptions.IgnoreCase)
orderby u.Username
select u)
.Skip(startIndex)
.Take(pageSize)
.ToArray();
Ответ 4
Не могли бы вы заменить Regex.IsMatch на
where cust.AcctNum.StartsWith(ProductionMask)
Или Содержит/Концы В зависимости от ваших потребностей
Ответ 5
У меня была такая же проблема, но мне удалось избавиться от нее.
Я знаю, что это медленно, но работает, любая подсказка оптимизации/исправления будет приветствоваться:)
Сначала код собирает данные, а затем обрабатывает,
поэтому вам нужно отфильтровать столько, сколько вы можете, прежде чем звонить toarray()
или купить больше бара:)
Надеюсь, поможет,
наслаждаться
Regex rx = LikeToRegEx(emailToMatch);
User[] qry = (from u in context.Users
where u.ApplicationName == pApplicationName
orderby u.Username
select u)
.ToArray()
.Where(u => rx.IsMatch(u.Email))
.ToArray();
// -- LikeToRegEx : Converts SQL like match pattern to a regular expression --
public Regex LikeToRegEx(string likestr, RegexOptions opt = RegexOptions.None)
{
likestr = likestr
.Replace("*", ".")
.Replace("+", ".")
.Replace("(", ".")
.Replace("[", ".")
.Replace("/", ".")
.Replace("\\", ".")
.Replace("^", ".")
.Replace("$", ".")
.Replace("_", ".")
.Replace("%", ".*");
return new Regex(likestr, opt);
}
P.S. Это быстрый способ обработки световых таблиц данных, вы можете
улучшите его, просто извлекая необходимые столбцы для обработки и просто
возвращать идентификационные столбцы для полного доступа к строкам. Вы можете использовать мой последний пост
для более общих сценариев тяжелого режима. Выбор за вами.