Подтвердить что ты не робот

Написание метода расширения, помогающего запросить отношения "многие ко многим"

Я пытаюсь написать extension method, чтобы реорганизовать запрос linq many-to-many, который я пишу. Я пытаюсь получить коллекцию Post(s), которые были помечены любым из Tag(s) в коллекции, переданной в качестве параметра для моего метода.

Вот соответствующие объекты вместе с некоторыми из их свойств:

Сообщение

Скалярные свойства: PostID, PostDate

Свойства навигации: PostTags

PostTag

Скалярные свойства: PostTagID, PostID, TagID

Свойства навигации: Post, Tag

Тег

Скалярные свойства: 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 или любого другого более эффективного способа выполнения этого запроса будет с благодарностью.

4b9b3361

Ответ 1

Я думаю, что ваш оригинальный запрос в порядке, вам просто нужно обрабатывать повторяющиеся сообщения. Добавьте выделение до конца. Или вы можете использовать метод 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;
}

Изменить - добавлено другое Любое утверждение

Ответ 2

Наиболее эффективным способом достижения вашей цели является использование встроенного предложения 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;

Это не "более правильно", чем предыдущие варианты: у вас уже было что-то, когда вы отправляли свой вопрос. Но это, безусловно, более быстрый способ заставить его работать с точки зрения синтаксиса и производительности.

Ответ 3

На самом деле мне пришлось придумать собственное решение этой проблемы, потому что я имею дело с источником данных, который НЕ совместим с Entity Framework (DBase 4 на подключении odbc), который имеет довольно много-много для многих табличных отношений, и вместо того, чтобы записывать длинный вытянутый блок объединения и группы linq, я создал два метода расширения

Теперь, разумеется, я имею дело только с одним datasource в основном набор данных, эта программа предназначена только для исторических данных и не принимает новые данные или изменения, поэтому я не насколько хорошо эти методы расширения будут соответствовать любым действительным обновления данных вставляются или удаляются и т.д.

(Я обернул каждый параметр в своей строке для чтения)

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, поэтому писать такие методы расширения - это всего лишь способ, который я сейчас думаю.