簡體   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