Linq 查询中的异常处理

本文关键字:异常处理 查询 Linq | 更新日期: 2023-09-27 18:35:57

我正在使用...

tmpLst = (from e in App.lstAllChilds where e.Id == Id select e).ToList();

其中lstAllChilds是列表,其中还包含一些损坏的数据。

所以现在我要在这个查询中处理 Try-Catch 块。
请帮忙。

Linq 查询中的异常处理

如果您只想忽略"坏元素",那么:

App.lstAllChilds.SkipExceptions().Where( e => e.Id == Id).ToList();

扩展方法:

 public static class Extensions
    {
        public static IEnumerable<T> SkipExceptions<T>(this IEnumerable<T> values)
        {
            using (var enumerator = values.GetEnumerator())
            {
                bool next = true;
                while (next)
                {
                    try
                    {
                        next = enumerator.MoveNext();
                    }
                    catch
                    {
                        continue;
                    }
                    if (next) yield return enumerator.Current;
                }
            }
        }
    }

这是您可以进行的最简单的更改,以简单地排除为 null 或引发异常的项目。

tmpLst = App.lstAllChilds.Where(e =>
{
    try
    {
        return e != null && e.Id == Id;
    }
    catch
    {
        return false;
    }
}).ToList();

但在我看来,您可能应该调查并解决根本问题。 这似乎不像是应该出现异常的情况。

所以,事情是这样的。有语言集成查询(Linq),然后是生成的枚举(又名迭代器)。

Linq 允许您定义一个表达式树,该表达式树稍后由可能是也可能不是 C# 的内容执行(例如,表达式可以转换为 SQL 查询)。如果您正在编写 linq,那么您的查询提供程序(执行表达式翻译的东西)很可能不支持异常处理(更不用说您正在执行的引发异常的任何操作了)。

另一方面,交互器(或"linq to objects")最终会在 C# 中执行,因此您可以疯狂地处理异常。

例如,使用 linq 到对象,您可以执行以下操作:

var myList = new[] { "1", "2", "BARF", "3" };
var sum = myList.Select(str => {
  try {
    return Int32.Parse(str);
  } catch {
    return 0;
  }
}).Aggregate((x, y) => x + y);

如果你确实在对对象执行linq,并且你只想跳过源IEnumerable抛出异常的元素,请查看Vladimir Gondarev的答案。

然而,要了解的重要一点是,我们刚刚传递给该 Select 调用的匿名函数不是表达式(未编译的表达式树),它是一个 Func(指向已编译的 c# 代码的委托),这意味着它将在 .Net 进程中运行,即使我们将 myList 替换为 linq to entities 表(或其他一些 linq 提供程序)。原因是 C# 表达式语法不支持块,也不支持 try-catch。不出所料,鉴于此,SQL 样式的 Linq 语句(来自 xxx select yyy)也不支持 try-catch 块。

但是,仅仅因为 C# 表达式语法不支持它并不意味着你不能这样做。但是,需要明确的是,我不建议这样做,因为我非常怀疑是否存在支持它的 QueryProvider(除了 linq to 对象提供程序)。对于好奇的人,下面介绍了如何创建包含 try-catch 块的 lambda 表达式。

var parseMethod = typeof(Int32).GetMethod("Parse", new[] { typeof(String) });
var param = Expression.Parameter(typeof(String));
var selectExp =
    Expression.Lambda<Func<String, Int32>>(
        Expression.TryCatch(
            Expression.Call(parseMethod, param),
            Expression.Catch(typeof(Exception), Expression.Constant(0))
        ),
        param
    );
var sum = myList.Select(selectExp).Aggregate((x, y) => x + y);

因此,当有人实现由支持异常处理的存储支持的 QueryProvider 时,您可以使用它。

这取决于您究竟要实现什么:

  1. 返回除"损坏"项之外的列表。

    • 您可以尝试使用 Where 子句来检查和过滤它们:

      App.lstAllChilds.Where(x => IsValidNode(x, Id)).ToList();
      

      显然,您需要实现应该检查null IsValidNode,如果它可以抛出InvalidObjectException(不确定您是否可以轻松检测到它,但您始终可以将其包装在try-catch块中)并Id相等性。诸如此类:

      private bool IsValidNode(Child c, int id)
      {
          if (x == null) return false;
          try {
              return c.Id == id;
          }
          catch (InvalidObjectException)
          {
          }
          return false;
      }
      
  2. 如果有任何损坏的项目,则返回空列表。

    • 只需将整个东西包裹在一个 try-catch 块中即可