2012-08-04 36 views
4

我已經閱讀了幾個關於「不允許在查詢中顯式構造實體類型」的錯誤問題,以及解決它的各種方法。使用LINQ插入/選擇 - 在查詢中獲取實體構造

我使用DBML在我的代碼自動生成的LINQ to SQL類,所以這將是偉大的,是能夠選擇並適當地插入數據。這是另一篇文章中提出的一種方法;在下面的示例中,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似乎效率非常低,只是讓我的代碼在那裏呆了很長時間。有一個更好的方法嗎?

回答

2

AsEnumerable()會做同樣的事情,在ToList()使加工成LINQ到對象的條件,但也不會浪費時間和存儲器,用於存儲所有這些第一。相反,當你遍歷它時,它會一次創建一個對象。

通常,您應該使用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。當與聚合方法一起完成時,通常是可以的,但否則它最終可能會變成多個數據庫調用(一個用於獲取不同的鍵值,然後每個值一個)。

考慮到上述情況(與我的elision不提及每一列)。如果沒有使用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。無論代碼的其他部分有多高效,這很快就會變成災難性的。

我們有兩種選擇。每種情況下最好的因素都不相同,所以我會給予這兩者,儘管第二種效率是最高的。

有時我們的最好的辦法是裝入所有相關的行,做分組記憶:通過我們關心(不着邊際的話,以上僅選擇列

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, 
     /* ... */ 
    }); 

最明顯的缺點,這是純粹的詳細程度。然而,它會保持數據庫中最好的db運行,​​同時不會像ToList()那樣浪費時間和內存,不會重複地敲擊db,並且將e_activeSession創建從linq2sql拖拽到linq2objects中,所以它應該是允許。 (順便說一下,.NET中的約定是以類名和成員名稱開頭的,並沒有技術原因,但這樣做意味着你可以匹配更多人的代碼,包括BCL和其他庫的代碼你用)。

編輯:順便說一句;我剛剛看到你的另一個問題。請注意,在某種程度上,這裏的AsEnumerable()正是導致該問題的一個變體。 Grok認爲,並且你會對不同的linq查詢提供者之間的界限進行深入研究。

+0

感謝您的非常詳細的答案,並解釋LINQ如何轉換爲SQL。 「AsEnumerable」真的加快了速度。我喜歡你的最後一個版本,因爲即使它是冗長的,它似乎讓我把SQL中的計算(聚合)與C#中的計算分開。最後,關於大寫:e_activeSession是表名中自動生成的類。 我現在有一些奇怪的'InvalidCastException'錯誤,但我將另存一天。 – 2012-08-04 02:12:02

+0

這裏一個非常有用的東西是運行SQL Profiler,特別是在你學習它的時候。大多數情況下,生成的SQL將與您預期的非常相似。有時甚至可能會更好(Linq在引入APPLY之前沒有多年的SQL經驗,所以它在我不想的時候「認爲」APPLY,但「APPLY」是最好的方法)。在極少數情況下,你會看到它做了一些可怕的事情,然後你想看看你的Linq查詢,並考慮它爲什麼會這樣做(比如當它將一個'group'變成一千個獨立的調用時)。 – 2012-08-04 08:45:07