繁体   English   中英

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

[英]Entity Framework core select causes too many queries

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

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( -我意识到这是由于ToList( )和First()等调用。

但是,由于对象的性质,我可以使用from.... select new {...}创建一个巨大的IQueryable<anonymous>对象。 from.... select new {...}并对其调用First ,但是代码超过300行,仅需5行层次结构中的各个层次,因此我试图用上面的代码代替它,它虽然更慢,但更干净。

ChannelObjectType, ChannelObjectSearchType, ChannelObjectSupportingObject

是否所有ChannelObjectModel实例,并且Fields是ChannelObjectModel实例的列表。

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

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

任何人都可以提出一种方法,以一种类似于上述方法的方式来递归地构建对象,但是可以大大减少数据库调用的次数,从而更快地进行?

我使用的是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()添加为AsNoTracking()最后一条指令。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM