最近有个项目用Linq做的,有个复合搜索模糊查询的功能,有点麻烦,绕了好几个弯,最后是解决了,在这里分享一下我的处理过程,如果大家有更好的办法也请给我介绍一下。我用Linq还不熟,好多东西边查资料边做的。
应用场景如下图,多条件复合搜索,很常见吧(但Linq搞这个还真是麻烦):
阶段一:
首先是找到了李永京(YJingLee)前辈的《LINQ体验(17)——LINQ to SQL语句之动态查询》一文,利用Lambda表达式树可以进行动态查询。
写了个方法进行复合查询,动态组合条件,生成Lambda表达式。
Code
1 /// <summary>
2 /// 这个方法带分页功能,通过输入的键值对NVC进行复合查询
3 /// </summary>
4 List<UserT_TractInfo> GetPagedObjectsByNVC(int startIndex, int pageSize, NameValueCollection nvc, bool isAnd)
5 {
6 IQueryable<UserT_TractInfo> query = Consulting.Instance.UserT_TractInfo;
7 query.Where(t => t.IsDel == 0).Where(t => t.IsAuditing == 1);//审核和逻辑删除
8
9 Expression condition = null;
10 ParameterExpression param = Expression.Parameter(typeof(UserT_TractInfo), "c");
11 int propertyCount = 0;
12 foreach (string key in nvc)
13 {
14 Expression right = Expression.Constant(nvc[key]);//键
15 string keyProperty = key;//属性
16 if (typeof(UserT_TractInfo).GetProperty(keyProperty) != null)//当对象存在此属性时(因为键值对可能还有很多其他的参数,例如page)
17 {
18 Expression left = Expression.Property(param, typeof(UserT_TractInfo).GetProperty(keyProperty));//建立属性
19 Expression filter = Expression.Equal(left, right);//过滤器
20 if (condition == null)
21 {
22 condition = filter;
23 }
24 else
25 {
26 if (isAnd)
27 {
28 condition = Expression.And(condition, filter);
29 }
30 else
31 {
32 condition = Expression.Or(condition, filter);
33 }
34 }
35 propertyCount++;
36 }
37 }
38 //以上foreach组合了各个有效的键值对对应的conditionExpression,复合查询最重要的组合工作就这么完了
39 if (propertyCount > 0)
40 {
41 Expression pred = Expression.Lambda(condition, param);
42 MethodCallExpression whereCallExpression = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(UserT_TractInfo) }, Expression.Constant(query), pred);
43
44 return Consulting.Instance.UserT_TractInfo.AsQueryable().Provider.CreateQuery<UserT_TractInfo>(whereCallExpression).OrderByDescending(t => t.ID).Skip(startIndex - 1).Take(pageSize).ToList();//查询出结果
45 }
46 else
47 {
48 return Consulting.Instance.UserT_TractInfo.OrderByDescending(t => t.ID).Skip(startIndex - 1).Take(pageSize).ToList();//如果没有有效键值对,则返回全部结果
49 }
50 }
51
搞了半天本来很兴奋的,之后才知道Lambda表达式是写不出.Contains()的,我的心瓦凉瓦凉的。
阶段二:
虽然李永京的文章没给我多少帮助,但它后面有个回复很有价值:“用微软提供的System.Linq.Dynamic方便点。”
很快找到了对应例子和Dynamic.cs,也找到了《Linq to SQL Dynamic 动态查询》,有更细致的例子,可惜Dynamic.cs也是不能使用like的,恨啊!
Code
return Consulting.Instance.UserT_TractInfo.Where("b_number == @0","P(2007)031").OrderByDescending(t => t.ID).Skip(startIndex - 1).Take(pageSize).ToList();
代码很容易,但没什么用:(
阶段三:
中文的实在是找不到了,在MS的官方BBS上找到了个链接,非常有用!《dynamic linq queries / dynamic where clause (part 2) 》,这个老外扩展了Dynamic.cs,写了个PredicateExtensions类,虽然不知道他是怎么想出来的,但确实有效!
这里放出核心代码,很容易看懂,简单就是美!
Code
1 searchPredicate = PredicateExtensions.True<UserT_TractInfo>();
2 foreach (string key in nvcParam)
3 {
4 string condition = string.Empty;
5 switch (key)
6 {
7 case "b_number":
8 condition = nvcParam[key];
9 searchPredicate = searchPredicate.And(u => u.B_number.Contains(condition));
10 break;
11 case "b_address":
12 condition = nvcParam[key];
13 searchPredicate = searchPredicate.And(u => u.B_address.Contains(condition));
14 break;
15 case "b_canton":
16 condition = nvcParam[key];
17 searchPredicate = searchPredicate.And(u => u.B_canton.Contains(condition));
18 break;
19 case "a_status":
20 condition = nvcParam[key];
21 searchPredicate = searchPredicate.And(u => u.A_status.ToString().Contains(condition));
22 break;
23 case "b_area":
24 condition = nvcParam[key];
25 searchPredicate = searchPredicate.And(u => u.B_area.Contains(condition));
26 break;
27 case "c_clinchdate":
28 condition = nvcParam[key];
29 searchPredicate = searchPredicate.And(u => u.C_clinchdate.Contains(condition));
30 break;
31 default:
32 break;
33 }
34 }
35
36 return Consulting.Instance.UserT_TractInfo.Where(searchPredicate).OrderByDescending(t => t.ID).Skip(startIndex - 1).Take(pageSize).ToList();
下面是我写了注释后的PredicateExtensions,我说不清楚构造函数的True和False具体是怎么起作用的,但结果就是我的注释那样,在复合查询写条件时很重要(不过目前全写AND就完成复合查询了,我还没搞多关键词OR的那种):
Code
1 /// <summary>
2 /// 构造函数使用True时:单个AND有效,多个AND有效;单个OR无效,多个OR无效;混合时写在AND后的OR有效
3 /// 构造函数使用False时:单个AND无效,多个AND无效;单个OR有效,多个OR有效;混合时写在OR后面的AND有效
4 /// </summary>
5 public static class PredicateExtensions
6 {
7 public static Expression<Func<T, bool>> True<T>() { return f => true; }
8
9 public static Expression<Func<T, bool>> False<T>() { return f => false; }
10
11 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
12 {
13 var invokedExpression = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
14
15 return Expression.Lambda<Func<T, bool>>(Expression.Or(expression1.Body, invokedExpression), expression1.Parameters);
16 }
17
18 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
19 {
20 var invokedExpression = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
21
22 return Expression.Lambda<Func<T, bool>>(Expression.And(expression1.Body, invokedExpression), expression1.Parameters);
23 }
24 }
不知道这篇文章是否适合放在园子首页,我想这个是有一定价值的,我在园子里貌似没找到这样的解决方法:)
posted @ 2008-08-03 20:26
肖坤 阅读(2726)
评论(25) 编辑 收藏 网摘 所属分类:
Linq
发表评论
模糊查询不能使用Contains嘛。
多条件 多加几个WHere条件吧。
from u in Consulting.Instance.UserT_TractInfo
where u => u.B_number.Contains(condition) &&
u => u.B_address.Contains(condition) &&
u => u.B_canton.Contains(condition);
有区别吗?
好像Linq并不太适合做动态查询呀。
我觉得可以放在首页的,支持。
啥的。。。多几个WHERE 方法 会自动加上条件的。。估计楼主还没很好了解Linq运作方式
这种查询方法太麻烦了,不好控制。
还是像HQL 这种查询语言好用点。
@violet250
这里有个问题,当最终用户可能用一个条件,也可能用全部条件时,语句并不同,而这个组合非常多时,你怎么办...并不是你简单的加几个where,要不然楼主也不用费劲写这个东西了.
Linq 里面的Contains要反过来用,不过不是用在模糊查询上,是批量查询
#12楼 [
楼主]2008-08-04 22:28 |
也许我这里的例子看不到它和WHERE的区别,不过请注意我外层使用的是foreach,它的条件数量是不固定的,而且它可以做到子条件中使用OR进行多关键词的查询,这个用传统的LINQ就很难做到了。
灵活性还是相当可观的。
痛頭的很呀!linq沒有多表關聯,動態條件查詢目前還沒有很好的解決方案.所以我個對他沒辦法了,我的建議到把他寫在存儲過程裡,然後用linq調用存儲過程,但這個冶標不冶本,期待樓主能給我們帶來更好的文章!
#14楼 [
楼主]2008-08-06 18:25 |
@ico
如果还是要用存储过程,那干脆还是用CodeSmith生成DAL,全部回归传统吧。。。用LINQ不就是看中它的“美观大方”吗
我也刚刚才开始研究LINQ的相关的东西.
看了你的文章.然后自己又研究了下.
发现你说的阶段一和阶段二也可以实现string.Contains()的方法的.代码如下.
NorthwindDataContext d = new NorthwindDataContext();
//linq
var cas = from s in d.Categories
where s.CategoryID > 1 && s.CategoryName.Contains("Con")
select s;
Response.Write("LinQ:<br/>");
foreach (Categories c in cas)
{
Response.Write(string.Format("{0}:{1}<br/>", c.CategoryID, c.CategoryName));
}
//linq.Dynamic
List<Categories> li = d.Categories.Where("CategoryID > 1 and CategoryName.Contains(\"Con\")").ToList();
Response.Write("<br/>Dynamic LinQ:<br/>");
foreach(Categories c in li)
{
Response.Write(string.Format("{0}:{1}<br/>", c.CategoryID, c.CategoryName));
}
//Expression Tree
IQueryable<Categories> categories = d.Categories;
//构造 s => s.CategoryID > 1 && s.CategoryName.Contains("Con")
ParameterExpression param = Expression.Parameter(typeof(Categories), "s");
//选择s.CategoryID
Expression selector = Expression.Property(param, typeof(Categories).GetProperty("CategoryID"));
//组合条件 s.CategoryID > 1
Expression FirstCondition = Expression.GreaterThan(selector, Expression.Constant(1, typeof(int)));
//组合条件 s.CategoryName.Contains("Con")
Expression SecondCondition = Expression.Call(
Expression.Property(param, typeof(Categories).GetProperty("CategoryName"))
, typeof(string).GetMethod("Contains")
, Expression.Constant("Con", typeof(string)));
//组合条件 s.CategoryID > 1 && s.CategoryName.Contains("Con")
Expression Condition = Expression.And(FirstCondition, SecondCondition);
Expression pred = Expression.Lambda(Condition, param);
//调用Where
Expression expr = Expression.Call(typeof(Queryable), "Where",
new Type[] { typeof(Categories) },
Expression.Constant(categories), pred);
li = d.Categories.AsQueryable()
.Provider.CreateQuery<Categories>(expr).ToList();
Response.Write("<br/>Expression Tree:<br/>");
foreach (Categories c in li)
{
Response.Write(string.Format("{0}:{1}<br/>", c.CategoryID, c.CategoryName));
}
如果有什么不对,请见谅.我也是个新手.
忘记写输出了..
用的是SQL SERVER里面的自带库-Northwind.
下面是上面代码的输出
--------------------------
LinQ:
2:Condiments
3:Confections
Dynamic LinQ:
2:Condiments
3:Confections
Expression Tree:
2:Condiments
3:Confections
#17楼 [
楼主]2008-08-26 17:32 |
@Alex.XL
这个方法是可以的,但好象还做不到多条件组合的查询。
我的主要目标是,在查询条件的关键词、数量都不确定的时候,可以有一种较优美的方法实现,而不必搞一大段if{}else if{}else if{}else{}这样的语句出来。
我查询的环境是,通过类似
/search.aspx?b_number=p&b_canton=%行政区&a_status=4&b_area=片区&c_clinchdate=2006&order=UnitPrice
这样一次传N个不同字段名的参数来动态实现复合查询,传统的组合SQL语句也有些判断语句的,而且条件越多就越难以进行测试。我的这篇POST意义就是说明我自己是如何解决这个问题的。
@肖坤
哦..原来如此..
不过.那么长的SWITCH语句...
也可以进行继续优化把.
#19楼 [
楼主]2008-08-28 09:17 |
@Alex.XL
switch里每个case中只有2条语句,很美了:)
请问nvcParam是不是二位数组,我是LINQ新学者,楼主能不能把完整的例子贴上来啊,谢谢了
#21楼 [
楼主]2008-09-04 12:19 |
@-可乐
nvcParam是NameValueCollection类型,是键值对的一种,用它可以传递key和value,你可以查查相关的信息
--引用--------------------------------------------------
-可乐: 请问nvcParam是不是二位数组,我是LINQ新学者,楼主能不能把完整的例子贴上来啊,谢谢了
--------------------------------------------------------
ExameThemeDataContext lDB = new ExameThemeDataContext();
List<Model.ET_COURSE_MSTR> lList = new List<Model.ET_COURSE_MSTR>();
var lLqList = lDB.ET_COURSE_MSTR.Where(condition).Select("New(vcCOURS_CODE,nvcNAME,nvcDESC,nvcPUBLISH,vcREG_CODE,vcUSER_ID,dtDO_TIME)").GetEnumerator();
while (lLqList.MoveNext())
{
Model.ET_COURSE_MSTR lModel = new Model.ET_COURSE_MSTR();
ET_LINQ.ET_COURSE_MSTR lLqModel = (ET_LINQ.ET_COURSE_MSTR)lLqList.Current;
lModel.vcCOURS_CODE = lLqModel.vcCOURS_CODE;
lList.Add(lModel);
}
无法将类型为“DynamicClass1”的对象强制转换为类型“ET_LINQ.ET_COURSE_MSTR”。
太复杂了。。。。我的想法可能是比较简单,但是我想肯定会有人更愿意用我这种方法的:
var rs=from p in ... select new{p.C1,p.C2};
//假定有两个文本框
if(txt1.Text!="")
rs=rs.Where(p.C1.Contains(txt1.Text));
if(txt2.Text!="")
rs=rs.Where(p.C2==txt2.Text);
更多的动态条件也是一样。 其中contains 会被翻译成like。
posted on 2008-12-17 18:11
Documents 阅读(2982)
评论(0) 编辑 收藏 所属分类:
Linq 、
ASP.NET