2010-09-23 183 views
2

我已经实现了一个搜索查询。它取代了“正常化”的特殊字符。我已在不同的领域应用此规则。事实上,查询看起来非常丑陋,并且充满了DRY违规。重构LINQ to Entities查询

但反驳这一点,似乎并不是一件容易的事(对我来说)。当然,我只是想对整个替换 - 东西重构为一个单独的方法,但是这导致像

LINQ到实体错误不承认 方法“System.String 帮助(System.String )”的方法,而这种方法 不能转换成 店expressio ...

下面的代码显示了查询的一部分,还有更多的语句是这样的吧。如果有人有想法,为了让这个更好,这将是非常棒的!

qry = qry.Where(guest => 
       (guest.FirstName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .StartsWith(firstName) 
       && (guest.LastName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .StartsWith(lastName) 
        || 
        guest.LastName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .Contains(" " + lastName) 
        || 
        guest.LastName.Replace(" ", "") 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .StartsWith(lastName)) 
       ) || (
       guest.FirstName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .StartsWith(lastName) 
       && (guest.LastName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .StartsWith(firstName) 
        || 
        guest.LastName 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .Contains(" " + firstName) 
        || 
        guest.LastName.Replace(" ", "") 
        .Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
        .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
        .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
        .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
        .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
        .Replace("ç", "c") 
        .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
        .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
        .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
        .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
        .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
        .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", "") 
        .StartsWith(firstName)) 
       )); 

回答

4

您确实可以将代码重构为返回表达式树的另一个函数(如Jon所述)。

我已经为您制定了一个解决方案,虽然它有点冗长。不幸的是,处理表达式非常复杂,我希望你觉得这很有用。

注:使你也需要使用LinqKit该解决方案的工作(这是非常有用的,当你使用LINQ与ORM)

我陪你如何使用代码开始,然后向您展示使其工作的方法。

我创建了一个名为TestCleanString的函数。你需要给它一个选择器来选择你想测试的属性,并且你还需要给它一个谓词来测试字符串。

例如;这里我们想测试FirstName属性,并且我们想测试它是否以firstName开头。它将选择FirstName属性,然后使用清理规则清理它,然后对谓词测试结果。

TestCleanString<Guest>(g => g.FirstName, s => s.StartsWith(firstName)); 

这是在行动:

//make an expression tree to check the first name 
var firstnameOk = TestCleanString<Guest>(g => g.FirstName, s => s.StartsWith(firstName)); 
//make an expression tree to check the last name 
var lastnameOk = TestCleanString<Guest>(g => g.LastName, s => s.StartsWith(lastName)); 
//make your additional filter expressions here ... 
//...  

//combine the expression trees together using the "And" and "Or" methods from LinqKit 
var filter = firstnameOk.And(lastnameOk); 

//pass the filter into the where method 
qry = qry.Where(filter); 

我放在你的字符串清洁代码到下面的函数返回一个表达式树。

//returns an expression that will clean the string 
private static Expression<Func<string, string>> CleanString() 
{ 
    return s => s.Replace("ü", "u").Replace("ue", "u").Replace("û", "u").Replace("ù", "u").Replace("ú", "u") 
       .Replace("ä", "a").Replace("ae", "a").Replace("â", "a").Replace("à", "a").Replace("á", "a") 
       .Replace("ë", "e").Replace("ê", "e").Replace("è", "e").Replace("é", "e") 
       .Replace("ö", "o").Replace("oe", "o").Replace("ô", "o").Replace("ò", "o").Replace("ó", "o") 
       .Replace("ï", "i").Replace("ì", "i").Replace("ì", "i").Replace("í", "i") 
       .Replace("ç", "c") 
       .Replace(".", "").Replace("-", "").Replace("_", "").Replace("´", "").Replace("'", "").Replace("\"", "") 
       .Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "") 
       .Replace("$", "").Replace("+", "").Replace("*", "").Replace("@", "") 
       .Replace("|", "").Replace("\\", "").Replace("/", "").Replace("<", "").Replace(">", "") 
       .Replace(".", "").Replace(",", "").Replace(";", "").Replace(":", "") 
       .Replace("=", "").Replace("%", "").Replace("^", "").Replace("?", "").Replace("!", ""); 
} 

最后,我们来看看TestCleanString函数。它构建一个表达树,执行以下3个步骤;选择要测试的属性,清理结果字符串,根据指定的条件表达式测试字符串。

public static Expression<Func<TElement, bool>> TestCleanString<TElement>(Expression<Func<TElement, string>> stringSelector, Expression<Func<string, bool>> conditionalExpression) 
{ 
    //declare the parameter: e => 
    var param = new[] { Expression.Parameter(typeof(TElement), "e") }; 
    //pass the parameter into the selector to get the string property 
    var invokedStringSelector = Expression.Invoke(stringSelector, param.Cast<Expression>()); 
    //pass the string property to the clean expression 
    var invokedCleanString = Expression.Invoke(CleanString(), invokedStringSelector.Expand()); 
    //pass the cleaned string to the conditional expression 
    var invokedConditionalExpression = Expression.Invoke(conditionalExpression, invokedCleanString.Expand()); 
    //rebuild the expression tree so the provider can understand it 
    return Expression.Lambda<Func<TElement, bool>>(invokedConditionalExpression.Expand(), param); 
} 

柜面你有兴趣,它生成的SQL看起来有点像这样(我跑这对我自己的模型,所以名称不同):

SELECT 
1 AS [C1], 
[Extent1].[EmailRecipientId] AS [EmailRecipientId], 
[Extent1].[Address] AS [Address], 
[Extent1].[SentOn] AS [SentOn], 
[Extent1].[FailedOn] AS [FailedOn], 
[Extent1].[FailReason] AS [FailReason], 
[Extent1].[IsTo] AS [IsTo], 
[Extent1].[IsCC] AS [IsCC], 
[Extent1].[IsBCC] AS [IsBCC], 
[Extent1].[EmailId] AS [EmailId] 
FROM [dbo].[EmailRecipients] AS [Extent1] 
INNER JOIN [dbo].[Emails] AS [Extent2] ON [Extent1].[EmailId] = [Extent2].[EmailId] 
WHERE (CAST(LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Extent2].[Subject], N'ü', N'u'), N'ue', N'u'), N'û', N'u'), N'ù', N'u'), N'ú', N'u'), N'ä', N'a'), N'ae', N'a'), N'â', N'a'), N'à', N'a'), N'á', N'a'), N'ë', N'e'), N'ê', N'e'), N'è', N'e'), N'é', N'e'), N'ö', N'o'), N'oe', N'o'), N'ô', N'o'), N'ò', N'o'), N'ó', N'o'), N'ï', N'i'), N'ì', N'i'), N'ì', N'i'), N'í', N'i'), N'ç', N'c'), N'.', N''), N'-', N''), N'_', N''), N'´', N''), N'''', N''), N'"', N''), N'(', N''), N')', N''), N'[', N''), N']', N''), N'{', N''), N'}', N''), N'$', N''), N'+', N''), N'*', N''), N'@', N''), N'|', N''), N'\', N''), N'/', N''), N'<', N''), N'>', N''), N'.', N''), N',', N''), N';', N''), N':', N''), N'=', N''), N'%', N''), N'^', N''), N'?', N''), N'!', N'')) AS int)) > 0 
+0

哇,你为我做了很多工作:-)非常感谢,这给了我一个好主意(或几乎解决方案)来重构我的需求。 – Daniel 2010-09-25 23:10:14

+0

不客气! :-)我使用LINQ到EF和LINQ到SQL很多,发现这个问题很有趣。我以前没有想到将表达式树结合起来,但现在我认为它将被证明对于未来的工作非常有用。 – 2010-09-26 10:08:02

+0

对不起,我以前无法尝试你的代码。您是否已成功将该代码与LINQ to Entities运行?我得到一个异常:LINQ表达式节点类型'Invoke'在LINQ to Entities中不受支持:-( – Daniel 2010-10-01 12:05:33

-1

你有没有使用Func键<>委托或私有方法调用,所有这个过程将考虑?这可以增加代码的可读性。

+0

的OP已经明确谈到一个私有方法 - 并且它不会转换成LINQ到EF表达式。 – 2010-09-23 07:17:06

+0

如DoctaJonez所示,'Func <>'将不起作用,但'Expression >'会。 – Gabe 2010-09-25 02:27:18

1

那么,LINQ to EF不会像lambda表达式那样喜欢简单的私有方法调用......但是您可以编写一个方法,为您已有的构建等效的表达式树。使用表达式树并不总是很容易,但它应该做到这一点。然后,您将调用Where方法,将表达式树作为普通参数传递(即使用lambda表达式,即而不是)。

我建议你写一个非常简单的声明执行2个Replace操作,要么看看C#编译器为你做的(通过反射),或使用表达式树展示台在VS2010。一旦你知道树是什么样子的话,以编程方式构建它应该不会太坏。请注意,您还必须在表达式树中建立“或”功能(我建议您使用单独的方法),但只需使用多个Where即可实现“AND”功能调用。

+0

嘿乔恩,难道不是容易出错,从lambda表达式获取表达式树,而不是手动构建它。例如;表达式> clean = s => s.Replace(“ü”,“u”)。Replace(“ue”,“u”)。Repl ... snip ...; – 2010-09-25 02:23:31

+0

我认为这比手工制作的树更易读,维护成本更低。当编译器为你做这件事时,不需要自己推出呃? :-)还是有什么我错过了? – 2010-09-25 02:25:08

1

它不会帮助你重构这段代码。 LINQ to Entities查询最终被翻译成SQL,无论代码看起来多么好,这个SQL都会变得很糟糕。您需要根据数据库为您提供的工具重新考虑您的查询策略。理想情况下,您应该能够编写使用索引的查询。

有两种策略需要考虑:归类和模式更改。

您尚未提及您正在使用哪个数据库,但大多数数据库提供对WHERE搜索不区分重音的归类。您应该考虑将列上的排序规则更改为其中之一。

关于美元符号等,您可能不会找到一个排除规则,除非您自己写。因此,不同的选择是在数据库中有一个单独的列,由触发器更新,其中包含删除这些字符的名字和姓氏。相反,运行搜索,可以使用这些列上的索引。

+0

排序规则,我不知道也有重音不敏感的排序规则。我想我会用它们。我也考虑过其他方法(将清理过的可搜索字符串保存在单独的列中),但我不喜欢它。它创造了一种冗余,确保它已经生成,但它仍然是多余的。 – Daniel 2010-09-25 23:13:49

+0

btw:我使用SQL Server 2008(和R2) – Daniel 2010-09-25 23:14:21