SingleOrDefault异常处理

时间:2014-05-15 09:51:19

标签: c# .net linq

我有一个samle代码,它调用SingleOrDefault方法3次,如果任何序列有多个匹配元素,则记录异常。

如果我想检查此代码的哪一部分引发异常,则问题开始。

是否可以从此异常中获取一些有用的信息,如谓词参数或集合类型,以获得更详细的跟踪?

像这样 - Sequence包含多个匹配元素。集合IEnumrable | ParamType | param {Predicate param toString()}

 public void GetSingleOrDefaultTest(){

    try{

        var user = Users.SingleOrDefault(e => e.Id == 1);

        var profile = UserProfiles.SingleOrDefault(e => e.Id == 1);

        var profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);


    } catch(InvalidOperationException ex){
        Log(ex);
    }

}

5 个答案:

答案 0 :(得分:4)

如果您想知道哪个语句发出错误,您必须单独检查它们。在每个InvalidOperationException调用上捕获SingleOrDefault并将其包装在一个新的异常中,您可以使用其他信息填充该异常。

try
{
    User user;
    UserProfile profile;
    UserProfile profile2;

    try
    {
        user = Users.SingleOrDefault(e => e.Id == 1);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User lookup for Id = 1 failed", ex);
    }

    try
    {
        profile = UserProfiles.SingleOrDefault(e => e.Id == 1);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User profile lookup for Id = 1 failed", ex);
    }

    try
    {
        profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User profile lookup for Id = 2 failed", ex);
    }

    // work with user, profile and profile2
}
catch(InvalidOperationException ex)
{
    Log(ex);
}

修改

您还可以通过以下

封装单个试用捕获
private static T GetSingleOrDefault<T>(IEnumerable<T> collection, Expression<Func<T, bool>> predicate)
{
    try
    {
        return collection.SingleOrDefault(predicate.Compile());
    }
    catch (InvalidOperationException e)
    {
        var message = string.Format(
            "{0} (Collection: {1}, param: {2})",
            e.Message,
            collection.GetType(),
            predicate);

        throw new InvalidOperationException(message);
    }
}

以便您的代码看起来像

try
{
    var user = GetSingleOrDefault(Users, e => e.Id == 1);

    var profile = GetSingleOrDefault(UserProfiles, e => e.Id == 1);

    var profile2 = GetSingleOrDefault(UserProfiles, e => e.Id == 2);

    // work with user, profile and profile2
}
catch(InvalidOperationException ex)
{
    Log(ex);
}

这会产生类似

的消息
  

System.InvalidOperationException:Sequence包含多个匹配元素(Collection:IEnumerable`1 [User],param:e =&gt; e.Id == 1)

答案 1 :(得分:1)

每当您使用SingleOrDefault时,您都清楚地声明查询最多只能产生一个结果。另一方面,当使用FirstOrDefault时,查询可以返回任意数量的结果,但是您声明只需要第一个结果。

我个人觉得语义非常不同,使用适当的语义,取决于预期的结果,提高了可读性。

Reference

答案 2 :(得分:1)

没有使用SingelOrDefault的方法。我会把它重构为:

var user = Users.Count(e => e.Id == 1);
var profile = UserProfiles.Count(e => e.Id == 1);
var profile2 = UserProfiles.Count(e => e.Id == 2);

if(user + profile + profile2 != 3){
  Log("more than one");
}

这基本相同但不是异常驱动的。我在你的问题中没有看到使用异常驱动的编程。

答案 3 :(得分:0)

记录进入场景的位置(NLog使用示例,可​​从NuGet获得):

public void GetSingleOrDefaultTest()
{
    try
    {
        Logger.Debug("Getting user with id = {0}", 1);
        var user = Users.SingleOrDefault(e => e.Id == 1);

        Logger.Debug("Getting user profile with id = {0}", 1);
        var profile = UserProfiles.SingleOrDefault(e => e.Id == 1);

        Logger.Debug("Getting user profile with id = {0}", 2);
        var profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);
    } 
    catch(Exception ex)
    {
        Logger.ErrorException("Failed getting single or default", ex);
    }
}

只需浏览一下日志文件即可告诉您哪个语句失败了:

2014-05-26 12:04:48.8655 DEBUG Getting user with id = 1
2014-05-26 12:04:48.8815 DEBUG Getting user profile with id = 1
2014-05-26 12:04:48.8815 DEBUG Getting user profile with id = 2
2014-05-26 12:04:48.8815 ERROR Failed getting single or default
Sequence contains more than one matching element

答案 4 :(得分:-2)

在回答之前:请注意这里使用异常处理的危险。在这个问题的评论中,我指出了一些问题。最后,您可能会通过记录误导性信息来恶化您的日志输出。

不要依赖异常处理控制流程。除了其他问题之外,你会意外地捕获真正的错误并隐藏它们。

例外也有非常糟糕的表现。

使用异常处理来诊断特定问题非常容易出错。让我们看看我们是否可以在不使用它的情况下离开。

public static TSource SingleOrDefaultWithDiagnostics<TSource>(
  this IEnumerable<TSource> source,
  Func<TSource, bool> predicate,
  string failureMessage) {

    using (IEnumerator<TSource> enumerator = source.Where(predicate).GetEnumerator())
    {
        if (!enumerator.MoveNext())
        {
            return default(TSource);
        }
        TSource current = enumerator.Current;
        if (!enumerator.MoveNext())
        {
            return current;
        }
        else throw new SingleElementException(failureMessage);
    }
}

我已经采用了一些BCL代码并改变了引发错误的方式。

在您正在寻找的情况下,这会输出一个有意义的异常。您可以附加自定义诊断消息。

 var user = Users.SingleOrDefaultWithDiagnostics(e => e.Id == 1, "User with Id 1");
 var profile = UserProfiles.SingleOrDefaultWithDiagnostics(e => e.Id == 1, "...");
 var profile2 = UserProfiles.SingleOrDefaultWithDiagnostics(e => e.Id == 2, "...");