SingleOrDefault() throws an exception on more than one element

☆樱花仙子☆ 提交于 2021-02-06 08:47:18

问题


I'm getting an exception whenever I fetch like this

Feature f = o.Features.SingleOrDefault(e => e.LinkName == PageLink);

because this can return one or more than one element. What is the alternative approach that I can use to solve this issue?


回答1:


Single and SingleOrDefault are designed to throw if more that one match exists in the sequence. A consequence of this is that the entire sequence must be iterated prior to completion. It does not sound like this is what you want. Try FirstOrDefault instead:

Feature f = o.Features
    .FirstOrDefault(e => e.vcr_LinkName == PageLink && e.bit_Activate == true);

This will (generally) perform better because it completes as soon as a match is found.

Of course, if you actually want to retain more than one element, a Where clause would be more appropriate:

IEnumerable<Feature> fs = o.Features
    .Where(e => e.vcr_LinkName == PageLink && e.bit_Activate == true);



回答2:


Alternatively, if you only want the item when there is exactly one match and do not want to throw when there are more than one, this can be easily accomplished. I've created an extension method for this in my project:

public static class QueryableExtensions
{
    public static TSource SingleWhenOnly<TSource>(this IQueryable<TSource> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        var results = source.Take(2).ToArray();

        return results.Length == 1 ? results[0] : default(TSource);
    }

    public static TSource SingleWhenOnly<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        if (predicate == null)
            throw new ArgumentNullException("predicate");

        var results = source.Where(predicate).Take(2).ToArray();

        return results.Length == 1 ? results[0] : default(TSource);
    }
}



回答3:


If you only want the first element, use FirstOrDefault instead.

Basically, here are the options in terms of valid results (i.e. where you don't want to throw) and what to use:

  • Exactly one: Single
  • One or zero: SingleOrDefault
  • One or more: First
  • Zero or more: FirstOrDefault

(ElementAt and ElementAtOrDefault, Last and LastOrDefault are also available.)




回答4:


I've found I need the behavior of returning a default value if there is not exactly one element (i.e. zero, two, or more) more often than I need the normal SingleOrDefault behavior, so here's my adapted version of Pieter van Ginkel's answer:

public static class LinqExtensions
{
    /// <summary>
    /// Returns the only element of a sequence, or a default value if the sequence is empty or contains more than one element.
    /// </summary>
    public static TSource SingleOrDefaultIfMultiple<TSource>(this IEnumerable<TSource> source)
    {
        var elements = source.Take(2).ToArray();

        return (elements.Length == 1) ? elements[0] : default(TSource);
    }

    /// <summary>
    /// Returns the only element of a sequence, or a default value if the sequence is empty or contains more than one element.
    /// </summary>
    public static TSource SingleOrDefaultIfMultiple<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        return source.Where(predicate).SingleOrDefaultIfMultiple();
    }

    /// <summary>
    /// Returns the only element of a sequence, or a default value if the sequence is empty or contains more than one element.
    /// </summary>
    public static TSource SingleOrDefaultIfMultiple<TSource>(this IQueryable<TSource> source)
    {
        var elements = source.Take(2).ToArray();

        return (elements.Length == 1) ? elements[0] : default(TSource);
    }

    /// <summary>
    /// Returns the only element of a sequence, or a default value if the sequence is empty or contains more than one element.
    /// </summary>
    public static TSource SingleOrDefaultIfMultiple<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Where(predicate).SingleOrDefaultIfMultiple();
    }
}

I've omitted the null argument checks because I'm OK with relying on the Take and Where calls to throw exceptions when the arguments are null, but you might feel otherwise.




回答5:


SingleOrDefault suggests that you are expecting 0 or 1 results from your query. If you have more than 1 then there is something wrong with your data or query.

If you are expecting more than 1 result and only want the first one, then FirstOrDefault should be used.




回答6:


Single means that you expect be one element in the sequence. SingleOrDefault means that you expect there to be one or zero elements in the sequence. This should be used when you want know there is one (or zero) and you want it to crash when more than one it returned.

If you are after just one, use First (or FirstOrDefault) as suggested above, but make sure you order the data correctly.




回答7:


If you are using SingleOrDefault if the condition satisfy more than result it will throw error.

you can achieve your result by using FirstOrDefault



来源:https://stackoverflow.com/questions/3185067/singleordefault-throws-an-exception-on-more-than-one-element

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!