2012-08-07 2 views
1

Я вижу поведение Linq для Entitites, которое не соответствует моему пониманию того, как работает Linq. Пожалуйста, обратите внимание на следующий фрагмент кода:Использование Linq для объектов с OrderByDescending и SaveChanges

MWGRCEntities entities = new MWGRCEntities(); 

foreach (EDMXModel.Classes.RiskScoreMetric rsm in entities.RiskScoreMetrics.Where(rsmq.StatusCode != (int)KnownCodes.RiskScoreMetricStatusInActive)) 
{ 
    //Magic happens here... 
    rsm.ImpactOverall = (rsm.ImpactWorkingGroup + rsm.ImpactExecutive)/2; 
    rsm.LikelihoodOverall = (rsm.LikelihoodWorkingGroup + rsm.LikelihoodExecutive)/2; 
} 

int rank = 0; 
double prevScore = -1; 
double score = -2; 
foreach (EDMXModel.Classes.RiskScoreMetric rsm in entities.RiskScoreMetrics.Where(rsmq.StatusCode != (int)KnownCodes.RiskScoreMetricStatusInActive).OrderByDescending(rsmq => Math.Round((Math.Round(rsmq.ImpactOverall, 3) + Math.Round(rsmq.LikelihoodOverall, 3)), 3))) 
{ 
    score = Math.Round((Math.Round(rsm.ImpactOverall, 3) + Math.Round(rsm.LikelihoodOverall, 3)), 3); 

    if (score != prevScore) 
     rank++; 

    rsm.Ranking = rank; 
    prevScore = score; 
} 

entities.SaveChanges(); 

Я ожидал, что RiskScoreMetric объекты будут отсортированы во втором цикле Еогеасп с использованием значений ImpactOverall и LikelihoodOverall, установленные в первом цикле Еогеасп. Однако представляется, что Linq сортирует во втором цикле foreach на основе исходных значений ImpactOverall и LikelihoodOverall (как и в значениях в базе данных, а не в памяти). Я могу легко исправить код, просто добавив второй вызов сущностям .SaveChanges() непосредственно перед вторым циклом foreach.

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

Спасибо!

ответ

2

Вы должны иметь в виду, что OrderbyDescending, который вы используете здесь, является методом расширения от IQueryable<T>, а не IEnumerable<T>. Этот метод расширения и выражение LINQ, которое вы используете в качестве параметра этого метода - rsmq => Math.Round(...) - не выполняются в структуре данных/коллекции в памяти, а представляют собой дерево выражений. То, что на самом деле происходит с этим деревом выражений, зависит от поставщика данных (на который ссылается внутри запрашиваемого объекта типа IQueryable<T>). В случае Entity Framework/LINQ to Entities этот провайдер преобразует дерево выражений в строку SQL (на диалекте, который зависит от деталей этого провайдера, например T-SQL для SQL Server, некоторого другого родного диалекта SQL для Oracle или MySQL и т. Д.).

Переведенный SQL получает отправку на сервер базы данных и будет выполнен в механизме базы данных, который ничего не знает об изменениях, внесенных вами в уже загруженные объекты в памяти.

Все Запросы LINQ to Entities всегда выполняются в базе данных на основе текущих значений состояния и данных в таблицах. Они никогда не считают, что вы уже загрузили объекты, какие значения у них есть, и независимо от того, изменились они или нет. (DbSet<T>.Find или ObjectSet<T>.GetObjectByKey являются единственными исключениями, которые проверяют, уже ли объект с указанным ключом уже загружен в память, но эти методы не являются запросами LINQ to Entities, хотя они будут выдавать запрос LINQ to Entities, а именно SingleOrDefault, если они не могут " t найти объект, который уже привязан к контексту.)

В качестве дополнительного примечания. Необходимость перевода дерева выражений в SQL также является причиной того, что вы не можете использовать произвольные методы .NET в запросе LINQ to Entites, поскольку в в большинстве случаев перевода на SQL невозможно, или поставщик LINQ to Entities не знает, как его перевести. Что-то вроде ...

rsmq => MySpecialRoundMethod(...) 

... где MySpecialRoundMethod обычай метод, который вы написали в C# будет работать с LINQ к объектам (на IEnumerable<T>), но не с помощью LINQ к Entities (на IQueryable<T>). Случается, что для Math.Round(...) реализован перевод на SQL, чтобы вы могли использовать его с Entity Framework.

+0

Удивительный отклик. Большое спасибо. Я заметил, что некоторые методы не могут быть использованы в этих выражениях и не знают, почему. Теперь я понимаю, что происходит намного больше. Означает ли это, что если я вставляю объект в контекст, то попытайтесь найти его с помощью запроса select linq, который я не найду, потому что он не был вставлен в базу данных? – AEberhard

+1

@AEberhard: Да, точно.Исключением, которое вы видели, вероятно, является позорным исключение «не могу перевести в хранилище», и это один из наиболее часто задаваемых вопросов, что это значит. Я считаю, что люди путаются в этом вопросе - это термин «выражение хранилища», которое в 99% всех случаев означает «SQL». Я думаю, что они используют более абстрактный термин, потому что EF не требует перевода в SQL. Если бы существовала компания со специальной системой БД и фирменным языком запросов, называемым «Babble», они могли бы написать провайдер LINQ to Entities для Babble. Тогда выражение «store» будет «Babble». – Slauma

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