0

I have an MVC Action Controller method as:

public ActionResult Register(ViewModel.User model)
    {
        DbContextTransaction dbTransaction = null;
        if (ModelState.IsValid)
        {
            try
            {
                UserAccountLogic registerUser = new UserAccountLogic();

                dbTransaction = registerUser.RegisterUser(model);
                Mail.SendEmail(model.Email, model.FirstName);

                dbTransaction.Commit();
                //Session["Email"] = model.Email;
                ViewBag.Style = "block";
            }
            catch (Exception e)
            {
                dbTransaction.Rollback();
                log.Error(e.Message, e);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

RegisteredUser function is in Business Layer as:

public DbContextTransaction RegisterUser(User model)
    {
        //var userEntity = Mapper.Map<User, Dal.UserInfo>(model);
        var userEntity = MapModelToEntity(model);
        var registerService = new Dal.AccountService();

        return registerService.SaveRegisterDetails(userEntity);
    }

and SaveRegisterDetails function is in DataAccessLayer as:

public DbContextTransaction SaveRegisterDetails(UserInfo registerDetails)
    {
        //TransactionModel transactionModel = new TransactionModel();

        using (HealthCarePortalEntities context = new HealthCarePortalEntities())
        {

            using (DbContextTransaction dbTran = context.Database.BeginTransaction())
            {
                context.UserInfo.Add(registerDetails);
                context.SaveChanges();

                return dbTran;
            }
        }

Now my problem is I want to rollback a transaction i.e a newly registered user data from the database when there is any exception in sending activation link to the user. But the problem is the part where I am doing rollback an exception throws because the Database connection is null. So my question is how can I get the connection of DAL layer in Controller which is in another layer. Thanks for the help.

ADyson
  • 57,178
  • 14
  • 51
  • 63
  • Possible duplicate of [Entity Framework 6 transaction rollback](http://stackoverflow.com/questions/22486489/entity-framework-6-transaction-rollback) – bkaf Oct 06 '16 at 13:35
  • personally I wouldn't have ANY data access stuff in the controller - that should be purely object-oriented and not care how the data is persisted. If you have a DAL then everything that deals directly with the database, including things like organising transactions, ought to be within that layer. That's just my humble opinion :-). In your situation, if the email fails for any reason, maybe delete the user rather than rolling back. But of course, even if you don't get an exception sending the mail, it doesn't mean it arrived sucessfully. – ADyson Oct 06 '16 at 13:36
  • another good design option would be not to send emails directly from your controller either - have another (automated) service monitoring user registrations and sending the emails at regular intervals. that way if the email fails it can retry a number of times, and if it still fails then maybe you have some fallback like sending a notification to a technician or something. Also most sites allow the user to request another activation email (or SMS) to be sent instead if they don't receive it. That's a much more scalable and robust solution than trying to send mails during a HTTP request. – ADyson Oct 06 '16 at 13:38
  • @ADyson thanks for your suggestion but I am doing my DataProcessing stuff in my DAL layer and sending the mail from BusinessLayer –  Oct 06 '16 at 13:46
  • I see that, which is why I'm suggesting that doing so is not a very robust or scalable design. Nor, as you've discovered, does it encourage good and logical separation between your UI and data layers. The problem you've got with transactions is entirely the result of not separating your different types of functionality out into separate services. I've suggested one type of design which could help, that's all. – ADyson Oct 06 '16 at 13:49
  • You don't need any of this transaction management. `SaveChanges` manages its own transaction. If any error occurs, it does a roll-back, while the error bubbles up, so you can respond to it. – Gert Arnold Oct 06 '16 at 13:57

2 Answers2

1

You only need to implement IDisposable

private void Dispose(bool disposing)
{
    // ...
    if (disposing && (this._innerConnection != null))
    {
        this._disposing = true;
        this.Rollback(); // there you go
    }
}

Implement IDisposable

MMM
  • 3,132
  • 3
  • 20
  • 32
0

You could use the ambient transaction model.

using(TransactionScope tran = new TransactionScope()) {
     UserAccountLogic registerUser = new UserAccountLogic();

     dbTransaction = registerUser.RegisterUser(model);
     Mail.SendEmail(model.Email, model.FirstName);
     tran.Complete();
}

That way if an exception occurs inside your transaction scope the db rolls back the data.

Quinton Bernhardt
  • 4,773
  • 19
  • 28
  • Actually I am using n-tier architechture and my mail sending layer is different fromm the layer in which Actual dataprocessing is happening and I cannot access Business Layer in DAL –  Oct 06 '16 at 13:44