2012-02-17 103 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