【问题标题】:Linq Extension Methods and Error HandlingLinq 扩展方法和错误处理
【发布时间】:2017-11-09 17:52:23
【问题描述】:

我有以下 C# 代码...

    // We're essentially pivoting the data, using LINQ's GroupBy.
    var pivotedOperands = Operands.GroupBy(o => new { outputid = (Guid)o[DETAILS_OUTPUTID], unitid = o[DETAILS_UNITID] })
        .Select(g => new
        {
            PivotKey = g.Key,
            c1 = g.Where(x => (int)x[DETAILS_OV_SEQUENCEID] == 1).Sum(x => double.Parse(x[DETAILS_VALUE].ToString())),
            r1 = g.Where(x => (int)x[DETAILS_OV_SEQUENCEID] == 2).Sum(x => double.Parse(x[DETAILS_VALUE].ToString())),
            a1 = g.Where(x => (int)x[DETAILS_OV_SEQUENCEID] == 3).Sum(x => double.Parse(x[DETAILS_VALUE].ToString()))
        });

它获取 Operands 中的数据(它是一个 List 对象)并使用 GroupBy() 扩展方法对数据执行透视。本质上,c1、r1 和 a1 都是不同 DataRow 对象中的值,序列 ID 分别为 1、2 和 3。 (如果有必要,我可以对此进行更多拘留,但我认为不会。)

所以有时 c1 的值可能为空。 (这是不应该的,但在流程的上游不时会发生错误。)如果 c1 不是数值,则 double.Parse() 调用将引发异常。没关系。这是我的问题。例如,如果 Operands 对象包含 9 行,这些行将被转换为 3 行,而这 9 个值中的一个不是数字,是否可以确定哪个 DataRow 对象引发了异常?

示例: 如果 Operands 包含 SequenceID 和 Value 的以下值...

OutputID UnitID SequenceID Value
A        1      1          '0'
A        1      2          '0'
A        1      3          '0'
A        2      1          ''
A        2      2          '0'
A        2      3          '0'
B        1      1          '0'
B        1      2          '0'
B        1      3          '0'

...然后,当它尝试通过我的数据集第 4 行的 double.Parse() 方法处理空字符串时,我们将得到一个“输入字符串格式不正确”的异常。我想向用户提出一个友好的例外,告诉他们哪一行是问题;不仅仅是这组数据的某个地方有问题。是否可以准确识别导致异常的原因?

如果您在 Visual Studio 中创建一个新的 C# 控制台应用程序并将以下代码转储到 Main 方法中,您将能够重现我的问题。

        // Create a DataTable so that we can easily create new DataRows to add to our List.
        DataTable dt = new DataTable();
        DataColumn col = new DataColumn();
        col.DataType = System.Type.GetType("System.String");
        col.ColumnName = "OutputID";
        dt.Columns.Add(col);

        col = new DataColumn();
        col.DataType = System.Type.GetType("System.Int32");
        col.ColumnName = "UnitID";
        dt.Columns.Add(col);

        col = new DataColumn();
        col.DataType = System.Type.GetType("System.Int32");
        col.ColumnName = "SequenceID";
        dt.Columns.Add(col);

        col = new DataColumn();
        col.DataType = System.Type.GetType("System.String");
        col.ColumnName = "Value";
        dt.Columns.Add(col);



        // Create the List and add our sample data
        List<DataRow> Operands = new List<DataRow>();

        DataRow dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 1;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 2;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 3;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "2";
        dr["SequenceID"] = 1;
        dr["Value"] = "";       // This should cause an error.
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "2";
        dr["SequenceID"] = 2;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "A";
        dr["UnitID"] = "2";
        dr["SequenceID"] = 3;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "B";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 1;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "B";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 2;
        dr["Value"] = "0";
        Operands.Add(dr);

        dr = dt.NewRow();
        dr["OutputID"] = "B";
        dr["UnitID"] = "1";
        dr["SequenceID"] = 3;
        dr["Value"] = "0";
        Operands.Add(dr);

        // Now pivot the data
        try
        {
            var pivotedOperands = Operands.GroupBy(o => new { outputid = o[0], unitid = o[1] })
                .Select(g => new
                {
                    PivotKey = g.Key,
                    c1 = g.Where(x => (int)x[2] == 1).Sum(x => double.Parse(x[3].ToString())),
                    r1 = g.Where(x => (int)x[2] == 2).Sum(x => double.Parse(x[3].ToString())),
                    a1 = g.Where(x => (int)x[2] == 3).Sum(x => double.Parse(x[3].ToString()))
                });

            foreach (var o in pivotedOperands)
            {
                Console.WriteLine(string.Format("c1 = {0}; r1 = {1}; a1 = {2}", o.c1, o.r1, o.a1));
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        Console.WriteLine("Done.");
        Console.ReadLine();

【问题讨论】:

  • 使用带索引的 select 以及使用 TryParse 来绕过异常就在我的脑海中。如果 tryparse false 则记录索引。或者如果tryparse false 默认为零(0)

标签: c# linq error-handling extension-methods


【解决方案1】:

根据您希望信息显示的方式,您可以更改结果类型以考虑失败的可能性,或者您可以捕获有关异常的上下文信息并抛出包含更多信息的新异常。

无论哪种情况,都不要害怕使用辅助方法。例如,假设您通过创建这样的方法摆脱了选择器中的重复代码:

string GetSumOrErrorMessage(int idToMatch, IEnumerable<DataRow> dataRow)
{
    try
    {
        var sum = dataRow.Where(x => (int)x[2] == idToMatch).Sum(x => double.Parse(x[3].ToString()));
        return sum.ToString();
    }
    catch (Exception)
    {
        return "Error happened here"; // or something more specific
    }
}

现在您可以像这样更改您的查询:

    var pivotedOperands = Operands.GroupBy(o => new { outputid = o[0], unitid = o[1] })
        .Select(g => new
        {
            PivotKey = g.Key,
            c1 = GetSumOrErrorMessage(1, g),
            r1 = GetSumOrErrorMessage(2, g),
            a1 = GetSumOrErrorMessage(3, g)
        });

你的输出变成:

c1 = 0; r1 = 0; a1 = 0
c1 = Error happened here; r1 = 0; a1 = 0
c1 = 0; r1 = 0; a1 = 0

如果您喜欢这种模式,而不是仅仅返回 string,您可能希望研究可以帮助解决此问题的专用 Monadic 类型。例如,您可以创建一个在操作成功时具有通用值的类,或者在失败时具有错误消息的类。您可以创建各种扩展方法和帮助程序以使其更易于处理,类似于my CallMeMaybe library 允许您尝试解析一个值,但如果解析失败则只返回一个空的Maybe&lt;&gt;。 (例如Maybe.From(x[3].ToString()).ParseInt64().Select(i =&gt; i.ToString()).Else("Error happened here"))。

或者,如果您确实想在输入错误时停止,但仍想知道错误输入在哪里,您可以捕获并抛出:

double GetSum(int idToMatch, IGrouping<object, DataRow> dataRows)
{
    try
    {
        return dataRows.Where(x => (int)x[2] == idToMatch).Sum(x => double.Parse(x[3].ToString()));
    }
    catch (Exception e)
    {
        throw new Exception($"Failure when matching {idToMatch} with group {dataRows.Key}", e);
    }
}

...

    var pivotedOperands = Operands.GroupBy(o => new { outputid = o[0], unitid = o[1] })
        .Select(g => new
        {
            PivotKey = g.Key,
            c1 = GetSum(1, g),
            r1 = GetSum(2, g),
            a1 = GetSum(3, g)
        });

输出:

c1 = 0; r1 = 0; a1 = 0
Failure when matching 1 with group { outputid = A, unitid = 2 }

【讨论】:

  • 美丽。我从来没有想过要这样做。使用辅助方法,我可以充分控制执行以处理更多不寻常的情况。谢谢。
【解决方案2】:

您尝试使用TryParse 来绕过异常。如果 TryParse 为 false,则默认为零 (0)

.Sum(x => { 
    double value = 0; 
    return double.TryParse(x[DETAILS_VALUE].ToString(), out value) ? value : 0; 
}) 

【讨论】:

  • 感谢您的帮助。我选择了 StriplingWarrior 的回答,因为它让我可以灵活地在解析失败时使用默认值,或者引发异常并中断执行。 (这正是我需要的。)
  • @DeadZone 一切都好。如果您想实际捕获异常,那是更好的选择。很高兴提供帮助。
猜你喜欢
  • 1970-01-01
  • 2011-03-11
  • 1970-01-01
  • 2017-08-02
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多