[英]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.