我具有以下方法,该方法用于构建单个对象实例,该对象实例的属性是通过递归调用相同的方法来构建的:
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行以上的代码生成的查询要少得多,并且速度相当快,但显然是可怕的,可怕的代码(我没有写过!)
任何人都可以建议一种方法,以类似于上述方法的方式递归构建对象,但可以大幅度减少数据库调用的次数,从而更加快捷?
答案 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,我听说默认情况下具有应用程序级缓存。