I am trying to implement the Unit of Work pattern and have hit an unexpected behavior. My controller below successfully gets the user from the db, updates the name, and rolls back the transaction. After running this method, no change is made to the db as expected. However, the db query after the rollback still gets the user with the changed name and I don't understand why. Does EF perform some kind of caching?
public ActionResult GetTest()
{
_unitOfWork.BeginTransaction();
var user = _unitOfWork.UserRepository.GetByID(123);
// current name is "Chris"
user.Name = "Adam";
_unitOfWork.Save();
_unitOfWork.RollbackTransaction();
var user2 = _unitOfWork.LarRepository.GetByID(123);
// user2.Name is equal to "Adam" but the DB was never updated and I expected "Chris"
return Ok(user2) ;
}
Here are the applicable Unit of Work methods
public void Save()
{
_context.SaveChanges();
}
public void BeginTransaction()
{
_transaction = _context.Database.BeginTransaction();
}
public void CommitTransaction()
{
if (_transaction != null)
_transaction.Commit();
}
public void RollbackTransaction()
{
if (_transaction != null)
_transaction.Rollback();
}
private IDbContextTransaction _transaction;
Related
I am trying to implement roll back the transaction in asp net boilerplate, so if the delete of the subsequent entity throws an exemption I need to roll back the delete of the previous entity, with the following code, the first delete is committed and is not rolled back on catch of exemption, and help over why is much appreciated.
the controller
public ActionResult Expense_Destroy([DataSourceRequest] DataSourceRequest request, TS_Expense_ViewModel expenseVM)
{
if (expenseVM != null)
{
try
{
//Delete The Expense and Expense Audit.....
_iTS_Emp_Expense_AppService.Delete(expenseVM.TS_Expense_VMID);
}
catch (Exception ex)
{
string exmptionText = _exemption_AppService.GetTimeTenantUserStamp + ex.Message;
ModelState.AddModelError(string.Empty, exmptionText);
Logger.Error(exmptionText + ex.StackTrace);
}
}
return Json(new[] { expenseVM }.ToDataSourceResult(request, ModelState));
}
then the application services
public void Delete(int id)
{
//Delete Expense.
_iTS_Emp_Expense_Manager.Delete(id);
//Delete Expense Audit.
_iTS_Audit_Expense_Manager.DeleteByExpenseId(id);
}
and the entity management survives
public void DeleteByExpenseId(int id) //Expense Id
{
var expense = _repositoryAuditExpense.FirstOrDefault(p => p.ExpenseID == /*id*/88);
if (expense == null)
{
throw new Exception(CustomMessageConsts.ServerGetError);
}
_repositoryAuditExpense.Delete(expense);
}
what happens, if DeleteByExpenseId throws an exemptions, the _iTS_Emp_Expense_Manager.Delete(id); still commits the delete of the previous entity, I tried to manage the unit of work scope inside the application services by wrapping the two interfaces inside a using statement but this also did not work. the code I tried looks as follows:
public void Delete(int id)
{
using (var unitOfWork = _unitOfWorkManager.Begin())
{
//Delete Expense.
_iTS_Emp_Expense_Manager.Delete(id);
//Delete Expense Audit.
_iTS_Audit_Expense_Manager.DeleteByExpenseId(id);
unitOfWork.Complete();
}
}
I'm building an n-tier web app in ASP.NET Core 3.1 that uses Entity Framework to interact with a SQL Server database. Everything works fine except for updating an entity twice without refreshing in between both requests.
So the first time I read and entity from the database and update it, it works perfectly. When I try to update the entity immediately after, I get the following exception:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded...
I'm using the generic repository pattern in combination with asynchronous code.
An overview of my code:
GenericRepository.cs:
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DataContext _context;
protected DbSet<T> _entities;
public Repository(DataContext context)
{
this._context = context;
_entities = context.Set<T>();
}
}
MemberRepository.cs:
public class MemberRepository : Repository<Member>, IMemberRepository
{
public MemberRepository(DataContext context) : base(context)
{
//
}
public async override Task<Member> SelectByIdAsync(int id)
{
return await this._entities
.Where(m => m.Id == id)
.Include(m => m.Addresses)
.Include(m => m.MemberHobbies)
.Include(m => m.Car)
.Include(m => m.EmailAddresses)
.FirstOrDefaultAsync();
//return await this._context.Members.Where(m => m.Id == id).Include(m => m.Addresses).Include(m => m.MemberHobbies).Include(m => m.Car).Include(m => m.EmailAddresses).FirstOrDefaultAsync();
}
public async override Task UpdateAsync(Member member)
{
this._context.Members.Update(member);
//this._context.Set<Member>().Update(member);
await this._context.SaveChangesAsync();
}
}
startup.cs:
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped<IMemberRepository, MemberRepository>();
I have tried to change the way I read or update my entity (see the commented out lines), but those give the same result. Also: I have other entities which work perfectly, the only difference is that the Member class has multiple foreign keys (see .Include(...))
Might it have anything to do with improper use of asynchronous functions? Maybe because I'm not returning the updated Member and thus next time I execute an update I'm sending a now out of date member?
Also: I found this answer, but it makes no sense to me at all
Thanks!
I use the generic code like this for update:
public virtual async Task<T> UpdateAsync(T t)
{
if (t == null) return null;
T exist;
try
{
exist = await Context.Set<T>().FindAsync(t.Id);
if (exist == null)
{
t.ErrorMessage = "Can't find item to update";
return t;
}
Context.Entry(exist).CurrentValues.SetValues(t);
var result = await Context.SaveChangesAsync();
if (result == 0) t.ErrorMessage = "Can't saved item";
}
catch (Exception ex)
{
t.ErrorMessage = ex.Message;
return t;
}
return exist;
}
Or you can try this code for Member class:
public async Task UpdateAsync(Member member)
{
try
{
var exist = await _context.Members.Where(i.Id=member.Id).FirstOrDefaultAsync();
if (exist == null) ....errorMessage = "Can't find item to update";
Context.Entry(exist).CurrentValues.SetValues(member);
var result = await Context.SaveChangesAsync();
if (result == 0) ....errorMessage = "Can't save item";
}
catch (Exception ex)
{
...errorMessage = ex.Message;
}
}
EF keeps track of your data. Here's what you do:
Get two copies of the same entry;
Change the former copy;
Update the change for former copy;
Change the latter copy, which kept the track of the entry's state prior the 3rd step;
Update the change made by the latter copy, but since this change reflects to the former state of an entry - you get that error;
Here's what you should do instead:
Add an extra layer to your business objects that will carry the states around;
Get a current state of your database entry upon receiving the update request;
Apply the changes to your DB entry;
Save;
Example:
// DB entry
public class Member
{
public int Id { get; set; }
public string Name { get; set; }
}
// DTO for carrying around
public class MemberForNameChangeDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Repo
{
public Task<bool> UpdateMemberName(MemberForNameChangeDto memberForNameChange)
{
// _context.Members is DbSet<Member>
var memberEntry = _context.Members.Find(memberForNameChange.Id);
// null checks, verifications, etc
memberEntry.Name = memberForNameChange.Name;
var result = await _context.SaveChangesAsync();
return result > 0;
}
}
I have an ASP.Net MVC 5 application using EF6. I'm using Ninject for dependency injection.
I have the following code to add comments to a ticket. But sometimes it creating duplicate record. Why is is this happening and how can I fix?
So all in all I'm ending up with 3 comments being added as opposed to two with the user comments being duplicated - this is only happening occasionally.
var ticket = _unitOfWork.TicketRepository.GetTicket(dto.TicketId);
using (var transaction = _unitOfWork.BeginTransaction())
{
try
{
// Add user posted comment
var comment = new Comment
{
TicketId = dto.TicketId,
Comments = dto.Comments
};
ticket.AddComment(comment);
// save changes
_unitOfWork.Complete();
// Add a system comment
ticket.AddComment(new Comment
{
TicketId = dto.TicketId,
Comments = "<b>System Comment:</b><br>Some auto comment"
});
_notificationService.Send(ticket, comment, User.Identity.GetUserId());
// save changes
_unitOfWork.Complete();
// Commit
transaction.Commit();
return Ok(Mapper.Map<CommentDto>(_unitOfWork.CommentRepository.GetComment(comment.Id)));
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
These are the methods in my unitOfWork class:
public void Complete()
{
_context.SaveChanges();
}
public DbContextTransaction BeginTransaction()
{
return _context.Database.BeginTransaction();
}
This is the add comment method in my Ticket class:
public void AddComment(Comment comment)
{
Comments.Add(comment);
}
* UPDATE *
The only other thing I'm aware of that uses db context is a background job to send email which runs every minute using hangfire but doesn't use unitofwork - see my code below:
[DisallowConcurrentExecution]
public class EmailJob : IJob
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly IUserNotificationRepository _userNotificationRepository;
private readonly INotificationMailer _notificationMailer;
private readonly ApplicationDbContext _context;
public EmailJob()
{
_context = new ApplicationDbContext();
_userNotificationRepository = new UserNotificationRepository(_context);
_notificationMailer = new NotificationMailer();
}
public EmailJob(IUserNotificationRepository userNotificationRepository,
INotificationMailer notificationMailer)
{
_userNotificationRepository = userNotificationRepository;
_notificationMailer = notificationMailer;
}
// Use for hangfire
public void Execute()
{
SendMail(_logger);
}
private void SendMail(Logger logger)
{
// some mail sending code...
// Persist changes back to database
_context.SaveChanges();
}
}
Any ideas chaps?
I want to change all of an object properties using entity framwork.
after searching i got to have this:
Controller,action:
public ActionResult Test()
{
var user = GetCurrentUser();
user.FirstName = "BLAH BLAH";
new UserRepository().UpdateUser(user);
return RedirectToAction("Index");
}
and in my UserRepository:
public bool UpdateUser(ApplicationUser target)
{
using (var db = new AppDatabase())
{
db.Entry(target).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
but when i try execute i got this error
An entity object cannot be referenced by multiple instances of EntityChangeTracker.
so,any ways to fix or any better way?
using entity framework 6.0.0 and .net 4.5
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}
You should use same instance of db context for finding and updating, so you UserRepository can be:
class UserRepository : IDisposable //using IDisposable to dispose db context
{
private AppDatabase _context;
public UserRepository()
{
_context = new AppDatabase();
}
public ApplicationUser Find(string id)
{
return _context.Set<ApplicationUser>().Find(id);
}
public void Update(ApplicationUserentity entity)
{
_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
You can use it in controller:
public ActionResult Test()
{
using (var repository = new UserRepository())
{
var user = repository.Find(User.Identity.GetUserId());
user.FirstName = "BLAH BLAH";
repository.Update(user);
}
return RedirectToAction("Index");
}
I also think using some dependency injection framework would be beneficial for you. So go for it!!
Be sure that all objects came from the same context!
var userContextOne = new MyDbContext();
var user = userContextOne.Users.FirstOrDefault();
var AppDbContextTwo = new MyDbContext();
// Warning when you work with entity properties here! Be sure that all objects came from the same context!
db.Entry(target).State = EntityState.Modified;
AppDbContextTwo.SaveChanges();
The scond problem (not related to the exception!):
db.Entry(target).State = EntityState.Modified;
Why you are doing that?! You dont not have Detached Scenario? did you have disabled your Changetracker? anyway just execute DetectChanges and this method will find the changed data you do not have to do it by your self.
I have a web application where I have just began to use Entity Framework. I read the beginners tutorials, and topics about benefits of object context per request for web apps.
However, I am not sure my context is at the right place...
I found this very useful post (Entity Framework Object Context per request in ASP.NET?) and used the suggested code :
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
And in Global.asax :
protected virtual void Application_EndRequest()
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context != null)
{
context.Dispose();
}
}
Then, I am using it in my pages :
public partial class Login : System.Web.UI.Page
{
private MyEntities context;
private User user;
protected void Page_Load(object sender, EventArgs e)
{
context = DbContextManager.Current;
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = context.Users.Single(u => (u.Id == guid));
}
}
protected void _Button_Click(object sender, EventArgs e)
{
Item item = context.Items.Single(i => i.UserId == user.Id);
item.SomeFunctionThatUpdatesProperties();
context.SaveChanges();
}
}
I did read a lot but this is still a little bit confused for me.
Is the context getter okay in Page_Load ? Do I still need to use "using" or will disposal be okay with the Global.asax method ?
If I am confusing something I am sorry and I would be really, really grateful if someone could help me understand where it should be.
Thanks a lot !
Edits following nativehr answer and comments :
Here is the DbContextManager:
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + typeof(MyEntities).ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
The page :
public partial class Login : System.Web.UI.Page
{
private User user;
protected void Page_Load(object sender, EventArgs e)
{
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = UserService.Get(guid);
}
}
protected void _Button_Click(object sender, EventArgs e)
{
if (user != null)
{
Item item = ItemService.GetByUser(user.Id)
item.SomeFunctionThatUpdatesProperties();
ItemService.Save(item);
}
}
}
And the ItemService class :
public static class ItemService
{
public static Item GetByUser(Guid userId)
{
using (MyEntities context = DbContextManager.Current)
{
return context.Items.Single(i => (i.UserId == userId));
}
}
public static void Save(Item item)
{
using (MyEntities context = DbContextManager.Current)
{
context.SaveChanges();
}
}
}
I would not rely on Thread.CurrentContext property.
Firstly, Microsoft says, Context class is not intended to be used directly from your code:
https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx
Secondly, imagine you want to make an async call to the database.
In this case an additional MyEntities instance will be constructed, and it will not be disposed in Application_EndRequest.
Furthermore, ASP.NET itself does not guarantee not to switch threads while executing a request.
I had a similar question, have a look at this:
is thread switching possible during request processing?
I would use "MyDb_" + typeof(MyEntities).ToString() instead.
Disposing db context in Application_EndRequest is OK, but it produces a bit performance hit, 'cause your context will stay not disposed longer than needed, it is better to close it as soon as possible (you actually don't need an open context to render the page, right?)
Context pre request implementation would make sense if it has to be shared between different parts of your code, insted of creating a new instance each time.
For example, if you utilize the Repository pattern, and several repositories share the same db context while executing a request.
Finally you call SaveChanges and all the changes made by different repositories are committed in a single transaction.
But in your example you are calling the database directly from your page's code, in this case I don't see any reason to not create a context directly with using.
Hope this helps.
Update: a sample with Context per request:
//Unit of works acts like a wrapper around DbContext
//Current unit of work is stored in the HttpContext
//HttpContext.Current calls are kept in one place, insted of calling it many times
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyContext _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork) HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities GetContext()
{
if(_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
public int Commit()
{
return _dbContext != null ? _dbContext.SaveChanges() : null;
}
public void Dispose()
{
if(_dbContext != null)
_dbContext.Dispose();
}
}
//ContextManager allows repositories to get an instance of DbContext
//This implementation grabs the instance from the current UnitOfWork
//If you want to look for it anywhere else you could write another implementation of IContextManager
public class ContextManager : IContextManager
{
public MyEntities GetContext()
{
return UnitOfWork.Current.GetContext();
}
}
//Repository provides CRUD operations with different entities
public class RepositoryBase
{
//Repository asks the ContextManager for the context, does not create it itself
protected readonly IContextManager _contextManager;
public RepositoryBase()
{
_contextManager = new ContextManager(); //You could also use DI/ServiceLocator here
}
}
//UsersRepository incapsulates Db operations related to User
public class UsersRepository : RepositoryBase
{
public User Get(Guid id)
{
return _contextManager.GetContext().Users.Find(id);
}
//Repository just adds/updates/deletes entities, saving changes is not it's business
public void Update(User user)
{
var ctx = _contextManager.GetContext();
ctx.Users.Attach(user);
ctx.Entry(user).State = EntityState.Modified;
}
}
public class ItemsRepository : RepositoryBase
{
public void UpdateSomeProperties(Item item)
{
var ctx = _contextManager.GetContext();
ctx.Items.Attach(item);
var entry = ctx.Entry(item);
item.ModifiedDate = DateTime.Now;
//Updating property1 and property2
entry.Property(i => i.Property1).Modified = true;
entry.Property(i => i.Property2).Modified = true;
entry.Property(i => i.ModifiedDate).Modified = true;
}
}
//Service encapsultes repositories that are necessary for request handling
//Its responsibility is to create and commit the entire UnitOfWork
public class AVeryCoolService
{
private UsersRepository _usersRepository = new UsersRepository();
private ItemsRepository _itemsRepository = new ItemsRepository();
public int UpdateUserAndItem(User user, Item item)
{
using(var unitOfWork = new UnitOfWork()) //Here UnitOfWork.Current will be assigned
{
_usersRepository.Update(user);
_itemsRepository.Update(user); //Item object will be updated with the same DbContext instance!
return unitOfWork.Commit();
//Disposing UnitOfWork: DbContext gets disposed immediately after it is not longer used.
//Both User and Item updates will be saved in ome transaction
}
}
}
//And finally, the Page
public class AVeryCoolPage : System.Web.UI.Page
{
private AVeryCoolService _coolService;
protected void Btn_Click(object sender, EventArgs e)
{
var user = .... //somehow get User and Item objects, for example from control's values
var item = ....
_coolService.UpdateUserAndItem(user, item);
}
}
I think you should read a bit more about repository pattern for EntityFramework and UnitofWork pattern.
Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC
I know this is mvc and you are problably using web forms but you can get an idea of how to implement it.
Disposing the context on each request is a bit strange, because there might be requests where you will not touch the database, so you will be doing unnecessary code.
What you should do is get a layer for data access and implement a repository pattern that you will access on whatever method you will need on the code behind of your page.