在上几节Linq动态组合查询时,在肖坤的Linq动态查询与模糊查询(带源码示例)时看到了微软的《Linq to SQL Dynamic 动态查询》,但是楼主说“可惜Dynamic.cs也是不能使用like的,恨啊!”。于是我下载了Dynamic .cs仔细研究了下源码,一步一步的调试,本想在微软的类库里添加如like的支持,但是调试了半天,还是无从下手。但是发现了DLinq支持对方法的调用,支持 DataContext.Orders.Where("ShipCity.Contains(@0) ","test");于是想了下我能否用正则表达式匹配Like表达式,转化为string.Contains()。就尝试Code。说句其实微软已经支持了,只是我们有一些还是习惯于Like ‘str’形式吧了。

    赶快进入主题-Code:代码

  1. /// <summary>  
  2.     /// 操作like帮助类,自己增加的;  
  3.     /// </summary>  
  4.     public class DynamicQueryableLikeHelper  
  5.     {  
  6.         public static string CheckLikeSattement(string predicate, ref  object[] values)  
  7.         {  
  8.             MatchCollection matches = Regex.Matches(predicate.ToLower(),  
  9. @"(?<item>(\s*like\s*((?<param1>(@[\d+]))|(?<param2>(((\')?\w+\'?))))))\s*((and)|(or)?)");  
  10.             string s = predicate;  
  11.             if (matches != null)  
  12.             {  
  13.                 int offset = 0;  
  14.                 foreach (Match item in matches)  
  15.                 {  
  16.                     Group gitem = item.Groups["item"];  
  17.                     if (!string.IsNullOrEmpty(item.Groups["param1"].Value))  
  18.                     {  
  19.                         Group g = item.Groups["param1"];  
  20.                         string insertstr = ".Contains(" + g.Value + ")";  
  21.                         s = DynamicQueryableLikeHelper.ChangePredicate(s, insertstr, gitem, ref offset);  
  22.  
  23.                     }  
  24.                     else if (!string.IsNullOrEmpty(item.Groups["param2"].Value))  
  25.                     {  
  26.                         Group g = item.Groups["param2"];  
  27.                         string insertstr = ".Contains(@" + values.Length + ")";  
  28.                         s = DynamicQueryableLikeHelper.ChangePredicate(s, insertstr, gitem, ref offset);  
  29.                         ArrayList list = new ArrayList(values);  
  30.                         list.Add(g.Value.Replace("\'"""));  
  31.                         values = list.ToArray();//like对字符串操作所以没有考虑其他类型  
  32.  
  33.                     }  
  34.  
  35.                 }  
  36.             }  
  37.             return s;  
  38.         }  
  39.  
  40.         public static string ChangePredicate(string s, string insertstr, Group gitem, ref int offset)  
  41.         {  
  42.             string strremove = s.Remove(gitem.Index + offset, gitem.Length);  
  43.             s = strremove.Insert(gitem.Index + offset, insertstr);  
  44.             offset += insertstr.Length - gitem.Length;  
  45.             return s;  
  46.         }  
  47.     }  
  48.  
  49. 复制代码 

    在上面代码就是正则表达式的运用,很简单。其中唯一花费了我很多时间就是我在正则表达式匹配时和修改后的string的index不一致,本想用个假单方法实现,最后还是么有办法,就只有加入了一个offset偏移量,每次删除插入的差值的和。这里就把like ‘value’ 转化为 property.Contains(value);其实和like还是有差别的Contains其实只等于 ‘%value%’。但是这里提供了一个方案,同理我们可以实现为‘%value’ 为EndWith,‘vakue%’为StartWith。由于在我们的应用中一般常用的为Contains所以暂时还么有扩展其他两个,等有时间再做(实现也简单就是判断有么有%和他的位置选择我们所转化的函数名)。

     为了在Dlinq里扩展我在where方法里面加入了传入条件字符串的转化。并加入了参数ignoreLike,默认为False。我怕的扩展有某些问题暂时还么有测试出来了,还有就是不想改变原类库。

    在我的扩展中支持ShipCity like @1 、ShipCity like 'don'、ShipCity like @0 两种形式,原来类库里面只支持第三种形式。我的前两者形式都是当做字符串处理的,因为我们的like操作一般都是对字符串操作,如果要支持其他类型可以转化类型,我以前写了一个类型属性转化的类,可以实现,但是我想这个问题么有必要。

在类库里面该的方法有(已经注释了,还是贴出来看看调用):

代码
 
  1. public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values)  
  2.         {  
  3.             return (IQueryable<T>)Where((IQueryable)source, predicate, falsevalues);  
  4.             //TODO:添加False 
  5.         }  
  6.  
  7.         public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate,  
  8.                 bool ignoreLike, params object[] values)  
  9.         //TOD:增加的方法,ignoreLike  
  10.         {  
  11.             return (IQueryable<T>)Where((IQueryable)source, predicate, ignoreLike, values);  
  12.         }  
  13.  
  14.         public static IQueryable Where(this IQueryable source, string predicate, params object[] values)  
  15.         //TOD:增加的方法,ignoreLike,下面方法的忽略ignoreLike写法;  
  16.         {  
  17.             return Where(source, predicate, falsevalues);  
  18.         }  
  19.         public static IQueryable Where(this IQueryable source, string predicate,  
  20.                          bool ignoreLike, params object[] values)  
  21.         //TODO:改变参数ignoreLike  
  22.         {  
  23.             if (source == null) throw new ArgumentNullException("source");  
  24.             if (predicate == null) throw new ArgumentNullException("predicate");  
  25.             if (!ignoreLike)  
  26.             {  
  27.                 predicate = DynamicQueryableLikeHelper.CheckLikeSattement(predicate, ref values);  
  28.                 //TODO:增加语句;  
  29.             }  
  30.             LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType,  
  31.                         typeof(bool), predicate, values);  
  32.             return source.Provider.CreateQuery(  
  33.                 Expression.Call(  
  34.                     typeof(Queryable), "Where",  
  35.                     new Type[] { source.ElementType },  
  36.                     source.Expression, Expression.Quote(lambda)));  
  37.         }  
  38.  

只是TODO处就是我改变的。下面该测试测试下(数据库Northwind的orders表)。

 代码

  1. IQueryable<Orders> q = DataContext.Orders  
  2. .Where("ShipCity like @1 and ShipCity like @0 or ShipCity like 'don' and ShipCity like 'don' 
  3.           and ShipCity like 'don'  or EmployeeID1 =1", "test ", "test").OrderBy("ShipCity");  
  4.             //IQueryable<Orders> q = DataContext.Orders.Where("ShipCity.Contains(@0) ","test");  
  5.             Console.WriteLine(DataContext.GetCommand(q).CommandText);  
  6.        
 为了测试多个我邮箱投个懒就把ShipCity like 'don' 赋值了多次。
输出sql为: 
 
  1. 代码   
  2. SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID] AS [EmployeeID1], [t 0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[F reight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion] , [t0].[ShipPostalCode], [t0].[ShipCountry] FROM [dbo].[Orders] AS [t0] WHERE (([t0].[ShipCity] LIKE @p0) AND ([t0].[ShipCity] LIKE @p1)) OR (([t0].[Shi pCity] LIKE @p2) AND ([t0].[ShipCity] LIKE @p3) AND ([t0].[ShipCity] LIKE @p4)) OR ([t0].[EmployeeID] = @p5) ORDER BY [t0].[ShipCity]  
  3.    

如果在类库中有和你的字符串等冲突就可以用ignoreLike为真,避免我的扩展。

最后希望大家一起帮我测试下,如果有什么问题请留言和Email:破狼Email指出,我好改变。

附带:扩展后的Dynamic.cs下载