2013-10-03 4 views
4

Давайте предположим, что у меня есть эти классы:Как сделать привязку EntityFramework IQueryable?

public class Car 
{ 
    public int CarId { get; set} 
    public virtual ICollection<Door> Doors { get; set} 
} 

public class Door 
{ 
    public int DoorId { get; set} 
    public decimal Weight { get; set} 
    public int CarId { get; set} 
} 

И я хочу сделать что-то вроде этого

foreach (var car in db.Cars) 
{ 
    var x = car.Doors.Min(d => d.Weight); 
} 

Как я вижу в EFTraceLog он делает что-то вроде Select * от дверей, где CarId = @. .. и вычисляет «Мин» на сервере приложений, а не на сервере db.

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

foreach (var car in db.Cars) 
{ 
    var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight); 
} 

, то это несколько секунд.

Почему существует такая большая разница и как ее исправить? Проблема здесь состоит в том, что это гораздо более проще написать

var x = car.Doors.Min(d => d.Weight); 

затем

var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight); 

Update

Мы используем Entity Framework 5.0

Update 2

Я пробовал эти варианты, они медленно

var x = car.Doors.Select(door => door.Weight).Min(); 
var x = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault(); 
var x = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).First(); 
var x = car.Doors.OrderBy(x => x.Weight).FirstOrDefault().Weight; 
var x = car.Doors.OrderBy(x => x.Weight).First().Weight; 

Только этот один быстрый

var x = db.Doors.Where(d => d.CarId == car.CarId).Min(d => d.Weight); 

Update 3

Лучший запрос производит этот SQL

declare @p__linq__0 Int32 = cast(N'204' as Int32); 

SELECT 
[GroupBy1].[A1] AS [C1] 
FROM (SELECT 
MIN([Extent1].[Weight]) AS [A1] 
FROM [dbo].[Doors] AS [Extent1] 
WHERE [Extent1].[CarId] = @p__linq__0 
) AS [GroupBy1] 
+0

Что SQL создает оператор 'Where' (ваша последняя строка)? – haim770

+0

обновленный вопрос * –

ответ

5

Linq to Entities поддерживает только Min без дополнительной проекции. Это означает, что ваш текущий Min будет вызван с использованием Linq To Objects (что приведет к созданию вашей коллекции Doors - следовательно, полученного SQL, который вы видите в ваших журналах).

От MSDN:

Поддерживаемые

TSource Min<TSource>(this IQueryable<TSource> source) 

Не поддерживается:

TResult Min<TSource, TResult>(this IQueryable<TSource> source,Expression<Func<TSource, TResult>> selector) 

Вы можете попробовать использовать OrderBy, Select и FirstOrDefault (что являются поддерживаются), чтобы достичь того же результата:

var min = car.Doors.OrderBy(x => x.Weight).Select(x => x.Weight).FirstOrDefault(); 

UPDATE:

Видимо Entity Framework не поддерживает отложенной загрузки с Projection, то это означает, что всякий раз, когда вы загружаете ссылки сущностей (Car.Doors в ваш случай), фреймворк Lazy-Load все его данные, и вы не можете выбрать, какое свойство (Weight в вашем случае) загрузить.

Это причина разницы между 2 вызовами:

// Accessing 'Doors' thru 'Car' means 'Lazy Load' 
car.Doors.Select(x => x.Weight).Min(); 

Но

// No 'Lazy Load' involved, hence projection is possible 
db.Doors.Where(x => x.CarId == carId).Select(x => x.Weight).Min(); 

Вы также можете попытаться получить доступ к Car.Doors и нетерпеливых ЗагрузитеDoors:

foreach (var car in db.Cars.Include(x => x.Doors)) 
{ 
    var x = car.Doors.Select(x => x.Weight).Min(); 
} 

(я использовал t он сократил альтернативу Min, предложенный Servy)

+0

У меня не было возможности проверить, поэтому я удалил. – christiandev

+0

Я попробовал car.Doors.OrderBy (x => x.Weight). Выберите (x => x.Weight) .First(); И car.Doors.OrderBy (x => x.Weight) .First(). Вес. Оба не помогают. Еще один запрос: выберите * from Doors, где CarId = @ ... –

+0

@YegorRazumovsky. Это другой вопрос, чем вам было предложено. EF не поддерживает «First», а только «FirstOrDefault». – Servy

0

Согласно haim770's answer, перегрузка Min, которая принимает проекции не поддерживается EF, но вы можете просто выполнить проекцию первого использования Select вместо того, чтобы получить желаемое результат:

var minWeight = db.Doors.Select(door => door.Weight).Min(); 
+0

Slow again, извините –

+0

@YegorRazumovsky Похоже, что ваш поставщик запросов настроен неправильно. Удовлетворить ли * любые * запросы? Правильно ли переведены другие запросы на эту таблицу? – Servy

+0

этот работает отлично var x = db.Doors.Where (d => d.CarId == car.CarId) .Min (d => d.Weight); –

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