Как фильтровать дочерние коллекции в Linq
-
22-07-2019 - |
Вопрос
Мне нужно отфильтровать дочерние элементы объекта в 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()
в качестве левого соединения, чтобы быть уверенным, что мы получим все стихи, даже если в них отсутствует перевод.