4

「クエリでのエンティティタイプの明示的な構築は許可されていません」というエラーに関するいくつかの質問と、それを回避するためのさまざまな方法を読みました。

コードでDBMLの自動生成されたLINQtoSQLクラスを使用しているので、データを適切に選択して挿入できると便利です。これは別の投稿で提案されている1つのアプローチです。次の例では、e_activeSessionは、DataContext内のテーブルの自動生成された表現です。

var statistics =
    from record in startTimes
    group record by record.startTime into g
    select new e_activeSession
            {
                workerId = wcopy,
                startTime = g.Key.GetValueOrDefault(),
                totalTasks = g.Count(),
                totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
                minDwell = g.Min(o => o.record.dwellTime).GetValueOrDefault(),
                maxDwell = g.Max(o => o.record.dwellTime).GetValueOrDefault(),
                avgDwell = g.Average(o => o.record.dwellTime).GetValueOrDefault(),
                stdevDwell = g.Select(o => Convert.ToDouble(o.record.dwellTime)).StdDev(),
                total80 = g.Sum(o => Convert.ToInt16(o.record.correct80) + Convert.ToInt16(o.record.wrong80)),
                correct80 = g.Sum(o => Convert.ToInt16(o.record.correct80)),
                percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80))) /
                            g.Sum(o => Convert.ToInt16(o.record.correct80) + Convert.ToInt16(o.record.wrong80))
            };

上記はエラーをスローするので、私は次のことを試みました:

var groups =
    from record in startTimes
    group record by record.startTime
    into g
    select g;

var statistics = groups.ToList().Select(
    g => new e_activeSession
             {
                 workerId = wcopy,
                 startTime = g.Key.GetValueOrDefault(),
                 totalTasks = g.Count(),
                 totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
                 minDwell = g.Min(o => o.record.dwellTime).GetValueOrDefault(),
                 maxDwell = g.Max(o => o.record.dwellTime).GetValueOrDefault(),
                 avgDwell = g.Average(o => o.record.dwellTime).GetValueOrDefault(),
                 stdevDwell = g.Select(o => Convert.ToDouble(o.record.dwellTime)).StdDev(),
                 total80 = g.Sum(o => Convert.ToInt16(o.record.correct80) + Convert.ToInt16(o.record.wrong80)),
                 correct80 = g.Sum(o => Convert.ToInt16(o.record.correct80)),
                 percent80 = Convert.ToDouble(g.Sum(o => Convert.ToInt16(o.record.correct80))) /
                             g.Sum(o => Convert.ToInt16(o.record.correct80) + Convert.ToInt16(o.record.wrong80))
             });

ただし、これToListは非常に非効率的であり、コードを長時間そこに置いておくだけです。これを行うためのより良い方法はありますか?

4

1 に答える 1

2

AsEnumerable()処理をlinq-to-objectsに持ち込むという点で同じことを行いToList()ますが、最初にそれらすべてを保存するために時間とメモリを無駄にしません. 代わりに、それを繰り返すと、一度に 1 つずつオブジェクトが作成されます。

原則として、実際にリストが必要でない限り (たとえば、同じデータを複数回ヒットするため、リストがキャッシュとして機能する場合)を使用AsEnumerable()して、操作を別のソースからメモリに移動する必要があります。ToList()

これまでのところ、次のことがわかっています。

var statistics = (
  from record in startTimes
  group record by record.startTime
  into g
  select g;
  ).AsEnumerable().Select(
    g => new e_activeSession
    {
      workerId = wcopy,
      startTime = g.Key.GetValueOrDefault(),
      totalTasks = g.Count(),
      totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
      /* ... */
     });

しかし、もっと大きな問題があります。あなたも気をつけたいと思いgroup byます。集約メソッドと一緒に実行する場合、通常は問題ありませんが、それ以外の場合は、多くのデータベース呼び出し (キーの異なる値を取得するために 1 つと、各値ごとに 1 つ) になる可能性があります。

上記を考慮して(すべての列について言及しないことを省略します)。AsEnumerable()おそらく完全にクエリの外側にあるため(またはToList()あなたが何を持っているか)、使用wcopyしないと(定義されている場所がわかりません)、最初に生成されたSQLは(許可されている場合)、次のようになります。

select startTime, count(id), max(timeInSession), /* ... */
from tasks
group by startTime

これは、データベースによって非常に効率的に処理されるはずです (そうでない場合は、インデックスを確認し、生成されたクエリに対してデータベース エンジン チューニング アドバイザーを実行します)。

ただし、メモリ内でグループ化すると、最初に実行される可能性があります。

select distinct startTime from tasks

その後

select timeInSession, /* ... */
from tasks
where startTime = @p0

見つかった個別のすべてについてstartTime、それを として渡し@p0ます。これは、コードの残りの部分がどれほど効率的であっても、すぐに悲惨な結果になる可能性があります。

選択肢は 2 つあります。どちらが最適かは場合によって異なるため、両方を挙げますが、ここでは 2 番目が最も効率的です。

場合によっては、関連するすべての行をロードし、メモリ内でグループ化を行うことが最善の方法です。

var statistics =
  from record in startTimes.AsEnumerable()
  group record by record.startTime
  into g
  select new e_activeSession
  {
    workerId = wcopy,
    startTime = g.Key.GetValueOrDefault(),
    totalTasks = g.Count(),
    totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
    /* ... */
  };

関心のある列のみを選択することで、もう少し効率的にすることができます(上記がテーブル内のすべての列を使用する場合は関係ありません)

var statistics =
  from record in (
    from dbRec in startTimes
    select new {dbRec.startTime, dbRec.timeInSession, /*...*/}).AsEnumerable()
    group record by record.startTime
    into g
    select new e_activeSession
    {
      workerId = wcopy,
      startTime = g.Key.GetValueOrDefault(),
      totalTasks = g.Count(),
      totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
      /* ... */
    };

ただし、これが最善のケースになるとは思いません。これは、グループを列挙してから各グループを列挙する場合に使用します。すべてのグループで集計を行い、それらを列挙しない場合は、その集計作業をデータベースに保持することをお勧めします。データベースはそれらを得意としており、ネットワーク経由で送信されるデータの総量を大幅に削減します。この場合、私が考えることができる最善の方法は、それをミラーリングするがエンティティとして認識されないエンティティ タイプ以外の新しいオブジェクトを強制することです。このためだけに型を作成することもできます (これに対していくつかのバリアントを実行している場合に便利です)。それ以外の場合は、匿名型を使用します。

var statistics = (
  from record in startTimes
  group record by record.startTime
  into g
  select new{
    startTime = g.Key.GetValueOrDefault(),
    totalTasks = g.Count(),
    totalTime = g.Max(o => o.record.timeInSession).GetValueOrDefault(),
    /* ... */
  }).AsEnumerable().Select(
    d => new e_activeSession
    {
      workerId = wcopy,
      startTime = d.startTime,
      totalTasks = d.totalTasks,
      /* ... */
    });

これの明らかな欠点は、非常に冗長なことです。ただし、データベース内で操作を最適に実行し続ける一方で、時間とメモリを浪費ToList()したり、データベースに繰り返しアクセスしたり、e_activeSession作成を linq2sql から linq2objects にドラッグしたりすることはないため、許可する必要があります。

(ちなみに、.NET の規則では、クラス名とメンバー名は大文字で始めます。技術的な理由はありませんが、そうすることで、使用する BCL や他のライブラリのコードを含め、より多くの人のコードと一致することになります)。

編集:ちなみに2番目。私はちょうどあなたの他の質問を見ました。ある意味で、AsEnumerable()ここにあるのは、その問題の原因となった正確なバリアントであることに注意してください。それを理解すると、さまざまな linq クエリ プロバイダー間の境界について多くのことを理解できたことになります。

于 2012-08-04T01:08:51.413 に答える