2015-02-12 5 views
2

Существует множество вопросов об использовании функций Include() и Load() для связанной информации таблицы при использовании Linq для объектов. У меня другое отношение к этому вопросу.Объекты, связанные с инфраструктурой Entity Framework, с использованием генерических файлов

Моя ситуация:

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

public class Repository<T> : IRepository<T> where T : class 
{ 
    protected readonly ApplicationDbContext Context; 

    public Repository(IConnectionHelper connection) 
    { 
     Context = connection.Context; 
    } 
    public virtual DbSet<T> ObjectSet 
    { 
     get { return Context.Set<T>(); } 
    } 
    public List<T> GetAll(String[] include, Expression<Func<T, bool>> predicate) 
    { 
     DbQuery<T> outQuery = null; 
     foreach (String s in include) 
     { 
      outQuery = ObjectSet.Include(s); 
      outQuery.Load(); 
     } 
     return outQuery.Where(predicate).ToList(); 
    } 
} 

Вызов метода выглядит так:

string[] includes = { "User.UserProfile", "CampaignTimeClocks.CampaignRole.Campaign", "Site", "Type" }; 
DateTime uTcCurrent = GetUtc(); 
DateTime MinClockinDate = uTcCurrent.AddHours(-10); 
List<TimeClock> tcPending = _timeClock.GetAll(includes, x => (x.PendingReview || x.ClockInDate < MinClockinDate && x.ClockOutDate == null) && (x.Site.Id == currentUser.SiteId)); 

Когда этот метод работает и загружает первая таблица User.Profile, она загружает все записи тайм-слова и связывает их со всеми пользователями, это занимает больше минуты, это слишком долго, так как количество конечных записей составляет всего 185 записей, но начальная загрузка в запросе работает 27 000 * 560 пользователей или 15 миллионов записей, и это со временем будет намного хуже.

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

Я также попытался:

List<TimeClock> testLst = _timeClock.GetAll(x => x.PendingReview || 
    (x.ClockInDate < MinClockinDate && x.ClockOutDate == null)) 
      .Select(x => new TimeClock{Id = x.Id, 
            ClockInDate = x.ClockInDate, 
            ClockOutDate = x.ClockOutDate, 
            TotalClockTime = x.TotalClockTime, 
            Notes = x.Notes, 
            PendingReview = x.PendingReview, 
            Type = x.Type, 
            User = x.User, 
            CampaignTimeClocks = x.CampaignTimeClocks, 
            TimeClockAdjustments = x.TimeClockAdjustments, 
            Site = x.User.Site}).ToList(); 

Это даст мне информацию User.Profile, но места и типа свойства равны нулю.

Так что я немного потерял, как загрузить данные, которые мне нужны здесь.

Вся помощь очень ценится.

+0

Что произойдет, если вы запустите 'Where' перед' Load'? – Shoe

+0

Не имеет значения, где предложение where - это потому, что он загружает данные в запрос query.load(). –

+0

Да, добавьте предикат к запросу, затем загрузите. 'ObjectSet.Where(). Include(). Load();' – Shoe

ответ

0

Вы можете получить начальный список первого

List<TimeClock> testLst = _timeClock.Where(x => x.PendingReview || (x.ClockInDate < MinClockinDate && x.ClockOutDate == null)).ToList(); 

, а затем вызвать модифицированную GetAll(), который принимает T в качестве аргумента?

+0

Не совсем так, я боюсь, оператор Load() для соответствующей таблицы User.UserProfile по-прежнему загружает все записи времени в таблице. Независимо от того, где предложение where(). –

+0

Да, я просто понял, что .... ищет альтернативу. – DrewJordan

0

Каждый из них включает в себя соединение, выполняемое в db. Предположим, что ваш левый стол имеет большой размер 1024 байта в размере записи и что у вас много деталей, скажем 1000, и что размер размерной записи составляет всего 100. Это приведет к повторному повторению информации для левой таблицы 1000 раз, эта информация будет помещена в провод db, и EF должен отфильтровать дублированный, чтобы создать ваш левый экземпляр.

Может быть лучше не использовать include и выполнять явную загрузку. В основном выполняется 2 запроса в одном контексте.

У меня есть такой пример, отличный от вашего, но я надеюсь, что вы получите эту идею. Это может быть в 10 раз быстрее, чем полагаться на include. (БД может обрабатывать только limitited число объединений эффективно кстати)

var adressen = adresRepository 
       .Query(r => r.RelatieId == relatieId) 
       .Include(i => i.AdresType) 
       .Select().ToList(); 

var adresids = (from a in adressen select a.AdresId).ToList(); 
      IRepositoryAsync<Comm> commRepository = unitOfWork.RepositoryAsync<Comm>(); 

      var comms = commRepository 
       .Query(c => adresids.Contains(c.AdresId)) 
       .Include(i => i.CommType) 
       .Select(); 

Для commType и adresType я использую включать, потому что есть 1 к 1 отношения, Я избегаю слишком много соединений и, таким образом, мои многочисленные запросы будут быть быстрее, чем использовать один из них.Я не включаю Comms в первый запрос, чтобы попытаться избежать второго запроса, дело в том, что в этом случае 2 запроса быстрее, чем один.

Обратите внимание, что мой код построен с использованием моих собственных репозиториев, поэтому этот код не будет работать для вас, но вы можете получить за это идею.

+0

Я понимаю, что вы говорите по большей части, но проблема здесь в том, что я динамически создаю включенные для запроса. Поэтому точная строка, которая вызывает мою проблему. outQuery.Include (s); outQuery.Load(); <----- Эта строка, когда s = «User.UserProfile» занимает более 45 секунд, даже если она является единственной таблицей в include. –

+0

То, что я пытаюсь сделать, это то, что вы делаете слишком много включений, в первую очередь, что приведет к плохой работе. Я возлагаю ответственность за deepload на мои репозитории, сама заявка не должна знать, как сделать deepload. С вашей стратегией включения вы подталкиваете этот knowlegde к тому, кто собирается предоставить вам все включено, нарушая SRP на этом пути. –

+0

Ответственность за знание того, какие связанные данные должны быть возвращены, не несет ответственность за уровень данных, его уровень в бизнес-сервисе. (Есть много дебатов по этой теме) Хотя вы частично правы с низкой производительностью, это произойдет не по той причине, что вы предлагаете, поскольку проблема связана с одной таблицей, а не с остальными включенными, я нашел ответ Я искал, и я отправлю его. –

0

Способ, которым я решил сделать это более эффективно, без использования оператора Load(), заключается в том, чтобы изменить DBQuery на IQueryable и объединить его, а также вернуть результаты выполненного запроса и удалить DBQuery.Load() все вместе. Это изменило время выполнения запроса на миллисекунды с секунд.

public List<T> GetAll(String[] include) 
    { 
     IQueryable<T> outQuery = ObjectSet; 
     foreach (String s in include) 
     { 
      outQuery = outQuery.Include(s); 
     } 
     return outQuery.ToList(); 
    } 
Смежные вопросы