7

As Eric Lippert described in this article, yield return is not allowed within try/catch clauses.

Is there a nice way I could get something like this, without having to write my own IEnumerator by hand:

public IEnumerable<Data> GetData()
{
    var transaction = Session.BeginTransaction());
    try 
    {
        IQuery q = CreateQuery(session);

        foreach (var result in q.Enumerable())
            yield return ProjectResult(result);  // <-- doesn't work

        session.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
    finally
    {
        transaction.Dispose();
    }
}
vgru
  • 49,838
  • 16
  • 120
  • 201

2 Answers2

12

I'd just change the transaction-handling logic like this:

public IEnumerable<Data> GetData()
{
    var transaction = Session.BeginTransaction();
    bool rollback = true;
    try 
    {
        IQuery q = CreateQuery(session);

        foreach (var result in q.Enumerable())
        {
            yield return ProjectResult(result);
        }

        rollback = false;
        session.Commit();
    }
    finally
    {
        if (rollback)
        {
            transaction.Rollback();
        }
        transaction.Dispose();
    }
}

Or if your transaction supports the idea of "dispose means rollback unless it's commited":

public IEnumerable<Data> GetData()
{
    using (var transaction = Session.BeginTransaction();
    {
        IQuery q = CreateQuery(session);

        foreach (var result in q.Enumerable())
        {
            yield return ProjectResult(result);
        }

        // Commits the tnrasaction, so disposing it won't roll it back.
        session.Commit();
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Didn't the OP say that `yield return` is not allowed in a try/catch block? – StriplingWarrior Aug 30 '11 at 14:56
  • 7
    @Stripling, that's not a *try/catch*. – Anthony Pegram Aug 30 '11 at 14:56
  • 2
    @StriplingWarrior: Just to expand on Anthony's comment: yield return is allowed in try/finally, just not in try/catch. – Jon Skeet Aug 30 '11 at 14:57
  • 1
    Ah, the article referenced says "3) Allow yield returns in try blocks that have finally blocks, but not if they have catch blocks." I see. – StriplingWarrior Aug 30 '11 at 14:57
  • Thanks, first example is a clever workaround. Second example is even better, because NHibernate really does perform [implicit rollbacks when disposed without prior commit](http://nhforge.org/blogs/nhibernate/archive/2009/09/08/part-9-nhibernate-transactions.aspx). – vgru Aug 30 '11 at 15:06
  • @Groo: Cool - that's the sensible behaviour for it to implement, to be honest :) – Jon Skeet Aug 30 '11 at 15:07
  • @StriplingWarrior: To clarify Jon's comment further, a Try/Finally or Using will only work if the code which uses the enumerator correctly calls Dispose on it. If the code which creates the enumerator abandons it without calling Dispose, the code in the "Finally" block (or the Finally block implied by "using") will not execute. – supercat Aug 30 '11 at 15:57
-2

Re-factor

foreach (var result in q.Enumerable()) 
  yield return ProjectResult(result);

into a separate method and simply return it's result.

Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • 2
    Could you add an example with a try/catch block shown? My guess is that your suggestion will not work correctly because of lazy evaluation; `finally` clause will get executed before results are iterated if there is no `yield return`. – vgru Aug 30 '11 at 15:01