实体框架核心选择导致太多查询

时间:2018-07-04 16:44:24

标签: c# entity-framework-core database-performance

我具有以下方法,该方法用于构建单个对象实例,该对象实例的属性是通过递归调用相同的方法来构建的:

public ChannelObjectModel GetChannelObject(Guid id, Guid crmId)
    {
        var result = (from channelObject in _channelObjectRepository.Get(x => x.Id == id)
                      select new ChannelObjectModel
                      {
                          Id = channelObject.Id,
                          Name = channelObject.Name,
                          ChannelId = channelObject.ChannelId,
                          ParentObjectId = channelObject.ParentObjectId,
                          TypeId = channelObject.TypeId,
                          ChannelObjectType = channelObject.ChannelObjectTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectTypeId.Value, crmId) : null,
                          ChannelObjectSearchType = channelObject.ChannelObjectSearchTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectSearchTypeId.Value, crmId) : null,
                          ChannelObjectSupportingObject = channelObject.ChannelObjectSupportingObjectId.HasValue ? GetChannelObject(channelObject.ChannelObjectSupportingObjectId.Value, crmId) : null,
                          Mapping = _channelObjectMappingRepository.Get().Where(mapping => mapping.ChannelObjectId == channelObject.Id && mapping.CrmId == crmId).Select(mapping => new ChannelObjectMappingModel
                          {
                              CrmObjectId = mapping.CrmObjectId
                          }).ToList(),
                          Fields = _channelObjectRepository.Get().Where(x => x.ParentObjectId == id).Select(field => GetChannelObject(field.Id, crmId)).ToList()
                      }
                     );
        return result.First();
    }

public class ChannelObjectModel
{
    public ChannelObjectModel()
    {
        Mapping = new List<ChannelObjectMappingModel>();
        Fields = new List<ChannelObjectModel>();
    }
    public Guid Id { get; set; }
    public Guid ChannelId { get; set; }
    public string Name { get; set; }
    public List<ChannelObjectMappingModel> Mapping { get; set; }
    public int TypeId { get; set; }
    public Guid? ParentObjectId { get; set; }
    public ChannelObjectModel ParentObject { get; set; }
    public List<ChannelObjectModel> Fields { get; set; }
    public Guid? ChannelObjectTypeId { get; set; }
    public ChannelObjectModel ChannelObjectType { get; set; }
    public Guid? ChannelObjectSearchTypeId { get; set; }
    public ChannelObjectModel ChannelObjectSearchType { get; set; }
    public Guid? ChannelObjectSupportingObjectId { get; set; }
    public ChannelObjectModel ChannelObjectSupportingObject { get; set; }
}

这正在使用Entity Framework Core 2.1.1连接到SQL数据库

从技术上讲,它可以进行大量数据库查询-我意识到,这是因为进行了ToList(First()等调用。

但是,由于对象的性质,我可以用IQueryable<anonymous>制作一个巨大的from.... select new {...}对象,并在其上调用First,但是代码只花了300多行层次结构中有5层,所以我试图用上面的代码代替它,虽然更慢,但是更干净,但是更慢。.

ChannelObjectType, ChannelObjectSearchType, ChannelObjectSupportingObject

是所有ChannelObjectModel实例,“字段”是ChannelObjectModel实例的列表。

当前执行查询大约需要30秒,这太慢了,并且它也在小型localhost数据库上,因此查询只会随着大量的db记录而变得更糟,并且在查询时会产生很多数据库调用我运行它。

300行以上的代码生成的查询要少得多,并且速度相当快,但显然是可怕的,可怕的代码(我没有写过!)

任何人都可以建议一种方法,以类似于上述方法的方式递归构建对象,但可以大幅度减少数据库调用的次数,从而更加快捷?

1 个答案:

答案 0 :(得分:0)

我使用的是EF6,而不是Core,但据我所知,这里同样适用。

首先,将此函数移至您的存储库,以便所有调用共享DbContext实例。

其次,在属性的DbSet上使用Include来渴望加载它们:

ctx.DbSet<ChannelObjectModel>()
     .Include(x => x.Fields)
     .Include(x => x.Mapping)
     .Include(x => x.ParentObject) 
     ...

优良作法是使它成为上下文(或扩展方法)的函数,例如BuildChannelObject(),它应返回IQueryable-仅包含。

然后您可以开始递归部分:

public ChannelObjectModel GetChannelObjectModel(Guid id)
{
    var set = ctx.BuildChannelObject(); // ctx is this

    var channelModel = set.FirstOrDefault(x => x.Id == id); // this loads the first level

    LoadRecursive(channelModel, set);

    return channelModel;
}

private void LoadRecursive(ChannelObjectModel c, IQueryable<ChannelObjectModel> set)
{
     if(c == null)
         return; // recursion end condition

     c.ParentObject = set.FirstOrDefault(x => x.Id == c?.ParentObject.Id);
    // all other properties

     LoadRecursive(c.ParentObject, set);
    // all other properties
}

如果所有这些代码都使用相同的DbContext实例,则它应该非常快。如果没有,您可以使用另一个技巧:

ctx.DbSet<ChannelObjectModel>().BuildChannelObjectModel().Load();

这会将所有对象加载到DbContext的内存缓存中。不幸的是,它死于上下文实例,但由于没有进行数据库行程,因此使那些递归调用快得多。

如果这样做仍然很慢,则可以将AsNoTracking()添加为BuildChannelObjectModel()的最后一条指令。

如果这样做仍然很慢,只需对这些对象实施应用程序范围的内存缓存,并使用该缓存而不是每次都查询数据库-如果您的应用程序是可以长时间启动但又可以快速运行的服务,则效果很好。

另一种方法是通过将导航属性标记为虚拟来启用延迟加载-但是请记住,返回的类型将是派生类型的匿名代理,而不是原始的ChannelObjectModel!同样,只有在不处理上下文的情况下,属性才会加载-之后,您将获得异常。要用上下文加载所有属性然后返回完整的对象也有些棘手-最简单(但不是最好的方法!)的方法是在返回对象之前将对象序列化为JSON(记住关于循环引用)。

如果您不满意,请切换到nHibernate,我听说默认情况下具有应用程序级缓存。