2

I have a Unit of Work implementation in .NET 4.7 that is checking to see if an exception has happened during the dispose. If there has, it calls rollback on a transaction. It is not catching the exception, or trying to handle it, it's simply trying to make sure the rollback happens if an exception occurs.

It's using;

Marshal.GetExceptionPointers() != IntPtr.Zero && Marshal.GetExceptionCode() != 0

I need to port this code to .NET Core 2.1. But GetExceptionPointers() no longer exists, and GetExceptionCode is marked as Obsolete, with no indication as to what replaces it. I'm not quite sure how to term what I'm looking for, but I need the equivalent behavior, so I can port over this code. Any thoughts?

To expand on this. This is a Unit Of Work implementation around an EF DbContext. It is currently working well, and if exceptions happen does successfully rollback any changes to the database. It can't have a try/catch inside of it, because this is used as such;

using(var uow = ctx.Begin())
{
.. code 
}

I am not trying to handle exceptions inside this, I am trying to rollback the transaction while the exception is happening. This does appear to be a poor practice, I'm interested in learning an alternative, but I have to have the same behavior.

tbddeveloper
  • 2,407
  • 1
  • 23
  • 39
  • 3
    Would it be an option to refactor it into something decent? Because that is a terrible hack. – H H Apr 20 '18 at 17:00
  • That was the way it was developed, but as long as I can achieve the overall behavior, I have no argument against learning a better way. – tbddeveloper Apr 20 '18 at 17:01
  • 2
    well the better way is `try/catch` – Vidmantas Blazevicius Apr 20 '18 at 17:08
  • There seems to be some good information in answers and comments to this question: https://stackoverflow.com/q/577607/562459 . Not sure how helpful it might be, though. – Mike Sherrill 'Cat Recall' Apr 20 '18 at 17:09
  • 3
    Why not simply catch the exception when calling _Dispose_, handle the rollback, and rethrow it? I don't understand the purpose of this code. – IS4 Apr 20 '18 at 17:19
  • It is pretty questionable code, pretty low odds that the rollback could ever succeed when something this nasty happened. But the core issue is that this just can't port to the unixes, they don't have SEH. – Hans Passant Apr 20 '18 at 17:24
  • Agree with questionable. Understand the portability. But how do I implement a UoW around a DbContext effectively? Good examples? It's checking if an exception is thrown, I don't have the option of putting it in a try catch – tbddeveloper Apr 20 '18 at 17:28
  • 1
    It can only ever work in a catch block, you don't really have to guess whether an exception was thrown. Did you ever actually test this? – Hans Passant Apr 20 '18 at 18:04
  • yes, the current implementation is working. – tbddeveloper Apr 20 '18 at 18:18
  • The alternative is to have a try/catch around, at least, the SaveChanges. Maybe you're stuck with a bad design but then you should be aware of it. – H H Apr 20 '18 at 19:29
  • The `using` block is just syntactic sugar. Unroll it, call `Dispose` yourself, and use `try/catch`. – McGuireV10 Apr 20 '18 at 19:38

1 Answers1

1

The answer here was to change the entire way the code was designed. I can't go into to much depth as this is not a personal project, but here's the reason I was looking and the solution we came up with.

The system is designed to make reusable components that can be called from other components. Thus;

A can invoke B which can invoke C
D can invoke C
C can be invoked on its own

The idea was to break the system down into smaller parts. It's small enough that it wouldn't warrant being put in a more distributed system like NServiceBus or MT.

When we're doing this though, the entire thing should be wrapped in a transaction (or unit of work) so that independent pieces of code can be treated independently, but should a piece fail they all roll back. Because of this design, we can't treat a DbContext like most examples would and it be the Unit Of Work.

So, as was suggested in the comments, our only solution was to put a try..catch somewhere. We have a class that invokes these pieces, so we changed it to begin an ambient transaction and to understand if a piece is nested. If an exception occurs it will dispose the transaction without completing and bubble up so, as in the example above, an error in B would cause anything done in C and A to rollback.

I realized the moment the original code was written that it was a hack, but it did give us an elegant solution on a day to day basis. This just forced us to revisit the design.

tbddeveloper
  • 2,407
  • 1
  • 23
  • 39