Написание метода расширения, чтобы помочь с запросом отношений «многие ко многим» - PullRequest
12 голосов
/ 15 июня 2011

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

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

Сообщение

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

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

PostTag

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

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

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

Ответы [ 3 ]

2 голосов
/ 15 июня 2011

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

Edit - добавлен еще один оператор Any

1 голос
/ 30 мая 2013

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

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

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

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 или свойству объекта.оставленный элемент

Использование их будет выглядеть примерно так (Как и выше, я обернул каждый параметр в отдельной строке для удобства чтения)

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

1 голос
/ 20 июня 2011

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

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

...