2012-04-30 4 views
4

Мы используем EF 4.3 для нашего уровня данных и имеем общий шаблон репозитория. Backend - это SQL 2008 R2, а проект .NET 4.0/MVC 3, но я не думаю, что эти факторы в этом вопросе.EF 4.3 Многократная ошибка перечисления

В принципе, у нас есть одно-много отношений в нашей базе данных по двум объектам. Один - для «Ловушек», а второй - для «Активность ловушки». Смысл, когда одна из этих «ловушек» развернута, все, что происходит с этой ловушкой, хранится в таблице активности трейпа. Должен быть довольно простой способ сделать это.

Отношения определяются с помощью FK в таблице «Активность трейпа» в PK таблицы «Ловушки». Обе таблицы имеют PK.

В нашем сервисном слое мне нужно запросить список «Ловушек» с датой развертывания этих ловушек. Это выполняется с помощью следующего фрагмента кода:

var traps = this.trapRepository.Find(x => x.DeploymentYear == 2012).Select(x => new TrapHomeViewModel 
      { 
       County = x.County.Name, 
       DeploymentDate = x.TrapActivities.First(y => y.ActivityType == 1).ActivityDate, 
       State = x.County.CountyState.Abbreviation, 
       Latitude = x.Latitude, 
       Longitude = x.Longitude, 
       TrapId = x.TrapID, 
       TrapNumber = x.SerialNumber, 
       Centroid = x.TrapCentroid 
      }).ToList(); 

Вопрос касается свойства DeploymentDate. Как написано, это займет 25 секунд, чтобы вернуть список из примерно 3000 предметов. Обновление таблицы Trap, чтобы дата развертывания будут храниться там и заполнение этой строки:

DeploymentDate = x.DeploymentDate.Value.Date 

Результаты за время менее 1 сек ответа. Теперь я думаю, что я знаю, что здесь происходит (несколько перечислений из набора данных), но то, что я думал, что произойдет бы запрос похож на следующее:

SELECT  Counties.Name, TrapActivities.ActivityDate, States.Abbreviation, 
Traps.Latitude, Traps.Longitude, Traps.TrapID, Traps.SerialNumber, Traps.TrapCentroid 
    FROM   TrapActivities INNER JOIN 
          Traps ON TrapActivities.TrapID = Traps.TrapID INNER JOIN 
          Counties ON Traps.CountyID = Counties.CountyID INNER JOIN 
          States ON Counties.State = States.FIPS_Code 
    WHERE  (TrapActivities.ActivityType = 1) 

... но это не кажется, случай. Со всей исходной информацией выше, где я заблудился при заполнении этой модели представления? Я не думаю, что раньше сталкивался с этим вопросом, но это также гораздо больший набор данных, чем некоторые из наших других проектов. Любое руководство по этому вопросу будет очень полезно. Если мне нужно предоставить любую другую информацию, пожалуйста, дайте мне знать.

РЕДАКТИРОВАТЬ

В соответствии с просьбой GenericRepository Найти способ и конструкторы:

public class GenericRepository<T> : IGenericRepository<T> 
     where T : class 
    { 
     private readonly IObjectSet<T> objectSet; 
     private ObjectContext context; 

     public GenericRepository() 
      : this(new APHISEntities()) 
     { 
     } 

     public GenericRepository(ObjectContext context) 
     { 
      this.context = context; 
      this.objectSet = this.context.CreateObjectSet<T>(); 
     }  

     public IEnumerable<T> Find(Func<T, bool> predicate) 
     { 
      return this.objectSet.Where(predicate); 
     } 

EDIT 2

Это пример SQL, генерируемого кода выше:

exec sp_executesql N'SELECT 
[Extent1].[TrapActivityID] AS [TrapActivityID], 
[Extent1].[TrapID] AS [TrapID], 
[Extent1].[ActivityType] AS [ActivityType], 
[Extent1].[Notes] AS [Notes], 
[Extent1].[AgentID] AS [AgentID], 
[Extent1].[ActivityDate] AS [ActivityDate], 
[Extent1].[CreatedOn] AS [CreatedOn], 
[Extent1].[EditedOn] AS [EditedOn], 
[Extent1].[Deleted] AS [Deleted], 
[Extent1].[VisualInspectionID] AS [VisualInspectionID] 
FROM [dbo].[TrapActivities] AS [Extent1] 
WHERE [Extent1].[TrapID] = @EntityKeyValue1',N'@EntityKeyValue1 uniqueidentifier',@EntityKeyValue1='FEBC7ED4-E726-4F5E-B2BA-FFD53AB7DF34' 

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

+0

Лучшая ставка в этой ситуации и для будущих ситуаций - запустить профилировщик, такой как профилировщик Anjlab sql в вашей базе данных. Он покажет вам точный SQL, который выполняется, и как долго выполняется каждый вызов. Затем вы сможете увидеть, не делает ли это что-то супер неэффективно. Вероятно, вам просто нужно добавить некоторые индексы в свою базу данных, чтобы ускорить их. Когда вы найдете запрос, который занимает 25 секунд, SSMS имеет возможность просмотреть путь выполнения, и вы должны увидеть, где проблемы. – NibblyPig

+0

Вы можете точно увидеть, какой запрос генерируется путем захвата сетевого трафика (Wire Shark сделает это) и посмотрев на него, а затем посмотрите, есть ли у вас индексы для его оптимизации. Вы можете быть удивлены запросом, который он создает - я был с некоторыми из моих. – itsmatt

+0

Как выглядит метод 'Find' вашего репозитория? –

ответ

1

В вашем репозитории вы можете использовать ObjectQuery.ToTraceString, чтобы увидеть, что SQL выполняет, прежде чем возвращать свои объекты.

Вы возвращаете все фактические объекты Trap, развернутые в 2012 году, из вашего метода поиска репозитория, а не IQueryable И вы не хотите загружать TrapActivities. Это означает, что при перечислении результатов в Select для создания вашей модели просмотра вы отправляете новый запрос в БД для каждого Trap, чтобы получить его TrapActivities.

Update 1

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

var q = from t in traps 
     where t.DeploymentYear == 2012 
     select new TrapFirstDeployment { 
      Trap = t, 
      DeploymentActivity = t.TrapActivities.Where(ta=>ta.FirstOrDefault(a=>a.ActivityType=1)) 
     }; 

return q.Where(tfd=>tfd.DeploymentActivity != null); 

Объяснение

Причина ваш первоначальный запрос был медленным, потому что EF не жаждущие нагрузки дочерних отношений, если не сказать, что это. По умолчанию Lazy Loading включен. Так как вы не скажете, чтобы он загружал TrapActivities с вашим Trap в ваш репозиторий, он ждет, пока вы не получите доступ к нему в первый раз, чтобы загрузить их. Это здорово, что вам нужна ловушка, но не действия, потому что она уменьшает трафик в/из БД. Однако в некоторых ситуациях они вам нужны. В этом случае вы можете принудительно загрузить нагрузку, добавив в запрос запрос Include. например,

var q = from t in this.objectSet.Include('TrapActivities') 
     select t; 

Это загружает все TrapActivities с ловушкой в ​​одном запросе. Однако в вашем случае вам нужна только первая операция развертывания, поэтому я создал класс TrapFirstDeployment. Таким образом, EF должен только захватить первую операцию развертывания.

Update 2

Вы должны также изменить значение параметра на методе Find на вашем репозитории Expression<Func<T,Boolean>>, чтобы соответствовать IQueryable.Where подпись. IEnumerable.Where использует Func<T,Boolean>, поэтому objectSet преобразуется в IEnumberable до того, как Where называется.

+0

Вот что мне кажется, как и мне - можете ли вы порекомендовать исправить то, что у меня есть, чтобы избавиться от этого? Это изменение .edmx или изменение реализации репозитория? – Tommy

+0

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

+0

Хорошо замечен, Брайан :) –

1

Как @SLC сказал: вам нужно посмотреть на SQL, который генерирует EF - вы будете поражены.

Я рекомендую использовать LINQPad. Есть бесплатные и платные версии.

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


Исправление может быть столь же легко, как возвращение IQueryable из Find вместо IEnumerable.

+0

Да, я попытался изменить метод .Find(), но оказалось, что подпись метода для this.objectSet.Where (предикат) возвращает IEnumerable. Intellisense показывает IQueryable. Где-то, но я не вижу, почему он использует IEnumerable. – Tommy

+1

Нечетный! Вы всегда можете заставить его, добавив [.AsQueryable()] (http://msdn.microsoft.com/en-us/library/bb353734.aspx) –

+0

Хорошо - выяснилось, но все еще наблюдается такое же поведение. .Find() возвращает IQueryable , используя следующее: public IQueryable Find (Func предикат) {return this.objectSet.Where (предикат) .AsQueryable();} – Tommy

Смежные вопросы