gpt4 book ai didi

c# - 使用EF4仅获得SQL表的最后一行的最有效方法是什么?

转载 作者:行者123 更新时间:2023-12-02 00:42:15 27 4
gpt4 key购买 nike

我正在通过表的ID列检索表的最后一行。我目前正在使用的作品:

var x = db.MyTable.OrderByDescending(d => d.ID).FirstOrDefault();


有什么方法可以更快地获得相同的结果吗?

最佳答案

我看不到整个表都在查询。

您在ID列上没有索引吗?

您可以将查询分析的结果添加到您的问题中,因为这不是应该的。

除分析结果外,还生成了SQL。我只看到显式的列名和一些别名就看不到除select top 1 * from MyTable order by id desc之外的其他内容。在id上也没有索引,除了扫描该索引外,还有什么其他的。

编辑:承诺的解释。

Linq为我们提供了一组通用接口,并且在C#和VB.NET的情况下提供了一些关键字支持,用于对返回0或更多项的源进行各种操作(例如,内存中的集合,数据库调用,XML文档的解析)等)。

这使我们可以表达类似的任务,而不管其底层来源如何。例如,您的查询包含源,但我们可以采用更通用的形式:

public static YourType FinalItem(IQueryable<YourType> source)
{
return source.OrderByDesending(d => d.ID).FirstOrDefault();
}


现在,我们可以做:

IEnumerable<YourType> l = SomeCallThatGivesUsAList();
var x = FinalItem(db.MyTable);//same as your code.
var y = FinalItem(l);//item in list with highest id.
var z = FinalItem(db.MyTable.Where(d => d.ID % 10 == 0);//item with highest id that ends in zero.


但是真正重要的部分是,尽管我们有一种方法可以定义我们想要完成的操作,但是我们可以将实际的实现隐藏起来。

调用 OrderByDescending会生成一个对象,该对象的源信息以及将在排序中使用的lambda函数。

FirstOrDefault的调用又具有有关此信息,并使用它来获取结果。

对于列表,实现是生成等效的基于 Enumerable的代码( QueryableEnumerable彼此镜像公共成员,它们使用的接口(例如 IOrderedQueryableIOrderedEnumerable和依此类推)。

这是因为,对于我们不知道的列表,已经按照我们关心的顺序(或相反的顺序)进行了排序,因此没有比检查每个元素更快的方法了。我们可以期望的最好的结果是O(n)操作,并且我们可能会得到O(n log n)操作-取决于订购的实现是否针对仅从其中获取一项的可能性进行了优化*。

或换一种说法,我们最好的手工编码方式仅适用于可枚举的代码,其效率仅比以下方式高一点:

public static YourType FinalItem(IEnumerable<YourType> source)
{
YourType highest = default(YourType);
int highestID = int.MinValue;
foreach(YourType item in source)
{
curID = item.ID;
if(highest == null || curID > highestID)
{
highest = item;
highestID = curID;
}
}
return highest;
}


我们可以通过一些微选项直接处理枚举数来做得更好,但是只有一点点,并且额外的复杂性只会使效果不佳的示例代码成为可能。

由于我们不能做任何比这更好的事情,并且由于linq代码对源代码的了解比我们更多,所以这是我们可能希望匹配它的最好方法。它可能做得不太好(再次,取决于是否只考虑了我们只想要一项的特殊情况),但它不会胜过它。

但是,这不是linq会采用的唯一方法。对于内存中可枚举的源,它将采用可比的方法,但是您的源并非如此。

db.MyTable代表一个表。枚举它可以使我们获得的SQL查询结果大致等于:

SELECT * FROM MyTable


但是, db.MyTable.OrderByDescending(d => d.ID)不等同于调用它,然后在内存中对结果进行排序。因为查询在执行时会被整体处理,所以实际上我们获得的SQL查询结果大致如下:

SELECT * FROM MyTable ORDER BY id DESC


最后,整个查询 db.MyTable.OrderByDescending(d => d.ID).FirstOrDefault()产生的查询如下:

SELECT TOP 1 * FROM MyTable ORDER BY id DESC


要么

SELECT * FROM MyTable ORDER BY id DESC LIMIT 1


取决于您使用的是哪种数据库服务器。然后将结果传递给等效于以下基于ADO.NET的代码的代码:

return dataReader.Read() ?
new MyType{ID = dataReader.GetInt32(0), dataReader.GetInt32(1), dataReader.GetString(2)}//or similar
: null;


你不会变得更好。

至于那个SQL查询。如果 id列上有一个索引(并且由于它看起来像主键,肯定应该有),那么该索引将用于非常快速地找到所涉及的行,而不是检查每一行。

总而言之,由于不同的linq提供程序使用不同的方式来完成查询,因此他们都可以尽最大的努力以最好的方式做到这一点。当然,在一个不完美的世界中,我们无疑会发现有些比其他的更好。而且,他们甚至可以努力为不同条件选择最佳方法。这样的一个例子是与数据库有关的提供者可以选择不同的SQL来利用不同版本的数据库的功能。另一个问题是,在内存枚举中使用的 Count()版本的实现有点类似。

public static int Count<T>(this IEnumerable<T> source)
{
var asCollT = source as ICollection<T>;
if(asCollT != null)
return asCollT.Count;
var asColl = source as ICollection;
if(asColl != null)
return asColl.Count;
int tally = 0;
foreach(T item in source)
++tally;
return tally;
}


这是较简单的情况之一(在这里的示例中再次进行了简化,我展示的不是实际的代码),但是它显示了代码的基本原理,并在可行时利用了更有效的方法-数组的O(1)长度和集合中的 Count属性有时为O(1),这并不像我们在O(n)的情况下使情况变得更糟-然后当它们不是可用的方法会退回到效率较低但仍能起作用的方法。

所有这些的结果是,就性能而言,Linq往往会带来很好的性价比。

现在,一个体面的编码器在大多数情况下应该能够在任何给定情况下匹配或击败其方法†,即使Linq提出了理想的方法,它本身也会有一些开销。

但是,在整个项目的范围内,使用Linq意味着我们可以简洁地创建与高效定义的实体数量相对有限的代码有关的代码(就数据库而言,通常每个表一个)。特别是,使用匿名函数和联接意味着我们得到的查询很好。考虑:

var result = from a in db.Table1
join b in db.Table2
on a.relatedBs = b.id
select new {a.id, b.name};


在这里,我们忽略了我们不在乎的列,并且生成的SQL将执行相同的操作。考虑一下如果创建与手工编码的DAO类相关的 ab对象的方法:


创建一个新类来表示 a的ID和 b的名称的组合,以及用于运行我们需要产生实例的查询的相关代码。
运行查询以获取有关每个 a和相关的 b的所有信息,并避免浪费。
运行查询以获取有关我们关心的每个 ab的信息,并为其他字段设置默认值。


其中,选择2将是浪费的,也许是非常浪费的。选项3有点浪费,而且很容易出错(如果我们不小心尝试使用代码中未正确设置的其他字段,该怎么办?)。只有选项1比linq方法产生的效率更高,但这只是一种情况。在一个大型项目中,这可能意味着产生数十个甚至数百个或数千个稍有不同的类(并且与编译器不同,我们不一定会发现它们实际上是相同的情况)。因此,在实践中,linq可以在效率方面为我们提供一些帮助。

有效的linq的好的策略是:


尽可能保持与您开始的查询类型相同。每当您使用 ToList()ToArray等将项目捕获到内存中时,请考虑是否确实需要这样做。除非您需要或可以明确说明这样做所带来的好处,否则就不要这样做。
如果确实需要转移到内存中进行处理,则优先使用 AsEnumerable()而不是 ToList()和其他方法,因此一次只能抓取一个。
使用SQLProfiler或类似方法检查长时间运行的查询。在少数情况下,此处的策略1是错误的,并且使用 AsEnumerable()转移到内存实际上更好(大多数与 GroupBy的使用有关,这些用法不对非分组字段使用聚合,因此不实际上只有一个与之对应的SQL查询)。
如果多次命中一个复杂的查询,则 CompiledQuery可以提供帮助(对于4.5来说则更少,因为它具有自动优化功能,可以解决某些情况),但是通常最好将其排除在第一种方法之外,然后仅在存在效率问题的热点中使用它。
您可以使EF运行任意SQL,但除非有很大收获,否则应避免使用它,因为过多的此类代码会在整个Gives中使用linq方法降低一致的可读性(我不得不说,我认为Linq2SQL在调用存储过程甚至击败EF时关于调用UDF的更多信息,但即使在这里仍然适用-从仅查看代码之间的相互关系还不清楚。


* AFAIK,没有应用此特定的优化,但目前我们正在讨论最佳的实现方式,因此无论是,不是或仅在某些版本中,都没有关系。

†尽管,我会承认Linq2SQL经常会生成使用我不会想到的APPLY的查询,因为我曾经想过如何在2005年引入SQL Server版本之前编写查询,而代码却没有这种排序人类倾向与旧习惯。它几乎教会了我如何使用APPLY。

关于c# - 使用EF4仅获得SQL表的最后一行的最有效方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12232708/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com