C# 将Linq查询结果映射到DTO类 < >我想使用EF从数据库中获取记录,并将这些值分配给DTO类。

C# 将Linq查询结果映射到DTO类 < >我想使用EF从数据库中获取记录,并将这些值分配给DTO类。,c#,linq,linq-to-sql,linq-to-entities,C#,Linq,Linq To Sql,Linq To Entities,表A,表B,表C 对于每个TableA记录,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; } }

表A,表B,表C

对于每个TableA记录,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
}
在我的映射类中,我循环查询结果中的项,如下所示:

    foreach (var dataitem in query)
    {
        TableA_DTO dto = new TableA_DTO();
        dto.tableA_rowid =  dataitem.ID;
        //remaining field definitions here
    }
现在,这适用于TableA中的所有字段,它从数据库中取出一条记录,并为TableA中的每个字段设置TableA_DTO中所需的属性。我还希望通过名称TableB_记录填充TableA属性字段中TableB中的所有匹配记录,并通过名称TableC_记录填充TableB_DTO属性中TableC中的所有匹配记录

这能做到吗?我需要改变什么?是linq查询还是我进行映射的方式


感谢您的时间…

我将制作一个工厂方法,即:
TableA\u DTO CreateDTO(TableAItem项)

使用此选项,您可以将查询重写为:

IEnumerable<TableA_DTO> = TableA.AsEnumerable().Select(CreateDTO);
IEnumerable=TableA.AsEnumerable().Select(CreateDTO);
这将直接为您提供“DTO”对象的集合


也就是说,如果您使用的是Entity Framework,那么在这种情况下,最新版本中添加的可能会更有用。

首先,我需要问一下,您是否可以使用Entity Framework 4.1和POCOs(DbContext)并避免使用DTO的替代品

假设答案是否定的,那一定是因为您没有收回所有字段,或者您正在以某种方式改变数据的“形状”

在这种情况下,您可以将LINQ查询更改为如下所示:

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

这样做的好处是:如果打开SQL Profiler,您应该看到只有您请求的列才能进入实际的SQL查询。如果您先查询所有数据,然后再提取值,所有列都将被提取。

我会将您的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
                    }
            }
    };

更新

正如其他人所指出的,在使用Entity Framework 4.0时,不需要展平结果(如下所示),因为它可以将LINQ查询转换为高效的展平结果。因此,只有在使用LINQtoSQL(或其他可能的LINQ提供程序)时,才需要以下代码。请注意,我只在SQL Server上使用EF进行了测试,而没有在Oracle上进行测试,因为此行为可能是特定于LINQ提供程序的,这意味着Oracle提供程序(仍处于测试阶段)或Oracle的商业Devart提供程序可能仍在执行N+1


您试图做的是得到一组结构类似于树的对象。如果没有任何特别注意,您将触发对数据库的许多查询。使用一级嵌套,您将触发N+1查询,但由于嵌套深度为两级,您将触发mx(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)以我们想要的结构获得这些数据


请注意,这还允许您进行分页和排序。这将有点棘手,但肯定不是不可能的。

有什么原因不能使用实体框架POCO(也称为DbContext,有时错误地称为代码优先)?基本上,你能消除对DTO的需求而改用EF POCO吗?你考虑过使用AutoMapper吗?根据DTO的不同程度,这可能只需两行或三行代码即可完成映射。@jMarsch:数据库已经存在,所以edmx也已经存在了way@Robaticus:我是否仍然需要更改linq查询中的某些内容以首先获取数据,以便automapper可以使用它?从我对automapper的概述来看,它似乎不能做到这一点。如果你说它能做到,它会深入挖掘。。。谢谢:)您仍然可以使用EF对象来获取数据。然后,AuotMapper将相关字段从一个映射到另一个(再映射回来)。如果你按照惯例(例如同名),AM的工作就像魔术一样。如果DTO中有前缀,您甚至可以定义它们。什么是CreateDTO?这是一节课吗?它的定义是什么?@user20358它将是一个你编写的方法,负责赋值。您仍然需要执行这些赋值,但它仅限于一个方法(从entity->DTO转换)@Reed:。除非该表的行数少于1000行(及其所有数据),或者您希望获取所有记录,否则这将对性能非常不利。@Steven若要将它们转换为本地类(即DTO),必须将它们向下拉。如果需要筛选,它应该在AsEnumerable()调用之前,但是转换为DTO需要它首先是本地的。@Steven注意,这与OP的foreach循环没有什么不同;)然而,问题是,这会触发比
N+1
查询更多的查询;它触发的
M*(N+1)+1
查询几乎肯定会导致非常糟糕的结果
// 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();