Ядро Entity Framework вызывает слишком много запросов - PullRequest
0 голосов
/ 04 июля 2018

У меня есть следующий метод, который предназначен для создания одного экземпляра объекта, где его свойства создаются путем рекурсивного вызова одного и того же метода:

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; }
}

это подключение к базе данных SQL с использованием Entity Framework Core 2.1.1

Хотя технически это работает, оно вызывает множество запросов к базе данных - я понимаю это из-за вызовов ToList() и First() и т. Д.

Однако из-за природы объекта я могу создать один огромный IQueryable<anonymous> объект с from.... select new {...} и вызовом First, но код длиной более 300 строк, занимающий всего 5 уровней в иерархии поэтому я пытаюсь заменить его чем-то вроде приведенного выше кода, который намного чище, хотя и намного медленнее ..

ChannelObjectType, ChannelObjectSearchType, ChannelObjectSupportingObject

Все ChannelObjectModel экземпляры, а Поля - это список ChannelObjectModel экземпляров.

В настоящее время для выполнения запроса требуется около 30 секунд, что слишком медленно, и он также находится в небольшой базе данных localhost, поэтому он будет ухудшаться только при большем количестве записей в БД и генерирует много вызовов базы данных, когда Я запускаю его.

Код с 300+ строками генерирует намного меньше запросов и достаточно быстрый, но, очевидно, ужасный, ужасный код (который я не писал!)

Может кто-нибудь предложить способ, которым я могу рекурсивно создать объект аналогично описанному выше методу, но радикально сократить количество вызовов базы данных, чтобы это было быстрее?

1 Ответ

0 голосов
/ 05 июля 2018

Я работаю с EF6, а не с Core, но, насколько я знаю, то же самое применимо и здесь.

Прежде всего, переместите эту функцию в свой репозиторий, чтобы все вызовы совместно использовали экземпляр DbContext.

Во-вторых, используйте Включите в свой DbSet в свойствах, чтобы загружать их:

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, который, как я слышал, по умолчанию имеет кеш приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...