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