Make sure that call api success and save myself su

2019-09-20 07:42发布

问题:

Hello I found problem when I use ASP.NET MVC with EF and call Web API from other website(that have also use Entity Framework) the problem is that

I want to make sure that both MVC SaveChanges() and Web API SaveChanges() succeed both together.

Here's my dream pseudo code

public ActionResult Operation()
{
    Code Insert Update Delete....

    bool testMvcSaveSuccess = db.TempSaveChanges();  //it does not have this command.

    if(testMvcSaveSuccess == true)
    {
       bool isApiSuccess = CallApi(); //insert data to Other Web App


       if(isApiSuccess == true)
       {
          db.SaveChanges(); //Real Save
       }
    }
}

From above code, if it doesn't have db.TempSaveChanges(), maybe Web API will be successful, but MVC SaveChanges() might fail.

回答1:

So there is nothing like TempSaveChanges because there is something even better: Transactions.

Transaction is an IDisposable (can be used in a using block) and has methods like Commit and Rollback.

Small example:

private void TestTransaction()
{
    var context = new MyContext(connectionString);

    using (var transaction = context.Database.BeginTransaction())
    {
        // do CRUD stuff here 

        // here is your 'TempSaveChanges' execution
        int changesCount = context.SaveChanges();

        if (changesCount > 0)
        // changes were made
        {
            // this will do the real db changes
            transaction.Commit();
        }
        else
        {
            // no changes detected -> so do nothing
            // could use 'transaction.Rollback();' since there are no changes, this should not be necessary
            // using block will dispose transaction and with it all changes as well
        }
    }
}

I have extracted this example from my GitHub Exercise.EntityFramework repository. Feel free to Star/Clone/Fork...



回答2:

Yes you can.

you need to overload the .Savechanges in the context class where it will be called first checked and then call the regular after.

Or create you own TempSaveChanges() in the context class call it then if successful call SaveChanges from it.



回答3:

What you are referring to is known as atomicity: you want several operations to either all succeed, or none of them. In the context of a database you obtain this via transactions (if the database supports it). In your case however, you need a transaction which spans across two disjoint systems. A general-purpose (some special cases have simpler solutions) robust implementation of such a transaction would have certain requirements on the two systems, and also require additional persistence.

Basically, you need to be able to gracefully recover from a sudden stop at any point during the sequence. Each of the databases you are using are most likely ACID compliant, so you can count on each DB transaction to fulfill the atomicity requirement (they either succeed or fail). Therefore, all you need to worry about is the sequence of the two DB transactions. Your requirement on the two systems is a way to determine a posteriori whether or not some operation was performed.

Example process flow:

  1. Operation begins
  2. Generate unique transaction ID and persist (with request data)
  3. Make changes to local DB and commit
  4. Call external Web API
  5. Flag transaction as completed (or delete it)
  6. Operation ends

Recovery:

  1. Get all pending (not completed) transactions from store
  2. Check if expected change to local DB was made
  3. Ask Web API if expected change was made
  4. If none of the changes were made or both of the changes were made then the transaction is done: delete/flag it.
  5. If one of the changes was made but not the other, then either revert the change that was made (revert transaction), or perform the change that was not (resume transaction) => then delete/flag it.

Now, as you can see it quickly gets complicated, specially if "determining if changes were made" is a non-trivial operation. What is a common solution to this is to use that unique transaction ID as a means of determining which data needs attention. But at this point it gets very application-specific and depends entirely on what the specific operations are. For certain applications, you can just re-run the entire operation (since you have the entire request data stored in the transaction) in the recovery step. Some special cases do not need to persist the transaction since there are other ways of achieving the same things etc.



回答4:

ok so let's clarify things a bit.

you have an MVC app A1, with its own database D1 you then have an API, let's call it A2 with its own database D2.

you want some code in A1 which does a temp save in D1, then fires a call to A2 and if the response is successful then it saves the temp data from D1 in the right place this time.

based on your pseudo code, I would suggest you create a second table where you save your "temporary" data in D1. So your database has an extra table and the flow is like this:

first you save your A1 data in that table, you then call A2, data gets saved in D2, A1 receives the confirmation and calls a method which moves the data from the second table to where it should be.

Scenarios to consider:

  1. Saving the temp data in D1 works, but the call to A2 fails. you now clear the orphan data with a batch job or simply call something that deletes it when the call to A2 fails.

  2. The call to A2 succeeds and the call to D1 fails, so now you have temp data in D1 which has failed to move to the right table. You could add a flag to the second table against each row, which indicates that the second call to A2 succeeded so this data needs to move in the right place, when possible. You can have a service here which runs periodically and if it finds any data with the flag set to true then it moves the data to the right place.

There are other ways to deal with scenarios like this. You could use a queue system to manage this. Each row of data becomes a message, you assign it a unique id, a GUID, that is basically a CorrelationID and it's the same in both systems. Even if one system goes down, when it comes back up the data will be saved and all is good in the world and because of the common id you can always link it up properly.