I am building my own custom repository, based on entity framework, and I'm creating some extension methods that allow me to save partial view models as entity models so I'm building my own Add and Update methods.
Currently, each method has SaveChanges() from DbContext called at the end which means for every model, one call will be invoked.
I'm building this base DAL pattern for MVC4 sites which means most of the time I will access 1 model, but it does not have to be the case though.
Is it too bad practice to call SaveChanges() for each model when updating i.e. 3 entities or should I add everything first to object context and than do SaveChanges() as some sort of transaction commit?
I know it's kind of late answer but i found it useful to share.
Now in EF6 it's easier to acheeve this by using dbContext.Database.BeginTransaction()
like this :
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
// do your changes
context.SaveChanges();
// do another changes
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception ex)
{
//Log, handle or absorbe I don't care ^_^
}
}
}
for more information look at this
again it's in EF6 Onwards
It is a bad practice to call SaveChanges
multiple times (Without a transaction scope) when the related entities should be persisted in a single transaction. What you have created is a leaky abstraction. Create a separate Unit of Work class or use the ObjectContext/DbContext
itself.
I would strongly advise against calling SaveChanges() in each method. Using the repository pattern and unit of work is the better way forward. Unit of work, allows you to be more efficient with your db calls and also helps you against polluting your db if some data is not valid (e.g. user details is ok, but address fails).
Here's a good tutorial to help you.
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
This is another approach to handle multiple context.SaveChanges()
using UnitOfWork that I currently use.
We will hold all context.SaveChanges()
method until this last one is called.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace DataAccess
{
public class UnitOfWork : IUnitOfWork
{
private readonly Context context;
private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private int beginChangeCount;
private bool selfManagedTransaction = true;
public UnitOfWork(Context context)
{
this.context = context;
}
//Use generic repo or init the instance of your repos here
public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntityModel
{
if (repositories.Keys.Contains(typeof(TEntity)))
return repositories[typeof(TEntity)] as IGenericRepository<TEntity>;
var repository = new Repository<TEntity>(context);
repositories.Add(typeof(TEntity), repository);
return repository;
}
public void SaveChanges()
{
if (selfManagedTransaction)
{
CommitChanges();
}
}
public void BeginChanges()
{
selfManagedTransaction = false;
Interlocked.Increment(ref beginChangeCount);
}
public void CommitChanges()
{
if (Interlocked.Decrement(ref beginChangeCount) > 0)
{
return;
}
beginChangeCount = 0;
context.SaveChanges();
selfManagedTransaction = true;
}
}
}
Sample using.
Find my comment in the code below
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace BusinessServices.Domain
{
public class AService : BaseBusinessService, IAService
{
private readonly IBService BService;
private readonly ICService CService;
private readonly IUnitOfWork uow;
public AService (IBService BService, ICService CService, IUnitOfWork uow)
{
this.BService = BService;
this.CService = CService;
this.uow = uow;
}
public void DoSomeThingComplicated()
{
uow.BeginChanges();
//Create object B - already have uow.SaveChanges() inside
//still not save to database yet
BService.CreateB();
//Create object C - already have uow.SaveChanges() inside
//still not save to databse yet
CService.CreateC();
//if there are no exceptions, all data will be saved in database
//else nothing in database
uow.CommitChanges();
}
}
}
A new modern approach as articulated here is adviced in such scenarios.
If you're familiar with the TransactionScope
class, then you already know how to use a DbContextScope
. They're very similar in essence - the only difference is that DbContextScope
creates and manages DbContext
instances instead of database transactions. But just like TransactionScope
, DbContextScope
is ambient, can be nested, can have its nesting behaviour disabled and works fine with async execution flows.
public void MarkUserAsPremium(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
dbContextScope.SaveChanges();
}
}
Within a DbContextScope
, you can access the DbContext
instances that the scope manages in two ways. You can get them via the DbContextScope.DbContexts
property like this:
public void SomeServiceMethod(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = dbContextScope.DbContexts.Get<MyDbContext>.Set<User>.Find(userId);
[...]
dbContextScope.SaveChanges();
}
}
But that's of course only available in the method that created the DbContextScope
. If you need to access the ambient DbContext
instances anywhere else (e.g. in a repository class), you can just take a dependency on IAmbientDbContextLocator
, which you would use like this:
public class UserRepository : IUserRepository
{
private readonly IAmbientDbContextLocator _contextLocator;
public UserRepository(IAmbientDbContextLocator contextLocator)
{
if (contextLocator == null) throw new ArgumentNullException("contextLocator");
_contextLocator = contextLocator;
}
public User Get(Guid userId)
{
return _contextLocator.Get<MyDbContext>.Set<User>().Find(userId);
}
}