Вопрос

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

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

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

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

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

Еще я хочу отфильтровать этот дочерний список переводов стихов.

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

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

Это было полезно?

Решение

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

        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;

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

Спасибо всем за ваш вклад.

Другие советы

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

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

Используя метод расширения «Обновить» из Хукедонлинк

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;
    }
}

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

Затем выполните загрузку или включение 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;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top