Rollingback a transaction in .Net Entity Framework - c#

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.

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

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.

Related

How to Fix: Cannot access a disposed object. in ef-code-first and Core 3.1

I want to Save User Information In the Database.
public async Task<User> CreateAsync(CreateRequestViewModel user)
{
User NewUser = new User
{
UserId = Guid.NewGuid(),
Password = user.Password,
UserName = user.UserName,
};
var resultUser = await _ProjectContext.Users.AddAsync(NewUser);
Person person = new Person()
{
UserId = resultUser.Entity.UserId,
RegisterDate = DateTime.Now,
Email = user.Email,
Gender = user.Gender,
PersonId = Guid.NewGuid(),
};
await _ProjectContext.Persons.AddAsync(person);
return resultUser.Entity;
}
User Service Code:
public async Task<User> Create(CreateRequestViewModel user)
{
try
{
var model = await _unitOfWork.Users.CreateAsync(user);
await _unitOfWork.CommitAsync();
return model;
}
catch (Exception me)
{
throw;
}
}
but when to run this code:
await _unitOfWork.CommitAsync();
code in CommitAsync:
public async Task<int> CommitAsync()
{
int oo = 0;
try
{
oo = await _context.SaveChangesAsync();
}
catch (Exception me)
{
throw;
}
return oo;
}
I get this error:
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances. Object name:
'ProjectContext'.
How it can be fixed.

Why is my ef core data context disposed before use in a service under test?

Background: I am writing tests around services that make use of ef core. I want to use sqllite as it is relational.
I have written a base class for tests that will use a mock db factory I wrote to setup basic generic things like http mocking and the DAL.
namespace Bll.UnitTests
{
public class TestBase : IDisposable
{
// pass httpclient as dependency, setup messageHandler for stubbing
protected HttpClient httpClient;
protected Mock<HttpMessageHandlerFake> fakeHttpMessageHandler = new Mock<HttpMessageHandlerFake> { CallBase = true };
protected Mock<Logger> loggerMock;
protected DalContext dataContext;
protected MockDbFactory mockDbFactory;
public TestBase()
{
mockDbFactory = new MockDbFactory();
httpClient = new HttpClient(fakeHttpMessageHandler.Object);
dataContext = mockDbFactory.testDb;
loggerMock = new Mock<Logger>(dataContext);
}
public void Dispose()
{
mockDbFactory.Dispose();
}
}
}
Here is my mock db factory that should just setup a connection in memory and seems to work.
using Dal;
using Microsoft.EntityFrameworkCore;
using Moq;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
namespace Bll.UnitTests.Factories
{
// In-memory database only exists while the connection is open
public class MockDbFactory : IDisposable
{
private SqliteConnection connection;
public DalContext testDb;
public MockDbFactory()
{
OpenConnection();
testDb = GetTestDb();
}
public void Dispose()
{
CloseConnection();
}
private void OpenConnection()
{
connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
}
private void CloseConnection()
{
connection.Close();
}
private DalContext GetTestDb()
{
var options = new DbContextOptionsBuilder<DalContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new DalContext(options))
{
context.Database.EnsureCreated();
return context;
}
}
}
}
In my test class datacontext is disposed when I debug my service under test.
public class LocationServiceTest : TestBase
{
private LocationService sut;
public LocationServiceTest(): base()
{
sut = new LocationService(
httpClient,
loggerMock.Object,
dataContext
);
}
[Fact]
public async Task UpdateCountriesAsync_CallsCountryApiAndUpdatesDatabase()
{
// arrange
// setup get country data to return 2 countries
var temp = BuildCountryApiReturnable(2);
fakeHttpMessageHandler.Setup(f => f.Send(It.IsAny<HttpRequestMessage>())).Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(temp)
});
// act
try
{
var result = await sut.UpdateCountriesAsync();
// assert
Assert.True(dataContext.Country.Count() == 2);
Assert.True(dataContext.Location.Count() == 2);
}
catch(Exception e)
{
throw e;
}
}
I think I understand that a using statement is necessary as this will create my connection and dispose of it, but I am trying to do that manually so that I can inject the data context into my service. If I have to wrap everything in a using statement I will be forced to change my service..
To answer your question:
In your MockDbFactory, you already disposed the context by the using clause:
private DalContext GetTestDb()
{
var options = new DbContextOptionsBuilder<DalContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new DalContext(options))
{
context.Database.EnsureCreated();
return context; // context will already be disposed after returning
}
}
You should initiate a new instance of DalContext and handle its disposal in your MockDbFactory.Dispose method instead:
private DalContext GetTestDb()
{
...
testDb = new DalContext(options);
//Other configurations
}
...
public void Dispose()
{
CloseConnection();
testDb.Dispose();
}

Task.Factory.StarNew and ApplicationDbContext Update

I need to update a table of my ApplicationDbContext within a Task; however, I'm not getting it done. Here is the error message I've got:
ex.Message = "Cannot access a disposed object. A common cause of this
error is disposing a context that was resolved from dependency
injection and then later trying to use the same context instance
elsewhere in your application. This may occur if you are calling
Dispose...
I understand it has something to do with threading which I'm not so familiar.
Here's the code:
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
Task.Factory.StartNew(() =>
{
//Read value from Table
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
if (contextWatsonFB == null)
{
contextWatsonFB = new ContextWatsonFB()
{
RecipientId = recipientid
};
_context.Add(contextWatsonFB);
_context.SaveChanges();
}
else
{
if (!string.IsNullOrEmpty(contextWatsonFB.Context))
{
model = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
///DO SOME STUFF ////////////////
///Here I need to update my table using some values processed above in "some stuff"
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
contextWatsonFB.Context = JsonConvert.SerializeObject(context);
_context.Update(contextWatsonFB);
_context.SaveChanges();
}
}
As you can figure it out, its a webhook connection for Facebook, which requires the process to be handled in a Task. Within "some stuff", basically I'm consuming IBM Watson Conversation service whom persist a conversation "context" that I'm not able to send back and forth to Facebook, that's why I figure it out to persist such data in a table to keep the differences among multiple request from facebook messenger.
Luckily, the following code did the trick:
private readonly IServiceProvider _provider;
public FacebookBotController(ApplicationDbContext context, IServiceProvider provider)
{
_provider = provider;
}
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
if (data == null || data?.entry?.Count == 0)
{
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
try
{
var task = Task.Factory.StartNew(async () =>
{
using (IServiceScope scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
ApplicationDbContext _contx = _provider.GetService<ApplicationDbContext>();
ContextWatsonFB contextWatsonFB = await _contx.ContextWatsonFB.Where(m => m.SenderId == senderId).FirstOrDefaultAsync();
if (contextWatsonFB == null)
{
context = null;
}
else
{
context = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
}
}

Has ASP.NET Identity UserManager.CreateAsync() been updated with breaking changes recently?

A few months ago I created my own implementation of ASP.NET Identity, overriding the UserStore to use dapper and a custom sql connection instead of Entity Framework. It worked fine at the time.
Now I updated all of the nuget packages today and I've been fighting problems since. Primarily when I register a new user by calling var result = await UserManager.CreateAsync(user, newAccount.Password); It creates the user and performs all other checks just fine, but then throws a bizarre error saying Invalid operation. The connection is closed.
Its as though UserManager.CreateAsync has a new method that needs to be overridden, but I have absolutely no idea what it could be.
For reference, here are parts of my implementation:
Account Controller:
[Authorize]
public class AccountController : Controller
{
public UserManager<User> UserManager { get; private set; }
public UserTokenProvider UserTokenProvider { get; set; }
public AccountController() : this(new UserManager<User>(new UserStore(ConfigurationManager.ConnectionStrings["DBConn"].ConnectionString)))
{
}
public AccountController(UserManager<User> userManager)
{
UserManager = userManager;
UserManager.PasswordHasher = new NoPasswordHasher();
}
...
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegistrationModel newAccount)
{
try
{
if (DbConfig.MaintenanceMode) return RedirectToAction("ComingSoon", "Home");
if (ModelState.IsValid)
{
var user = new User(newAccount);
var result = await UserManager.CreateAsync(user, newAccount.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
var userIn = await UserManager.FindByEmailAsync(newAccount.UserName);
if (!userIn.EmailConfirmed)
{
await SendValidationEmail(userIn);
return RedirectToAction("ConfirmationSent", new {userName = user.UserName});
}
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(newAccount);
}
catch (Exception ex)
{
var msg = ex.Message;
return View(newAccount);
}
}
User Store:
public class UserStore : IUserStore<User>, IUserLoginStore<User>, IUserPasswordStore<User>, IUserSecurityStampStore<User>, IUserRoleStore<User>, IUserEmailStore<User>
{
private readonly string _dbConn;
public UserStore(string conn = null)
{
if (conn != null)
_dbConn = conn;
else
_dbConn = DbConfig.ConnectionString;
}
public void Dispose()
{
}
public virtual Task CreateAsync(User user)
{
using (var _conn = new SqlConnection(_dbConn))
{
if (_conn.State == ConnectionState.Closed) _conn.Open();
return _conn.ExecuteAsync("users_UserCreate",
new
{
#UserId = user.Id,
#UserName = user.UserName,
#PasswordHash = user.PasswordHash,
#SecurityStamp = user.SecurityStamp
}, commandType: CommandType.StoredProcedure);
}
}
... Remaining methods omitted for brevity ...
You'll notice that the UserStore.CreateAsync() function has if (_conn.State == ConnectionState.Closed) _conn.Open(); as this was the suggestion from multiple threads regarding the connection closed error. Even without this line the query works fine and inserts a new user into the database correctly.
The error is coming from somewhere after UserStore.CreateAsync() is called by UserManager.CreateAsync().
Any idea what's missing?
The answer is no, ASP.NET Identity hasn't altered with breaking changes.
Using DotNetPeek I had a look at the Identity library to see what methods were being called during UserManager.CreateAsync() and it only calls UserStore.CreateSync and a password update.
After playing around with the code some more it dawned on me that whilst UserManager.CreateSync is awaited, the internal calls to UserStore.CreateSync isn't. Tanks to having to override public virtual Task CreateAsync(User user) and having to return a task that isn't awaited, we have to juggle some code to await the response from Dapper before returning it as part of a task again.
So, here's the updated UserStore.CreateAsync override that works. Note: if (_conn.State == ConnectionState.Closed) _conn.Open(); isn't actually needed as the connection was being closed before the method had finished, Dapper does a brilliant job of handling connections for you.
public virtual Task CreateAsync(User user)
{
using (var _conn = new SqlConnection(_dbConn))
{
var result = _conn.ExecuteAsync("users_UserCreate", new
{
#UserId = user.Id,
#UserName = user.UserName,
#PasswordHash = user.PasswordHash,
#SecurityStamp = user.SecurityStamp
}, commandType: CommandType.StoredProcedure).ConfigureAwait(true);
return Task.FromResult(result);
}
}
Hopefully this will help someone else in the future facing the same sort of problem.

Partial update in Web API is not working

I created a post method in my Web API to do a partial update of one table - to change Order Status field in the table. here is what I got so far:
public IHttpActionResult UpdateOrderStatus(Order ord)
{
try
{
if (!ModelState.IsValid)
{
throw new ProcessException("One or more values are invalid, please check.");
}
using (MyContext ctx = new MyContext())
{
ord.StatusID = 3;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}
}
catch (Exception ex)
{
throw new ProcessException(ex.Message);
}
}
I am stepping through the code and everything seems to be working - no errors, but the database is not getting updated. What am I doing wrong?
Update:
Set the StatusID in the object BEFORE passing it to the Web API method:
var ord = { "OrderID": 1, "OrderDate": CurrentDate, "StatusID": 3};
public IHttpActionResult UpdateOrderStatus(Order ord)
{
try
{
if (!ModelState.IsValid)
{
throw new ProcessException("One or more values are invalid, please check.");
}
using (MyContext ctx = new MyContext())
{
ctx.Entry(ord).State = EntityState.Modified;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}
}
catch (Exception ex)
{
throw new ProcessException(ex.Message);
}
}
If you're using EntityFramework then you need to inform the context about the Order object you are updating. In EF6 you would typically have a DbSet property on the context called Orders, so I'd advise adding ctx.Orders.Attach(Order) before you set the StatusID
Try this code:
Get orderEntity from database by comparing with ord.OrderID, Then update StatusID of that record.
using (MyContext ctx = new MyContext())
{
var orderEntity = ctx.Order.FirstOrDefault(x=> x.OrderID == ord.OrderID);
orderEntity.StatusID = 3;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}

Categories

Resources