Linqで子コレクションをフィルターする方法
-
22-07-2019 - |
質問
単一のlinqクエリを使用して、linqのエンティティの子要素をフィルタリングする必要があります。これは可能ですか?
2つの関連テーブルがあるとします。 VersesおよびVerseTranslations。 LINQ to SQLによって作成されたエンティティは、VerseTranslationのコレクションである子オブジェクトを含むVerseオブジェクトを持っているようなものです。
次のlinqクエリがある場合
var res = from v in dc.Verses
where v.id = 1
select v;
IDが1で、各verseオブジェクトにVerseTranslationsのすべての子オブジェクトが含まれているVersesのコレクションを取得します。
やりたいことは、Verse Translationsの子リストをフィルタリングすることです。
これまでのところ、私が思いついた唯一の方法は、新しいType 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
解決
だから私はついにシラーズから与えられたポインタのおかげで機能するようになりました。
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));
拡張メソッド&quot; Update&quot;を使用して 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;
}
}
これがデータベースからのものである場合、最初のステートメントを実行できます。
次に、Where句を使用してVerseTranslationsのLoadまたはIncludeを実行します。
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
を翻訳のフィルタされたリストとペアにします。言語ごとに1つの翻訳のみが必要な場合は、 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 };
Verseに複数の翻訳がある場合、これは&quot; row&quot;を返します。詩/翻訳のペアごと。 DefaultIfEmpty()
を左結合として使用して、翻訳がない場合でもすべてのVerseを取得できるようにします。