Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ method chaining and granular error handling

I have a method which can be written pretty neatly through method chaining:

return viewer.ServerReport.GetParameters()
    .Single(p => p.Name == Convention.Ssrs.RegionParamName)
    .ValidValues
    .Select(v => v.Value);

However I'd like to be able to do some checks at each point as I wish to provide helpful diagnostics information if any of the chained methods returns unexpected results.

To achieve this, I need to break up all my chaining and follow each call with an if block. It makes the code a lot less readable.

Ideally I'd like to be able to weave in some chained method calls which would allow me to handle unexpected outcomes at each point (e.g. throw a meaningful exception such as new ConventionException("The report contains no parameter") if the first method returns an empty collection). Can anyone suggest a simple way to achieve such a thing?

Edit:

This is the result of using @JeffreyZhao's answer:

return viewer.ServerReport.GetParameters()
    .Assert(result => result.Any(), "The report contains no parameter")
    .SingleOrDefault(p => p.Name == Convention.Ssrs.RegionParamName)
    .Assert(result => result != null, "The report does not contain a region parameter")
    .ValidValues
    .Select(v => v.Value)
    .Assert(result => result.Any(), "The region parameter in the report does not contain any valid value");
like image 402
Clafou Avatar asked Jun 07 '12 10:06

Clafou


3 Answers

Maybe you can use this approach.

static T Check<T>(this T value)
{
    if (...) throw ...;

    return value;
}

then:

xxx.Single(...).Check().Select(...).Check()...

Update:

You can even:

static T Validate<T>(this T value, Func<T, bool> validate, string errorMessage)
{
    if (!validate(value)) 
        throw new ValidationFailedException(errorMessage);

    return value;
}

then:

xxxx.Single()
    .Validate(v => v > 0, "Must be greater than zero")
    .NextStep()
    .Validate(...);
like image 63
Jeffrey Zhao Avatar answered Nov 10 '22 10:11

Jeffrey Zhao


You can easily split the process in separate steps with local variables:

var result1 = viewer.ServerReport.GetParameters();
var result2 = result1.Single(p => p.Name == Convention.Ssrs.RegionParamName);
var result3 = result2.ValidValues;
var result4 = result3.Select(v => v.Value);
return result4;

Now you can do any checks you want between the steps.

Note however that some results doesn't actually do any work. The last step for example doesn't produce a list as result, it produces an enumerable that reads from ValidValues, so any errors in that step would happen when you use the result, not inside this method. You might want to add .ToList() at the end of some steps to realise the result.

like image 38
Guffa Avatar answered Nov 10 '22 08:11

Guffa


Consider using of Code Contracts (e.g. add post-condition to ServerReport.GetParameters to ensure that methods doesn't return empty collection). They allow to do what you want in more elegant way, than writing your own checking logic.

class ReportParameter { }
class ServerReport
{
    public ReportParameter[] GetParameters()
    {
        Contract.Ensures(Contract.Result<ReportParameter[]>() != null && Contract.Result<ReportParameter[]>().Length > 0,
            Resource1.Oops);

        // here's some logic to build parameters array...
        return new ReportParameter[0];
    }
}

Usage:

// Oops! I need at least one parameter!
var parameters = new ServerReport().GetParameters();
like image 36
Dennis Avatar answered Nov 10 '22 09:11

Dennis