LINQ to entities – 构建where子句以在多对多关系中测试集合
所以,我正在使用Linqentity framework。 我有2个实体: Content
和Tag
。 他们彼此处于多对多的关系中。 Content
可以有很多Tags
, Tag
可以有很多Contents
。 所以我试着写一个查询来选择任何标签名称等于blah
所有内容
这些实体都将另一个实体的集合作为属性(但没有ID)。 这是我在努力的地方。 我确实有Contains
的自定义表达式(所以,无论谁可以帮助我,你可以假设我可以为一个集合做一个“包含”)。 我得到了这个表达式: http : //forums.microsoft.com/MSDN/ShowPost.aspx?PostID = 2670710&SiteID = 1
编辑1
我最终找到了自己的答案。
阅读了有关PredicateBuilder的内容 ,阅读了人们发给我的所有精彩post,在其他网站上发布,然后阅读更多关于Combining Predicates和Canonical Function Mapping的内容 ..哦,我从LINQ查询中的Calling函数中找到了一些内容(其中一些类来自这些页面)。
我终于有了解决方案! 虽然有一块有点被黑了……
让我们通过以下方式获取被攻击的一块:(
我不得不使用reflection器并复制标记为内部的ExpressionVisitor类。 然后我不得不对它进行一些小的改动,让它发挥作用。 我不得不创建两个exception(因为它是新的内部exception。我还必须更改ReadOnlyCollection()方法的返回:
return sequence.ToReadOnlyCollection();
至:
return sequence.AsReadOnly();
我会发布课程,但它很大,我不想把这个post弄得比它已经存在的更混乱。 我希望将来可以从我的库中删除该类,并且Microsoft将公开它。 继续…
我添加了一个ParameterRebinder类:
public class ParameterRebinder : ExpressionVisitor { private readonly Dictionary map; public ParameterRebinder(Dictionary map) { this.map = map ?? new Dictionary(); } public static Expression ReplaceParameters(Dictionary map, Expression exp) { return new ParameterRebinder(map).Visit(exp); } internal override Expression VisitParameter(ParameterExpression p) { ParameterExpression replacement; if (map.TryGetValue(p, out replacement)) { p = replacement; } return base.VisitParameter(p); } }
然后我添加了一个ExpressionExtensions类:
public static class ExpressionExtensions { public static Expression Compose (this Expression first, Expression second, Func merge) { // build parameter map (from parameters of second to parameters of first) var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => ps, p => pf); // replace parameters in the second lambda expression with parameters from the first var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); // apply composition of lambda expression bodies to parameters from the first expression return Expression.Lambda (merge(first.Body, secondBody), first.Parameters); } public static Expression> And (this Expression> first, Expression> second) { return first.Compose(second, Expression.And); } public static Expression> Or (this Expression> first, Expression> second) { return first.Compose(second, Expression.Or); } }
我添加的最后一节课是PredicateBuilder:
public static class PredicateBuilder { public static Expression> True() { return f => true; } public static Expression> False () { return f => false; } }
这是我的结果……我能够执行此代码并从我正在搜索的标签中获取具有匹配“标记”实体的结果“内容”实体!
public static IList GetAllContentByTags(IList tags) { IQueryable contentQuery = ... Expression> predicate = PredicateBuilder.False (); foreach (Tag individualTag in tags) { Tag tagParameter = individualTag; predicate = predicate.Or(p => p.Tags.Any(tag => tag.Name.Equals(tagParameter.Name))); } IQueryable resultExpressions = contentQuery.Where(predicate); return resultExpressions.ToList(); }
如果有人需要这方面的帮助,请告诉我,如果您希望我为此发送文件,或者只是需要更多信息。
总结一下……
contentQuery.Where( content => content.Tags.Any(tag => tags.Any(t => t.Name == tag.Name)) );
那是你所期待的那样吗?
我有点困惑。
这就是问题本身所要求的:
contentQuery.Where( content => content.Tags.Any(tag => tag.Name == "blah") );
我不确定究竟是什么思考过程才能找到提问者的代码,而且我并不完全确定它到底在做什么。 我真正确定的一件事是.AsQueryable()调用是完全没必要的 – 或者.Tags已经是IQueryable,或者.AsQueryable()只是为你假装 – 在哪里添加额外的调用没有必要。
该错误与’tags’变量有关。 LINQ to Entities不支持作为值集合的参数。 简单地调用tags.AsQueryable() – 正如一个更早的答案所建议的那样 – 也不会起作用,因为默认的内存LINQ查询提供程序与LINQ to Entities(或其他关系提供程序)不兼容。
作为解决方法,您可以使用表达式API手动构建filter(请参阅此论坛post )并按如下方式应用它:
var filter = BuildContainsExpression(e => e.Name, tags.Select(t => t.Name)); var query = source.Where(e => e.NestedValues.Any(filter));
tags.Select(testTag => testTag.Name)
tags变量从哪里初始化? 它是什么?
注意:请编辑问题本身,而不是回答答案 – 这不是讨论主题,他们可以随时重新排序
如果您要搜索标记有任何一组标记的所有内容:
IEnumerable otherTags; ... var query = from content in contentQuery where content.Tags.Intersection(otherTags).Any() select content;
看起来您可能正在使用LINQ To SQL,在这种情况下,如果您编写存储过程来执行此操作可能会更好:使用LINQ执行此操作可能无法在SQL Server上运行 – 它很可能会尝试从contentQuery
下拉所有contentQuery
并获取所有.Tags
集合。 不过,我必须设置一个服务器来检查它。