编写一个扩展方法来帮助查询多对多关系

本文关键字:帮助 查询 方法 关系 扩展 一个 | 更新日期: 2023-09-27 17:59:07

我正在尝试编写一个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时提供的任何帮助,或执行此查询的任何其他更有效的方法,都将不胜感激。

编写一个扩展方法来帮助查询多对多关系

我认为您的原始查询很好,您只需要处理重复的帖子。在末尾添加一个不同的。或者你可以使用这样的任意方法。

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语句

实现目标的最有效方法可能是使用内置的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;

这并不比之前的选项"更正确":当你发布问题时,你已经有了一些工作。但就语法和性能而言,这无疑是一种更整洁的方式。

实际上,我不得不想出自己的解决方案来解决这个问题,因为我处理的数据源与实体框架(odbc连接上的DBase4)不兼容,该框架具有相当多的多对多表关系,并且我不必写出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));
}

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

使用它们看起来像这样(就像上面一样,为了可读性,我把每个参数都包装在它自己的行上)

  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的方法语法,所以编写这些类型的扩展方法只是我现在认为的一种方式。