在实体框架中,创建投影而不是选择完整实体更有效/更首选吗?

时间:2015-09-30 21:09:06

标签: sql asp.net-mvc performance entity-framework rest

我正在开发一个应用程序,试图提高性能。显然,我将进行自己的分析和测试,但我想知道是否有#34;共识"或已知的最佳实践。

在旧的SQL时代,提高效率的主要方法之一是不选择您不会消费的数据。我试图用EF6沿着这条路走下去。

在这种特殊情况下,我有一个主 - 细节 - 细节关系,我需要在屏幕上呈现关于父,子和孙的一些数据。

我的应用程序是n-tier,带有MVC前端和web-api REST后端。这些实体最终将被序列化为JSON,通过其余连接发送回MVC控制器,在那里它们将呈现在屏幕上。在这种情况下,我不会更新此流程中的实体,因此我不需要担心将部分实体合并回存储库(在这些情况下,为了便于维护,我可能会发送整个实体)

所以,我写的原始简单的EF代码看起来像这样

Repository.GetAll()
          .AsNoTracking()
          .Include("Children")
          .Include("Children.GrandChildren")
          .ToList();

但是,我实际上只是消耗了这些实体的一部分属性,而且一些未使用的属性可能相当大(大块的XML等)

这是第一次尝试只投射我需要的字段(对于这里的示例,我已经剪切并重命名了我实际选择的大部分字段以提高可读性,但总的来说我是' m使用让我们说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();

这有点蹩脚,因为它只是作为休息电话的一部分再次被序列化。可能有一种方法我可以覆盖webAPI调用,说我正在返回已经序列化的数据,这将让我跳过重新合成到实体类型(因为所有属性名称匹配,其余客户端应该能够重新水化匿名类型,好像它是真正的类型,就像上面的代码片段一样)

但是对于实体框架似乎并不想支持的用例,所有这些似乎都需要大量的工作,较少的可维护代码,更多可能存在错误的地方等等。但是我的旧学校本能不能放弃我选择,序列化和转移最终我不会消费的大量数据的想法。

这是否会产生理智的SQL?这值得双序列化吗? (假设我没有弄清楚如何覆盖webapi让我交给他们数据)

我想我的另一个选择是重构所有实体,以便未使用的属性位于我可以不包含的不同子实体中,但这将在整个系统中进行大量的返工(而不是能够通过手术改进在关键点上的表现)并且在我正好使用的ORM设计实体与标准规范化规则等之间似乎也是一个糟糕的选择。

4 个答案:

答案 0 :(得分: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方法或模型时..我会在我的模型中添加一个静态Func,并在我的上下文投影中使用它。在你的情况下,它看起来像

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();

答案 1 :(得分:2)

使用表拆分,允许您在不修改基础表的情况下将表拆分为多个实体。 &#34;访问量较少&#34;属性可以根据需要延迟加载或者热切地加载,表现为任何其他导航属性。请注意,此处的关键是其他实体使用其pk作为主要实体的fk

答案 2 :(得分:1)

当然,项目更好。有两种方法,

使用DTO或拆分表

问题是代码太多而管理太多,但这是更好的方法,因为您可以可视化所有实体并在需要时轻松地重构它们。

或者使用带有限模型的单独DbContext,就像Readonly上下文一样。

动态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层使用Reflection和Expression API创建投影。这有点复杂。但这使我不会创造许多排列和组合。

源代码在这里,https://github.com/neurospeech/atoms-mvc.net

但是,此代码用于在JavaScript中创建类似于上下文的实体框架,然后将相关的导航属性作为单独的查询异步加载,但再次使用有限的字段加载。

有一个类LinqRuntimeTypeBuilder.cs,其中包含可动态构建类型的源代码,可用于查询。 https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/LinqRuntimeTypeBuilder.cs

这种方法需要更长时间,因为它需要设置&#34;防火墙&#34;反对实体控制访问。

答案 3 :(得分:0)

您可能对命令查询责任隔离(CQRS)方法感兴趣,其中您有一个用于写入侧的模型和为您的读取定制的多个单独的查询模型。写模型通常会反映单个整个EF实体,而读模型则是来自任意数量实体的数据的任何预测和聚合。

是的,这意味着将会有多个读取模型DTO只包含您需要的属性,但它在一致性和表现力方面比向客户端代码发送大量数据并让它能够满足其想要的IMO要好得多。