编写扩展方法以帮助查询多对多关系

时间:2011-06-15 09:22:44

标签: c# linq linq-to-entities extension-methods

我正在尝试编写一个extension method,以便重构我正在编写的linq多对多查询。我正在尝试检索Post(s)的集合,这些集合已被作为参数传递给我的方法的集合中的任何Tag(s)标记。

以下是相关实体及其部分属性:

  

发表

     

标量属性PostIDPostDate

     

导航属性PostTags

     

PostTag

     

标量属性PostTagIDPostIDTagID

     

导航属性PostTag

     

代码

     

标量属性TagID

     

导航属性PostTags

这是我目前使用的查询效果很好:

public IEnumerable<Post> GetPostsByTags(IEnumerable<Tag> tags)
{
    return from pt in context.PostTags
           from t in tags
           where pt.TagID == t.TagID &&
                 pt.Post.PostDate != null
           orderby pt.Post.PostDate descending
           select pt.Post;               
}   

这是extension method我努力创造的(可能是不正确的)开始:

public static IEnumerable<TResult> SelectRange<TSource, TResult>(
    this IEnumerable<TSource> collection,
    Func<IEnumerable<TSource>, IEnumerable<TResult>> selector)
{
    return selector(collection);
}

理想的原始查询简化:

public IEnumerable<Post> GetPostsByTags(IEnumerable<Tag> tags)
{
    return from p in context.Posts
           where p.PostTags.SelectRange(x => ?) &&
                 p.PostDate != null                    
           orderby p.PostDate descending
           select p;
}

在撰写此extension method或其他任何更有效的方式来执行此查询时,我们将非常感谢您提供帮助。

3 个答案:

答案 0 :(得分:2)

我认为您的原始查询很好,您只需要处理重复的帖子。添加一个截然不同的结尾。或者您可以使用Any方法。

public IEnumerable<Post> GetPostsByTags(IEnumerable<Tag> tags)
{
    return from p in context.Posts
           where p.PostTags.Any(pt => tags.Any(t => t.TagID == pt.TagID)) &&
                 p.PostDate != null                    
           orderby p.PostDate descending
           select p;
}

编辑 - 添加了另一个Any语句

答案 1 :(得分:1)

实现目标的最有效方法可能是使用专用于这种多对多关系的内置join子句:

from pt in PostTags
where pt.Post.PostDate != null
join t in tags on pt.TagID equals t.TagID
orderby pt.Post.PostDate descending
select pt.Post;

它不比以前的选项“更正确”:当你发布问题时,你已经有了一些工作。但它肯定是使其在语法和性能方面发挥作用的更简洁的方式。

答案 2 :(得分:1)

我实际上必须提出我自己的解决方案来解决这个问题,因为我正在处理一个与Entity Framework(odbc连接上的DBase 4)不兼容的数据源,它有很多表关系,而不是必须写出一个长期绘制的连接和linq的组块我创建了两个扩展方法

  

现在请注意,我只处理从一个方向读取的内容   数据源本质上是一个数据集,该程序仅用于   历史数据,不接受新的输入或更改,所以我不是   确定这些扩展方法能够胜任任何实际的   数据更新插入或删除等

(为了便于阅读,我将每个参数包装在它自己的行上)

public static IEnumerable<TResult> ManyToManyGroupBy
    <TLeft, TRight, TMiddle, TLeftKey, TRightKey, TGroupKey, TResult>
    (
      this IEnumerable<TLeft> Left, 
      IEnumerable<TRight> Right, 
      IEnumerable<TMiddle> Middle, 
      Func<TLeft, TLeftKey> LeftKeySelector, 
      Func<TMiddle, TLeftKey> MiddleLeftKeySelector, 
      Func<TRight, TRightKey> RightKeySelector, 
      Func<TMiddle, TRightKey> MiddleRightKeySelector, 
      Func<TLeft, TGroupKey> GroupingSelector, 
      Func<TGroupKey, IEnumerable<TRight>, TResult> Selector
    )
{
  return Left
   .Join(Middle, LeftKeySelector, MiddleLeftKeySelector, (L, M) => new { L, M })
   .Join(Right, LM => MiddleRightKeySelector(LM.M), RightKeySelector, (LM, R) => new { R, LM.L })
   .GroupBy(LR => GroupingSelector(LR.L))
   .Select(G => Selector(G.Key, G.Select(g => g.R)));
}

public static IEnumerable<TResult> ManyToManySelect
    <TLeft, TRight, TMiddle, TLeftKey, TRightKey, TResult>
    (
      this IEnumerable<TLeft> Left, 
      IEnumerable<TRight> Right, 
      IEnumerable<TMiddle> Middle, 
      Func<TLeft, TLeftKey> LeftKeySelector, 
      Func<TMiddle, TLeftKey> MiddleLeftKeySelector, 
      Func<TRight, TRightKey> RightKeySelector, 
      Func<TMiddle, TRightKey> MiddleRightKeySelector, 
      Func<TLeft, TRight, TResult> Selector
    )
{
  return Left
   .Join(Middle, LeftKeySelector, MiddleLeftKeySelector, (L, M) => new { L, M })
   .Join(Right, LM => MiddleRightKeySelector(LM.M), RightKeySelector, (LM, R) => new { R, LM.L })
    .Select(LR => Selector(LR.L, LR.R));
}

它们是相当长的方法,但它们涵盖了通过中间表将两个表连接在一起,以及按任何特定的左项或左项的属性进行分组

使用它们看起来像这样(就像上面我为了便于阅读而将每个参数包装在它自己的行上)

  var EmployeesCoursesTest = Employees.ManyToManySelect
  (
   Courses, 
   CourseParticipations, 
   E => E.SS, 
   CP => CP.SS, 
   C => C.EVENTNO, 
   CP => CP.EVENTNO, 
   (E, C) => new
   {
    C.EVENTNO,
    C.START_DATE,
    C.END_DATE,
    C.HOURS,
    C.SESSIONS,
    Trainings.First(T => T.TRGID == C.TRGID).TRAINING,
    Instructors.First(I => I.INSTRUCTID == C.INSTRUCTID).INSTRUCTOR,
    Locations.First(L => L.LOCATIONID == C.LOCATIONID).LOCATION,
    Employee = E
   }
  );

  var EmployeesCoursesGroupByTest = Employees.ManyToManyGroupBy
  (
   Courses, 
   CourseParticipations, 
   E => E.SS, 
   CP => CP.SS, 
   C => C.EVENTNO, 
   CP => CP.EVENTNO, 
   E => E,
   (E, Cs) => new
   {
    Employee = E,
    Courses = Cs
   }
  );

也许它会帮助你我自己倾向于远离查询语法,所以我几乎完全使用Linq的方法语法,所以编写这些扩展方法只是我现在想的一种方式。