C# 为什么这个linq扩展方法会两次命中数据库?

C# 为什么这个linq扩展方法会两次命中数据库?,c#,.net,linq,C#,.net,Linq,我有一个名为ToListIfNotNullOrEmpty的扩展方法,它对DB的命中是两次,而不是一次。第一次返回一个结果,第二次返回所有正确的结果 我很确定它第一次访问数据库时,是调用.Any方法的时候 这是密码 public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value) { if (value.IsNullOrEmpty()) { re

我有一个名为ToListIfNotNullOrEmpty的扩展方法,它对DB的命中是两次,而不是一次。第一次返回一个结果,第二次返回所有正确的结果

我很确定它第一次访问数据库时,是调用.Any方法的时候

这是密码

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    if (value.IsNullOrEmpty())
    {
        return null;
    }
    if (value is IList<T>)
    {
        return (value as IList<T>);
    }
    return new List<T>(value);
}

public static bool IsNullOrEmpty<T>(this IEnumerable<T> value)
{
    if (value != null)
    {
        return !value.Any();
    }
    return true;
}
我真的不想调用ToList然后调用我的扩展方法。 各位,有什么想法吗?

您可以将.ToList调用放在分机内,效果稍有不同,但这在您的情况下仍然有效吗

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    if(value == null)
    {
        return null;
    }
    var result = value.ToList();        
    return result.IsNullOrEmpty() ? null : result;
}
您可以将.ToList调用粘贴到分机内部,效果略有不同,但在您使用的情况下仍然有效吗

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    if(value == null)
    {
        return null;
    }
    var result = value.ToList();        
    return result.IsNullOrEmpty() ? null : result;
}

像这样的怎么样

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    if (value == null)
        return null;

    var list = value.ToList();
    return (list.Count > 0) ? list : null;
}

像这样的怎么样

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    if (value == null)
        return null;

    var list = value.ToList();
    return (list.Count > 0) ? list : null;
}

我承认我看不出这种方法有什么意义。当然,如果你只是做一个收费表,检查一下列表是否为空也就足够了。当您需要一个列表时,处理空结果可能会比较困难,因为在迭代它之前,您总是必须检查空值

我认为:

 var query = (from ...).ToList();
 if (query.Count == 0) {
     ...
 }
工作起来也很好,负担也比以前轻

var query = (from ...).ToListIfNotNullOrEmpty();
if (query == null) {
   ...
}

而且您不必实现和维护任何代码。

我承认我认为这种方法没有什么意义。当然,如果你只是做一个收费表,检查一下列表是否为空也就足够了。当您需要一个列表时,处理空结果可能会比较困难,因为在迭代它之前,您总是必须检查空值

我认为:

 var query = (from ...).ToList();
 if (query.Count == 0) {
     ...
 }
工作起来也很好,负担也比以前轻

var query = (from ...).ToListIfNotNullOrEmpty();
if (query == null) {
   ...
}

您不必实现和维护任何代码。

要真正回答您的问题:

此方法会两次命中数据库,因为System.Linq.Enumerable类提供的扩展方法显示了所谓的延迟执行。本质上,这是为了消除为查询的每个部分构造临时缓存集合字符串的需要。要理解这一点,请考虑下面的例子:

var firstMaleTom = people
    .Where(p => p.Gender = Gender.Male)
    .Where(p => p.FirstName == "Tom")
    .FirstOrDefault();
如果没有延迟执行,上述代码可能需要枚举整个集合中的人员,用所有性别为男性的人员填充临时缓冲区数组。然后需要再次枚举,用第一个缓冲区中名为Tom的所有个体填充另一个缓冲区数组。完成所有这些工作后,最后一部分将返回结果数组中的第一项

那是一大堆毫无意义的工作。延迟执行的思想是,上面的代码实际上只是使用所需的信息来设置firstMaleTom变量,以便以最少的工作量返回所请求的内容


现在,这有另一面:在查询数据库的情况下,延迟执行意味着在计算返回值时查询数据库。因此,在您的IsNullOrEmpty方法中,当您调用Any时,value参数实际上就在此时此地被求值——因此是一个数据库查询。在此之后,在ToListIfNotNullOrEmpty方法中,行return new Listvalue也会计算value参数,因为它会枚举这些值并将它们添加到新创建的列表中。

要真正回答您的问题:

此方法会两次命中数据库,因为System.Linq.Enumerable类提供的扩展方法显示了所谓的延迟执行。本质上,这是为了消除为查询的每个部分构造临时缓存集合字符串的需要。要理解这一点,请考虑下面的例子:

var firstMaleTom = people
    .Where(p => p.Gender = Gender.Male)
    .Where(p => p.FirstName == "Tom")
    .FirstOrDefault();
如果没有延迟执行,上述代码可能需要枚举整个集合中的人员,用所有性别为男性的人员填充临时缓冲区数组。然后需要再次枚举,用第一个缓冲区中名为Tom的所有个体填充另一个缓冲区数组。完成所有这些工作后,最后一部分将返回结果数组中的第一项

那是一大堆毫无意义的工作。延迟执行的思想是,上面的代码实际上只是使用所需的信息来设置firstMaleTom变量,以便以最少的工作量返回所请求的内容


现在,这有另一面:在查询数据库的情况下,延迟执行意味着在计算返回值时查询数据库。因此,在您的IsNullOrEmpty方法中,当您调用Any时,value参数实际上就在此时此地被求值——因此是一个数据库查询。在此之后,在ToListFNotNullOrEmpty方法中,行return new Listvalue还计算value参数,因为它正在枚举这些值并将它们添加到新创建的列表中。

我会根据现有的FirstOrDefault、SingleOrDefault约定将其重命名为ToListNull。@tvanfosson:我可能不会创建一个
n扩展首先要做到这一点。LINQ查询产生的IEnumerable永远不应该为null,所以我只使用一个普通的ToList调用。那么你只需要测试空列表,而不是空列表。就个人而言,我不同意你在这种情况下的观点。对我来说,如果它不存在,它是空的。我只是想进入下一步,确保我们没有空的集合。要检查的东西少了。集合具有某些内容或为空。。notnull/为空/具有某些内容。但正如我所说,这只是一场宗教战争我会根据现有的FirstOrDefault、SingleOrDefault约定将其重命名为istorNull。@tvanfosson:我可能不会首先创建一个扩展来实现这一点。LINQ查询产生的IEnumerable永远不应该为null,所以我只使用一个普通的ToList调用。那么你只需要测试空列表,而不是空列表。就个人而言,我不同意你在这种情况下的观点。对我来说,如果它不存在,它是空的。我只是想进入下一步,确保我们没有空的集合。要检查的东西少了。集合具有某些内容或为空。。notnull/为空/具有某些内容。但正如我所说,这只是一场宗教战争我只是在编辑我的答案的时候,当你的答案出现时,就这样说,这样+1。也许OP需要传递空列表而不是空列表是有原因的,但在这种情况下,我想我更喜欢使转换显式和明显,而不是用一个不特别描述性的名称调用扩展方法。是的,我只是喜欢传递空集合而不是空集合。我想我更喜欢另一种方式。我拥有的任何返回集合的方法通常都返回空集合,从不为null。这样我就不必在使用它之前检查它是否为null——这确实是一个例外情况。我想.NET的人会同意我的观点,因为在没有匹配项的情况下,所有IEnumerable扩展都会返回空集合-这就是允许链接它们的原因。我认为,特别是,如果你在IEnumerable上构建扩展方法,你也应该遵循这个约定。我的期望是,任何返回IEnumerable的东西都将返回一个非空对象,它可能是空的。对于在现有IEnumerable上进行某些转换的扩展方法来说尤其如此。我想不出有哪一种方法可以返回某种集合,如果集合中没有任何内容,我选择返回null而不是空集合。我能想到的任何情况,比如无法连接到数据源,如果返回null可能比较合适,最好抛出一个异常。@Andras-实际上,由于我使用了ToList,我应该只检查属性而不调用IEnumerable扩展计数。这肯定比在枚举上调用任何函数更有效,不过对于列表来说,这应该是同样的事情。我已经更新了它来使用这个属性-我认为它只是一个打印错误之前,但它已经足够长,我真的不记得。我只是在中间编辑我的答案,确切地说,当你的答案出现,所以+1。也许OP需要传递空列表而不是空列表是有原因的,但在这种情况下,我想我更喜欢使转换显式和明显,而不是用一个不特别描述性的名称调用扩展方法。是的,我只是喜欢传递空集合而不是空集合。我想我更喜欢另一种方式。我拥有的任何返回集合的方法通常都返回空集合,从不为null。这样我就不必在使用它之前检查它是否为null——这确实是一个例外情况。我想.NET的人会同意我的观点,因为在没有匹配项的情况下,所有IEnumerable扩展都会返回空集合-这就是允许链接它们的原因。我认为,特别是,如果你在IEnumerable上构建扩展方法,你也应该遵循这个约定。我的期望是,任何返回IEnumerable的东西都将返回一个非空对象,它可能是空的。对于在现有IEnumerable上进行某些转换的扩展方法来说尤其如此。我想不出有哪一种方法可以返回某种集合,如果集合中没有任何内容,我选择返回null而不是空集合。我能想到的任何情况,比如无法连接到数据源,如果返回null可能比较合适,最好抛出一个异常。@Andras-实际上,由于我使用了ToList,我应该只检查属性而不调用IEnumerable扩展计数。这肯定比打电话给任何人都更有效率 但在枚举中,对于列表,它应该是相同的。我已经更新了它以使用该属性-我想这只是一个打字错误,但已经足够长了,我真的不记得了。