2012-02-17 102 views
9

我想使用EF從數據庫獲取記錄並將值分配給DTO類。請考慮Linq查詢的以下表格。將Linq查詢結果映射到DTO類

表A,表B,表C

對於每個表A記錄有TableB中多條記錄。對於每個TableB記錄,TableC中有多個記錄。 現在我的DTO的這個樣子

public class TableA_DTO 
{ 
    public int tableA_rowid { get; set; } 
    //remaining tableA field definitions 

    public List<TableB_DTO> TableB_records { get; set; } 
} 

public class TableB_DTO 
{ 
    public int tableB_rowid { get; set; } 
    //remaining tableB field definitions 

    public List<TableC_DTO> TableC_records { get; set; } 
} 

public class TableC_DTO 
{ 
    public int tableC_rowid { get; set; } 
    //remaining tableC field definitions 
} 

我的LINQ查詢看起來查詢結果是這樣的

var qry = from ent in TableA 
      select ent; 

在我的映射類我遍歷項目,像這樣:

foreach (var dataitem in query) 
    { 
     TableA_DTO dto = new TableA_DTO(); 
     dto.tableA_rowid = dataitem.ID; 
     //remaining field definitions here 
    } 

現在這適用於TableA中的所有字段,它從數據庫中提取一條記錄,併爲表TableA中的每個字段在TableA_DTO中設置所需的屬性。我還希望在TableA屬性字段中通過名稱TableB_records填充TableB屬性字段中的所有匹配記錄,並且還在TableB_DTO中填充TableB_DTO屬性中TableC的所有匹配記錄名稱TableC_records

可以這樣做嗎?我需要改變什麼?難道LINQ查詢或我做我的映射

感謝您的時間的方式......

+3

是否有任何理由不能使用實體框架POCO(又名DbContext,有時錯誤地稱爲Code First)?基本上,你能否消除對DTO的需求,並使用EF POCO? – JMarsch 2012-02-17 17:56:21

+1

您是否考慮過使用AutoMapper?根據您的DTO的不同,這可能與執行映射的兩行或三行代碼一樣簡單。 – Robaticus 2012-02-17 19:47:51

+0

@jMarsch:數據庫已經存在,所以去了edmx的方式 – user20358 2012-02-18 07:27:57

回答

6

我會將您的DTO從List更改爲IEnumerable,而不是在LINQ查詢中做所有事情。

var query = 
    from ent in TableA 
    select new TableA_DTO 
    { 
     TableAProperty = a.Property, 
     TableB_records = 
      from b in TableB 
      where ent.Key == b.Key 
      select new TableB_DTO 
      { 
       TableBProperty = b.Property, 
       TableC_records = 
        from c in TableC 
        where b.Key == c.Key 
        select new TableC_DTO 
        { 
         TableCProperty = c.Property 
        } 
      } 
    }; 
+0

但是,這個問題的觸發甚至超過了'N + 1'查詢;它觸發'M *(N + 1)+ 1'查詢幾乎肯定會導致非常差的性能。 – Steven 2012-02-17 20:10:29

+3

@Steven - 不正確。它只發送一個查詢。 – Aducci 2012-02-17 20:15:38

+0

是的,直到您開始迭代'TableB_records'和'TableV_records'屬性。仔細查看使用SQL分析器執行的單個查詢。你會注意到它缺少關於'TableB'和'TableC'的所有信息。 – Steven 2012-02-17 21:08:14

0

我會做一個工廠方法,即:TableA_DTO CreateDTO(TableAItem item);

利用這一點,你可以只重寫查詢as:

IEnumerable<TableA_DTO> = TableA.AsEnumerable().Select(CreateDTO); 

這會直接給你「DTO」對象的集合。這就是說,如果你使用的是實體框架,在這種情況下,在最近版本中添加的EF Code First可能更有用。

+0

什麼是CreateDTO?這是一堂課嗎?它的定義是什麼? – user20358 2012-02-17 18:15:50

+0

@ user20358它會是你寫的一個方法,完成任務。你仍然需要做這些任務,但它只限於一種方法(從實體 - > DTO轉換) – 2012-02-17 18:16:17

+0

@Reed:在'IQueryable '上調用'AsEnumerable()'將確保你拉下數據庫中的所有行。除非表格的行數少於一千行(及其所有數據),否則,除非您想要獲取所有記錄,否則這對性能而言會非常糟糕。 – Steven 2012-02-17 20:13:14

4

首先,我只需要問你是否可以使用Entity Framework 4.1和POCOs(DbContext)並避免使用DTO的altoghther?

假設答案是否定的,那肯定是因爲你沒有拉回所有的字段,或者你在某種程度上改變了數據的「形狀」。

在這種情況下,你可以改變你的LINQ查詢看起來是這樣的:

from t in table 
where ... 
select new DTOA() 
{ 
    TheDtoProperty = theTableProperty, 
    AndSoOn = AndSoOn 
}; 

做這種方式的好處:如果你打開SQL事件探查器,你應該看到,僅列那您請求將其轉換爲實際的SQL查詢。如果您先查詢所有列,然後拉取值,則所有列將被拉下線。

0

UPDATE

正如其他指出的,平坦化的結果(如下所示)與實體框架4.0工作時是不需要的,因爲它可以LINQ查詢就轉化爲一種有效的扁平的結果您。因此,只有在使用LINQ to SQL(或其他可能的其他LINQ提供程序)時才需要以下代碼。請注意,我只用EF over SQL Server進行了測試,而不是通過Oracle進行了測試,因爲這種行爲可能是LINQ提供程序特定的,這意味着Oracle提供商(仍處於測試階段)或商業Devart提供商可能仍然在執行N + 1.


你要做的是得到一組結構像樹的對象。如果沒有特別的注意,你會觸發對數據庫的許多查詢。有一層嵌套,你會觸發 N + 1查詢,但由於你的嵌套是兩層深,你將觸發 M×(N + 1)+ 1查詢,這幾乎肯定會對性能(不管你的數據集的大小是多少)。你想要的是確保只有一個查詢發送到數據庫。爲了確保這一點,您必須創建一箇中間查詢來平滑結果,就像您在舊SQL日期中所做的那樣,可以檢索樹狀數據:-)。 看看下面的例子:

var records = 
    from record in db.TableC 
    where ... // any filtering can be done here 
    select record; 

// important to call ToArray. This ensures that the flatterned result 
// is pulled in one single SQL query. 
var results = (
    from c in records 
    select new 
    { 
     tableA_rowid = c.B.A.Id, 
     tableA_Prop1 = c.B.A.Property1, 
     tableA_Prop2 = c.B.A.Property2, 
     tableA_PropN = c.B.A.PropertyN, 
     tableB_rowid = c.B.Id, 
     tableB_Property1 = c.B.Property1, 
     tableB_Property2 = c.B.Property2, 
     tableB_PropertyN = c.B.PropertyN, 
     tableC_rowid = c.Id, 
     tableC_Property1 = c.Property1, 
     tableC_Property2 = c.Property2, 
     tableC_PropertyN = c.PropertyN, 
    }) 
    .ToArray(); 

下一步是進行改造,在內存中的數據結構(使用匿名類型)轉換成DTO的樹結構對象:

// translate the results to DTO tree structure 
TableA_DTO[] dtos = (
    from aresult in results 
    group aresult by aresult.tableA_rowid into group_a 
    let a = group_a.First() 
    select new TableA_DTO 
    { 
     tableA_rowid = a.tableA_rowid, 
     tableA_Prop1 = a.tableA_Prop1, 
     tableA_Prop2 = a.tableA_Prop2, 
     TableB_records = (
      from bresult in group_a 
      group bresult by bresult.tableB_rowid into group_b 
      let b = group_b.First() 
      select new TableB_DTO 
      { 
       tableB_rowid = b.tableB_rowid, 
       tableB_Prop1 = b.tableB_Prop1, 
       tableB_Prop2 = b.tableB_Prop2, 
       TableC_records = (
        from c in group_b 
        select new TableC_DTO 
        { 
         tableC_rowid = c.tableC_rowid, 
         tableC_Prop1 = c.tableC_Prop1, 
         tableC_Prop2 = c.tableC_Prop2, 
        }).ToList(), 
      }).ToList() 
    }) 
    .ToArray(); 

作爲你可以看到,解決方案的第一部分實際上是這樣做的'舊'方式,當我們仍然手工編寫我們的SQL查詢時,這種方式就回來了。不過很好,一旦我們得到這種類型的內存數據集,我們可以再次利用LINQ(to Objects)來獲取我們想要的結構中的這些數據。

請注意,這也可以讓你做分頁和排序。這會有點棘手,但肯定不是不可能的。

+1

整個「扁平化」步驟完全沒有必要,因爲Entity Framework爲您完成這一步。使用@Aducci的策略將導致單個數據庫查詢從SQL返回平展行中的結果,然後它會自動將這些值組合爲分層結構。 – StriplingWarrior 2012-02-17 20:39:19

+0

@StriplingWarrior:經過一番測試,看起來你是完全正確的。實體框架在這裏讓我眼花繚亂:-)最後,它在LINQ to SQL方面表現優秀,因爲LINQ to SQL確實可以做N + 1個查詢。這確實很酷。 – Steven 2012-02-17 21:42:25

+0

是的,它看起來像LINQ to SQL處理深達一層的嵌套模式(TableA和TableB),但是比這更深,並且您最終爲TableC中的每個項目單獨往返。 – StriplingWarrior 2012-02-17 22:45:49