0

I have a C# program that will run nightly. It reads in records from flat files and does many different inserts and updates to a SQL Server database depending on the contents of each record.

Despite using transaction blocks and Try...Catch in stored procedures, and Try...Catch in C#, I really need these updates to be all or nothing.

I cannot take snapshots of db since we are using SQL Server 2008 R2 Express edition.

Can anyone please explain a good method (if there is one) for returning the SQL Server database to the state it was in before the nightly C# job ran, if the C# program catches an error? Basically, if the C# program catches an error, I want it to stop running, and I want to "rollback" the database to before the C# program did any changes. I need this to be an automated solution, not a manual restore from a backup.

In a sense, I want a Transaction block around the C# program that could rollback everything the C# program did.

The database is in Multi_User mode with a full recovery model (with nightly scheduled db and log backups). There is a web app tied to this database, but end users should really never have need to access this web app in the middle of the night.

I have searched everywhere for solutions, but no luck. Perhaps not using right keywords.

I look forward to feedback.

Thanks in advance, David

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

2 Answers2

0

I've gotten around this in the past by reading the file, inserting into a staging table, and then calling a single stored procedure to to the actual inserts into the production tables from the staging table.

If there is an error reading the file, truncate the staging table (or use an import identifier of some sort). If there is an error in the data, it should be easy to know that on the stored procedure side and easy to use one or two set based operations wrapped in a transaction.

This has the added benefit of being able to look at the data easily in the staging table if there was a data issue, possibly manually fixing it, and running the import stored procedure again.

UnhandledExcepSean
  • 12,504
  • 2
  • 35
  • 51
0

I'm sorry, but that's what I thought database transactions were for. First you connect to the database, next you start a database transaction, and then you use the same connection & transaction for every operation:

using ( SqlConnection conn = new SqlConnection( . . . ) ) {
    using ( SqlTransaction tran = new conn.BeginTransaction() ) {
        try {
            // Data processing operation #1
            using ( SqlCommand command = new SqlCommand( ". . .", conn, tran ) {
                // Your processing code here
            }

            // Repeat the pattern used for Data Processing Operation #1 for all other operations

            // Commit the transaction if everything completed without error.
            tran.Commit();
        } catch ( SqlException ) {
            // An error occurred.  Roll the transaction back.
            tran.Rollback();

            // Other error handling code if needed.
        }
    }
}

Since there's only one transaction, everything is rolled back to the initial state if anything goes wrong. If you need to cancel the operation for other reasons, you call tran.Rollback() & return. This is exactly why transactions were invented; I don't understand why they won't do the job for you.

Tony Vitabile
  • 8,298
  • 15
  • 67
  • 123
  • Nice solution. Wonder if program needs to be re-written. People providing input cram every possible condition into 1 record layout. Never know what fields might be populated. 1 record might result in call of 1 method, while others in any mixture of methods. All methods are currently in various objects that have their own SQL connections, commands, parameters, and sp's. The sp's use transaction blocks, but that doesn't help with all or nothing scenario. Wondering if some way to wrap trans block (as in your example) around entire foreach loop that reads input file. Probably too hopeful? – user3332608 Feb 20 '14 at 15:36
  • If you have multiple methods in multiple classes, each establishing their own connection and transaction, I suggest you modify the methods so they take the connection and transaction as parameters and separate the connection / transaction handling logic from the method implementation. Then you can pass those objects as needed. – Tony Vitabile Feb 20 '14 at 15:43