【问题标题】:Would this SingleOrDefault() optimization be worthwhile or is it overkill / harmful?这种 SingleOrDefault() 优化是否值得还是过度杀伤/有害?
【发布时间】:2010-10-24 05:35:40
【问题描述】:

我在搞乱 LinqToSQL 和 LINQPad,我注意到 SingleOrDefault() 不会在生成的 SQL 中进行任何过滤或限制(我几乎预计相当于 Take(1))。

所以假设您想保护自己免受意外退货的影响,那么以下 sn-p 是否有用还是一个坏主意?


// SingleType is my LinqToSQL generated type 
// Singles is the table that contains many SingleType's
// context is my datacontext
public SingleType getSingle(int id)
{
     var query = from s in context.Singles where s.ID == id select s;
     var result = query.Take(2).SingleOrDefault();
     return result;
}

与通常的方式相反,我会这样做(注意没有 .Take(2) )


public SingleType getSingle(int id)
{
     var query = from s in Singles where s.ID == id select s;
     var result = query.SingleOrDefault();
     return result;
}

我认为使用 Take(2),我仍然可以获得 SingleOrDefault() 的功能,另外还有一个好处是永远不必担心意外返回 {n} 行,但我不确定它是否值得除非我一直期望在我的查询中意外返回 {n} 行。

那么,这值得吗?它有害吗?有没有我没有看到的优点/缺点?

编辑:

不带 Take(2) 的 SQL 生成


SELECT [t0].[blah], (...) 
FROM [dbo].[Single] AS [t0]
WHERE [t0].[ID] = @p0

使用 Take(2) 生成的 SQL


SELECT TOP 2 [t0].[blah], (...) 
FROM [dbo].[Single] AS [t0]
WHERE [t0].[ID] = @p0

另外,当我谈到 SingleOrDefault 的功能时,我特别希望在返回 2 个或更多时让它抛出异常,这就是我正在执行“Take(2)”的原因。不同之处在于,如果没有 .Take(2),它将从数据库返回 {n} 行,而实际上它只需要返回 2(足以让它抛出)。

【问题讨论】:

  • 你看过生成的SQL了吗?我从未验证过,但我认为 SingleOrDefault 会获得 TOP 1。
  • 当列上没有唯一约束时,TOP 1 不足以确定最多有一行。所以它必须是 TOP 2 或者如果该列具有唯一约束,则可以省略。

标签: c# sql linq-to-sql optimization


【解决方案1】:

如果序列包含多个元素,SingleOrDefault(和 IEnumerable<T>.SingleOrDefault())都会引发 InvalidOperationException。

您的上述情况永远不会发生 - 它会引发异常。


编辑:

我的建议取决于您的使用场景。如果您认为在某些情况下您会定期从该查询返回多于几行,那么添加 .Take(2) 行。这将为您提供相同的行为和相同的异常,但消除了从数据库返回许多记录的可能性。

但是,您对 SingleOrDefault() 的使用表明不应返回 >1 行。如果真的是这样,我会把它关掉,只是把它当作一个例外。在我看来,您通过暗示添加 .Take(2) 时有 >2 条记录是正常的,从而降低了代码的可读性,在这种情况下,我不相信这是真的。我会接受性能。为了简单起见,请在特殊情况下将其关闭。

【讨论】:

  • 对,我指定了,这就是为什么我要做一个 Take(2),但是,从两者生成的 SQL 是不同的,我将在我的帖子中提出
  • @Allen:您的帖子编辑显示两者是相同的。不过,我不会担心的。如果您有 >1 个元素,这是一个例外情况(并且会引发异常)。真的,如果您处于这种情况,您还有其他问题需要处理。轻微的性能。在抛出异常之前删除它比我在数据库中得到重复 ID 更令人担忧。
  • 啊,我搞砸了编辑,它现在已修复,没有 .Take(2) 你将不会获得“前 2”,抱歉 :)
  • @Allen:更新了我的答案:你的 cmets。如果您担心 Take(2),几乎没有实际理由避免使用它。
【解决方案2】:

Allen,ID 是 Singles 表的主键吗?如果是我不完全理解您的问题,因为您的第二个查询将返回一条记录或 null。 SQL 将是 ID = ###... 使用 Take(2).SingleOrDefault() 违背了 SingleOrDefault 的目的。

【讨论】:

  • 在这个例子中,是的,而且我意识到这可能是一个非常糟糕的例子,因为它在技术上不能返回多个。想象一下,如果它是一个“按名称获取...”,它可能返回多个,而您需要考虑这一点。
【解决方案3】:

你已经提到了这一点。如果您期望查询经常返回大量行,它可能会给您带来性能提升。但如果这是一个例外情况——SingleOrDefault() 清楚地表明——不值得付出努力。它只会污染你的代码,如果你决定让它进来,你应该记录下来。

更新

刚刚注意到您正在查询 id。我假设它是主键,结果你会得到一或零行。所以理论上你不应该太在意使用Single()First()Take(1) 或其他什么。但我仍然认为使用Single() 明确声明您只需要一行是很好的设计。几周前,一个团队告诉我,他们甚至有一个项目,其中出现了严重错误,并且由于市长数据库故障,主键不再是唯一的。所以安全总比后悔好。

【讨论】:

  • 是的,对不起,不好的例子,我在 Gustavo 的回答中通过评论解决了这个问题。哦,哎呀,我从没想过要考虑架构更改。
【解决方案4】:

Single 更多的是获取查询的单个元素的便捷方法,而不是限制结果数量的方法。通过使用Single,您实际上是在说“我知道这个查询只能有一个项目,所以把它给我吧”,就像在你知道只有一个元素时执行someArray[0] 时一样。 SingleOrDefault 增加了在处理长度为 0 的序列时返回 null 而不是抛出异常的能力。您不应该将 SingleSingleOrDefault 用于可能返回超过 1 个结果的查询:@987654328 @ 将被抛出。

如果查询中的ID 是表的主键或UNIQUE 列,则数据库将确保结果集包含1 行或没有,而无需TOP 子句。

但是,如果您在非唯一/非键列上进行选择并且想要第一个结果或最后一个结果(请注意,除非您还引入了 OrderBy,否则这些没有任何意义),那么您可以使用 FirstLast(两者都有 OrDefault 对应项)来获得您想要的 SQL:

var query = from s in context.Singles 
            where s.Id == id
            orderby s.someOtherColumn
            select s;

var item = query.FirstOrDefault();

附带说明一下,如果您确实是针对单个元素进行查询,则可以节省一些输入:

var query = from s in context.Singles where s.Id == id select s;
var item = query.SingleOrDefault();

可以变成:

var item = context.Singles.SingleOrDefault(s => s.Id == id);

【讨论】:

  • 你是对的,现在我想起来了,如果我认为我可能会返回多个结果,我不会使用 SingleOrDefault,我会处理它完全不同。我想这是一个过度思考的经典例子
猜你喜欢
  • 2013-09-21
  • 2011-06-24
  • 2011-04-16
  • 2011-02-09
  • 1970-01-01
  • 1970-01-01
  • 2020-03-03
  • 2013-04-02
  • 2012-02-17
相关资源
最近更新 更多