Как отфильтровать дочерние коллекции в Linq - PullRequest
3 голосов
/ 04 августа 2009

Мне нужно отфильтровать дочерние элементы сущности в linq, используя один запрос linq. Это возможно?

Предположим, у меня есть две связанные таблицы. Стихи и стихи Переводы. Сущность, созданная LINQ to SQL, такова, что у меня есть объект Verse, содержащий дочерний объект, который является коллекцией VerseTranslation.

Теперь, если у меня есть следующий запрос linq

var res = from v in dc.Verses
                  where v.id = 1
                  select v;

Я получаю коллекцию стихов с идентификатором 1, и каждый объект стиха содержит все дочерние объекты из VerseTranslations.

Я также хочу отфильтровать этот дочерний список Verse Translations.

Пока что единственный способ, которым я смог придумать, - это использовать новый тип Anonymous или иным образом. Следующим образом

var res= from v in dc.Verses
                   select new myType
                   {
                       VerseId = v.VerseId,
                       VText = v.Text,
                       VerseTranslations = (from trans in v.VerseTranslations
                                               where languageId==trans.LanguageId
                                               select trans
                   };

Приведенный выше код работает, но мне пришлось объявить новый класс для него. Нет ли способа сделать это таким образом, чтобы фильтрация дочерней таблицы могла быть включена в первый запрос linq, чтобы не нужно было объявлять новые классы.

С уважением, MAC

Ответы [ 4 ]

8 голосов
/ 05 августа 2009

Так что я наконец-то заставил его работать благодаря указателям, которые дал Шираз.

        DataLoadOptions options = new DataLoadOptions();
        options.AssociateWith<Verse>(item => item.VerseTranslation.Where(t => languageId.Contains(t.LanguageId)));

        dc.LoadOptions = options;

        var res = from s in dc.Verse
                   select s;

Это не требует проекции или использования новых классов расширения.

Спасибо всем, кто вас заинтересовал.

0 голосов
/ 04 августа 2009

Фильтрация по вложенной коллекции объекта,

var res = dc.Verses
            .Update(v => v.VerseTranslations 
                      =  v.VerseTranslations
                          .Where(n => n.LanguageId == languageId));

С помощью метода расширения «Обновление» из HookedOnLinq

public static class UpdateExtensions {

    public delegate void Func<TArg0>(TArg0 element);

    /// <summary>
    /// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
    /// </summary>
    /// <typeparam name="TSource">The source element type.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="update">The update statement to execute for each element.</param>
    /// <returns>The numer of records affected.</returns>
    public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update) {
        if (source == null) throw new ArgumentNullException("source");
        if (update == null) throw new ArgumentNullException("update");
        if (typeof(TSource).IsValueType) 
            throw new NotSupportedException("value type elements are not supported by update.");

        int count = 0;
        foreach(TSource element in source) {
            update(element);
            count++;
        }
        return count;
    }
}
0 голосов
/ 04 августа 2009

Нет ли способа сделать это таким таким образом, что фильтрация на дочерний стол может быть включен в первый запрос linq, так что нет новых классы должны быть объявлены?

Технически, ответ - нет. Если вы пытаетесь вернуть больше данных, чем может вместить один объект сущности (Verse, VerseTranslation), вам потребуется какой-то объект для «проецирования». Однако вы можете обойтись без явного объявления myType, используя анонимный тип:

var res = from v in dc.Verses
          select new
          {
              Verse = v,
              Translations = (from trans in v.VerseTranslations
                              where languageId==trans.LanguageId
                              select trans).ToList()
          };

var first = res.First();
Console.WriteLine("Verse {0} has {1} translation(s) in language {2}.",
    first.Verse.VerseId, first.Translations.Count, languageId);

Компилятор сгенерирует класс с соответствующими типами свойств Verse и Translations для вас. Вы можете использовать эти объекты практически для чего угодно, если вам не нужно ссылаться на тип по имени (например, для возврата из именованного метода). Поэтому, хотя вы технически не «объявляете» тип, вы все равно используете новый тип, который будет сгенерирован в соответствии с вашей спецификацией.

Что касается использования одного запроса LINQ, все зависит от того, как вы хотите структурировать данные. Мне кажется, ваш исходный запрос имеет больше смысла: соедините каждый Verse с отфильтрованным списком переводов. Если вы ожидаете только один перевод для каждого языка, вы можете использовать SingleOrDefault (или FirstOrDefault), чтобы сгладить свой подзапрос, или просто использовать SelectMany, например:

var res= from v in dc.Verses
         from t in v.VerseTranslations.DefaultIfEmpty()
         where t == null || languageId == t.LanguageId
         select new { Verse = v, Translation = t };

Если в стихе есть несколько переводов, будет возвращаться «строка» для каждой пары «Стих / Перевод». Я использую DefaultIfEmpty() в качестве левого соединения, чтобы убедиться, что мы получим все стихи, даже если они пропустили перевод.

0 голосов
/ 04 августа 2009

Если это исходит из базы данных, вы можете выполнить свой первый оператор.

Затем выполните загрузку или включение VerseTranslations с предложением Where.

http://msdn.microsoft.com/en-us/library/bb896249.aspx

Есть ли в вашей модели отношения между Verse и VerseTranslations. В этом случае это может сработать:

var res= from v in 
dc.Verses.Include("VerseTranslations").Where(o => languageId==o.LanguageId)
select v;
...