Entity framework 通过OrderByDescending和SaveChanges使用Linq到实体
我看到Linq对实体的行为与我对Linq工作原理的理解不一致。请考虑以下片段:Entity framework 通过OrderByDescending和SaveChanges使用Linq到实体,entity-framework,c#-4.0,linq-to-entities,Entity Framework,C# 4.0,Linq To Entities,我看到Linq对实体的行为与我对Linq工作原理的理解不一致。请考虑以下片段: MWGRCEntities entities = new MWGRCEntities(); foreach (EDMXModel.Classes.RiskScoreMetric rsm in entities.RiskScoreMetrics.Where(rsmq.StatusCode != (int)KnownCodes.RiskScoreMetricStatusInActive)) { //Magic
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循环中使用在第一个foreach循环中设置的ImpactTotal和LikelihoodTotal值进行排序。然而,Linq似乎在第二个foreach循环中根据原始的ImpactTotal和LikelihoodTotal值(如中所示,数据库中的值不是内存中的值)进行排序。我只需在第二个foreach循环之前添加对entities.SaveChanges()的第二个调用,就可以轻松修复代码
有人能告诉我这种行为是否是预期的吗?如果是,为什么
谢谢大家! 您需要记住,这里使用的
OrderbyDescending
是IQueryable
的扩展方法,而不是IEnumerable
。此扩展方法和用作此方法参数的LINQ表达式-rsmq=>Math.Round(…)
-不会在内存中的数据结构/集合上执行,但它仅表示表达式树。表达式树的实际情况取决于数据提供程序(在类型为IQueryable
的queryable对象内部引用)。在Entity Framework/LINQ to Entities的情况下,此提供程序将表达式树转换为SQL字符串(方言取决于该提供程序的详细信息,例如SQL Server的T-SQL、Oracle或MySQL的其他原生SQL方言等)
转换后的SQL将被发送到数据库服务器,并将在数据库引擎中执行,而数据库引擎不知道您对内存中已加载的实体所做的任何更改
所有LINQ to Entities查询始终基于表中的当前状态和数据值在数据库中执行。他们从不考虑你是否已经加载了实体,他们有哪些价值,不管是否改变。(DbSet.Find
或ObjectSet.GetObjectByKey
是检查具有提供键的实体是否已加载到内存中的唯一例外,但这些方法不是LINQ to Entities查询,尽管如果它们无法找到实体,它们将发出LINQ to Entities查询,即SingleOrDefault
附加到上下文。)
作为旁注:需要将表达式树转换为SQL也是不能在LINQ to Entities查询中使用任意.NET方法的原因,因为在大多数情况下,不可能转换为SQL,或者LINQ to Entities提供程序不知道如何转换它。类似于
rsmq => MySpecialRoundMethod(...)
…其中,
MySpecialRoundMethod
是您用C编写的自定义方法,将使用LINQ to对象(在IEnumerable
上),但不使用LINQ to实体(在IQueryable
上)。碰巧对于Math.Round(…)
实现了到SQL的转换,这样您就可以将其与实体框架一起使用了。Awesome response。非常感谢你。我注意到有些方法不能用在这些表达式中,不知道为什么。我现在更明白发生了什么。这是否意味着,如果我将一个对象插入到上下文中,然后尝试使用select linq查询查找它,我将找不到它,因为它尚未插入到数据库中?@AEberhard:是的,没错。您看到的异常可能是臭名昭著的“无法转换为存储表达式”异常,这是最常被问到的问题之一。我相信人们对这一点感到困惑的是术语“存储表达式”,在99%的情况下,它只表示“SQL”。我认为他们使用的是更抽象的术语,因为EF不需要翻译成SQL。如果有一家公司有一个特殊的数据库系统和一种叫做“Babble”的专有查询语言,他们可以为Babble编写一个LINQ to Entities提供者。“商店用语”应该是“胡言乱语”。