使用Linq to Entities与OrderByDescending和SaveChanges

时间:2012-08-07 20:46:30

标签: entity-framework c#-4.0 linq-to-entities

我看到Linq对Entitites的行为与我对Linq如何运作的理解不符。请考虑以下代码段:

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对象将使用第一个foreach循环中设置的ImpactOverall和LikelihoodOverall值在第二个foreach循环中进行排序。然而,似乎Linq基于原始的ImpactOverall和LikelihoodOverall值在第二个foreach循环中进行排序(例如,数据库中的值不是内存中的值)。我可以通过在第二个foreach循环之前立即添加对entities.SaveChanges()的第二次调用来轻松修复代码。

有人能告诉我这种行为是否是预料到的,为什么会这样?

谢谢!

1 个答案:

答案 0 :(得分:2)

您需要记住,您在此处使用的OrderbyDescendingIQueryable<T>的扩展方法,而不是IEnumerable<T>。此扩展方法和您用作此方法的参数的LINQ表达式 - rsmq => Math.Round(...) - 不会在内存中的数据结构/集合上执行,但它仅表示表达式树。该表达式树实际发生的情况取决于数据提供程序(在IQueryable<T>类型的可查询对象内引用)。对于Entity Framework / LINQ to Entities,此提供程序将表达式树转换为SQL字符串(取决于该提供程序的详细信息的方言,例如SQL Server的T-SQL,Oracle的其他一些本机SQL方言或MySQL等。)。

已翻译的SQL将被发送到数据库服务器,并将在数据库引擎中执行,该引擎不知道您对内存中已加载实体所做的更改。

所有 LINQ to Entities查询始终在数据库中根据表中的当前状态和数据值执行。他们从不考虑您是否已经加载了实体,它们具有哪些值,无论是否更改。 (DbSet<T>.FindObjectSet<T>.GetObjectByKey是检查具有提供的密钥的实体是否已经加载到内存中的唯一例外,但这些方法不是LINQ to Entities查询,尽管它们将发出LINQ to Entities查询,即SingleOrDefault,如果他们找不到已附加到上下文的实体。)

作为旁注:需要将表达式树转换为SQL也是因为在LINQ to Entites查询中不能使用任意.NET方法的原因,因为在大多数情况下,没有可能转换为SQL或LINQ to实体提供商不知道如何翻译它。有点像...

rsmq => MySpecialRoundMethod(...)

...其中MySpecialRoundMethod是一个用C#编写的自定义方法,可以使用LINQ to Objects(在IEnumerable<T>上)而不是LINQ to Entities(在IQueryable<T>上) )。碰巧是Math.Round(...)实现了对SQL的翻译,以便您可以将它与Entity Framework一起使用。