在Entity Framework中,创建投影比选择完整实体更有效/更受欢迎吗?

3
我正在开发一个应用程序,试图提高性能。显然,我会进行自己的分析和测试,但我想知道是否有“共识”或已知的最佳实践。
在旧的SQL时代,提高效率的主要方法之一是不选择您不需要使用的数据。我正试图通过EF6走这条路线。
在这种特定情况下,我有一个主-详细-详细关系,在屏幕上需要呈现有关父项、子项和孙项的一些数据。
我的应用程序是n层结构,具有MVC前端和Web-API REST后端。这些实体最终将被序列化为JSON,并通过REST连接发送回MVC控制器,在那里它们将呈现到屏幕上。在这种情况下,我不需要担心从此流程更新实体(在这些情况下,为了方便维护,我可能会发送完整实体)。
因此,我编写的原始EF代码如下:
Repository.GetAll()
          .AsNoTracking()
          .Include("Children")
          .Include("Children.GrandChildren")
          .ToList();

然而,我实际上只使用这些实体的属性子集,其中一些未使用的属性可能相当大(大块的XML等)。
这是尝试仅投射我需要的字段的第一步(对于此示例,我已经剪切并重命名了大多数我实际选择以提高可读性的字段,但通常我使用全实体的5-20%)。
var projection = Repository.GetAll()
            .AsNoTracking()
            .Select(r => new
            {
                r.Id,
                r.RandomId,
                r.State,
                r.RequestType,
                r.CreatedDate,
                r.CreatedBy,
                Children = r.Children.Select(r2 => new
                {
                    r2.Id,
                    r2.Status,
                    GrandChildren = r2.GrandChildren.Select(r3 => new
                    {
                        r3.Id,
                        r3.Status,
                        r3.GrandChildType
                    })
                }),
            }
            ).ToList();

这显然使用了匿名类型(我认为在EF中这是必需的,没有一种方法可以将其投影到命名类型中?)(编辑:显然您可以将其投影到未映射的命名类型中,但在这种情况下,查询的返回类型是已映射的类型。因此,我可以创建DTO,但那需要更多的代码来维护)。所以我必须回到我的具体类型。我当然可以生成仅具有所需属性的DTO,但我不认为这会改变所使用的基本逻辑,也可能不会改变性能特征。我尝试了Automapper和ValueInjecter,但似乎都不完全符合要求(深克隆异构类型并匹配名称),所以我采用了一种不太正规的方法。
var json = projection.Select(JsonConvert.SerializeObject).ToList();

var mapped = json.Select(JsonConvert.DeserializeObject<Parent>).ToList();

这有点麻烦,因为它只是作为REST调用的一部分而再次序列化。我可能可以覆盖webAPI调用,以表明我正在返回已经序列化的数据,这将让我跳过实体类型的再次处理(因为所有属性名称都匹配,REST客户端应该能够像上面的片段一样重新处理匿名类型)

但是,所有这些似乎是很多工作,代码也更不易维护,可能会出现更多的错误等,特别是在Entity Framework似乎不支持的情况下。但我那旧学派的本能无法放弃这个想法:我选择、序列化和传输了大量数据,但最终却没有使用。

这在内部是否产生合理的SQL?这是否值得双重序列化?(假设我没有找出如何覆盖webapi,让它接收我的数据)

我想我的另一个选择就是重构所有实体,使未使用的属性位于不同的子实体中,以便我可以忽略它,但这将需要对整个系统进行大量的重构(相比于能够在关键点上进行精确的性能优化),并且这似乎也不是一种好的选择,在设计实体时应该遵循标准的归一化规则等,而不是根据我所使用的ORM。

4个回答

3

您可以将数据投射到命名类型中。

var projection = Repository.GetAll()
        .AsNoTracking()
        .Select(r => new ParentModel()
        {
            Id = r.Id,
            RandomId = r.RandomId,
            State = r.State,
            RequestType = r.RequestType,
            CreatedDate = r.CreatedDate,
            CreatedBy = r.CreatedBy,
            Children = r.Children.Select(r2 => new ChildModel()
            {
                Id = r2.Id,
                Status = r2.Status,
                GrandChildren = r2.GrandChildren.Select(r3 => new GrandChildModel
                {
                    Id = r3.Id,
                    Status = r3.Status,
                    GrandChildType = r3.GrandChildType
                })
            }),
        }
        ).ToList();

但是确实没有必要包含你不需要的字段。

最近在使用DTO或模型时,我会在我的模型中添加一个静态函数,并在上下文投影中使用它。在你的情况下,它可能看起来像:

public class ParentModel
{
    public int Id { get; set; }
    public int RandomId { get; set; }
    public string State { get; set; }
    public List<ChildModel> Children { get; set; }
    public static Func<Parent, ParentModel> Project = item => new ParentModel
    {
        Id = item.Id,
        RandomId = item.RandomId,
        State = item.State,
        Children = item.Children.Select(ChildModel.Project)
    };
}
public class ChildModel
{
    public int Id { get; set; }
    public int Status { get; set; }
    public string State { get; set; }
    public List<GrandChildModel> GrandChildren { get; set; }

    public static Func<Child, ChildModel> Project = item => new ChildModel
    {
        Id = item.Id,
        Status = item.Status
        GrandChildren = item.GrandChildren.Select(GrandChildModel.Project)
    };
}
public class GrandChildModel
{
    public int Id { get; set; }
    public int Status { get; set; }
    public int GrandChildType { get; set; }
    public static Func<GrandChild, GrandChildModel> Project = item => new GrandChildModel
    {
        Id = item.Id,
        Status = item.Status,
        GrandChildType = item.GrandChildType
    };
}

那么你的投影代码只需要像这样:

var projection = Repository.GetAll()
      .AsNoTracking()
      .Include("Children")
      .Include("Children.GrandChildren")
      .Select(ParentModel.Project)
      .ToList();

我猜你是指 Id = r.Id, 等等。当不初始化匿名类型时,必须指定属性名称。 - CodeCaster
1
我会尝试使用具体类型,但我发誓我读过一些东西说linq to sql只支持匿名投影。 - Jason Coyne
1
实际上,当我尝试使用命名类型时,会出现“附加信息:在LINQ to Entities查询中无法构造实体或复杂类型'ConcreteType'。” - Jason Coyne
https://dev59.com/hG435IYBdhLWcg3wlRMf - Jason Coyne
1
我创建了一些继承自真实实体并标记为notmapped的DTO。然后我在3个select语句中添加了Cast<>()调用。所有转换的开销都是有点大,但它可能比双重序列化的开销小。 - Jason Coyne
@CodeCaster 你是对的,我只是复制了他的答案...我会进行更新...谢谢! - JamieD77

2

使用表拆分功能,可以将表拆分为多个实体,而不需要修改底层表。较少访问的属性可以按需延迟加载或急切地加载,行为与任何其他导航属性相同。请注意,关键在于附加实体使用其主键作为主实体的外键。


我考虑过这个方案,但它需要对应用程序进行更广泛的改变,而不是仅在痛点处进行手术式的修改。如果我从头开始构建,我可能会认真考虑这个方案。 - Jason Coyne
1
表格分割是您情况下的适当设计,比任何其他解决方案都更容易维护。 - Moho
表拆分对于大列应该是可行的,但对于一个具有许多小列的单个实体的一般情况,我需要随机子集,它并不真正适用(除非您可以在多个拆分实体中定义相同的属性以获取任意子集)。 - Jason Coyne
你说得没错,但如果列很小,有什么问题吗? - Moho

1
当然,项目更好。有两种方法,
使用DTO或拆分表格
问题是,需要编写太多代码和管理工作,但这是更好的方法,因为您可以可视化所有实体并在需要时轻松重构它们。
或者使用单独的DbContext,仅使用受限模型作为只读上下文。
动态DTO代理
相反,在ASP.NET MVC中,我创建了一个REST代理层,允许我动态查询而不会创建太多DTO(数据传输对象)。在我的方法中,我创建了一个查询,例如:
/app/entity/message/query
    ?query={UserID:2}
    &orderBy=DateReceived+DESC
    &fields={MessageID:'',Subject:''}
    &start=10
    &size=10

query expects anonymous object as filter, here are more examples

在这里,我通过查询字符串传递了必需的字段,在这种设计中,我的API层使用反射和表达式API创建投影。这有点复杂,但这使我不必创建许多排列组合。
源代码在这里,https://github.com/neurospeech/atoms-mvc.net 然而,该代码用于在JavaScript中创建类似Entity Framework的上下文,然后异步加载相关导航属性作为单独的查询,但再次仅限于有限的字段。
有一个名为LinqRuntimeTypeBuilder.cs的类,其中包含构建可用于查询的动态类型的源代码。 https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/LinqRuntimeTypeBuilder.cs 这种方法有点冗长,因为它需要设置“防火墙”来控制对实体的访问。

0

你可能会对命令查询职责分离(CQRS)方法感兴趣,其中你有一个模型用于写入操作,而多个单独的查询模型则专门为读取操作而设计。写入模型通常反映了一个完整的 EF 实体,而读取模型则是从任意数量的实体中投影和聚合数据的任何投影和聚合。

是的,这意味着将有多个只包含所需属性的读取模型 DTO,但在一致性和表达性方面,它比向客户端代码发送大量数据并让其挑选自己想要的数据要好得多,我认为。


我同意这种方法,但它并没有真正回答EF问题,因为我必须以某种方式选择要加载到DTO中的数据,这将需要其他答案中的技术之一。 - Jason Coyne
它确实回答了你的问题标题 ;) - guillaume31

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接