We are currently having a problem of architecture on a WPF application. It concerns EntityFramework context management, it’s instantiated once and used during the entire life of the application. So we end up with a cache issue, entities are not updated when they were loaded once. Our entities are obsolete when using the application.
This is a schema of the current architecture.
[Export(typeof(OrderViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class OrderViewModel : ViewModelBase
{
private readonly IOrderManagementService _orderManagementService;
private readonly IOrderReadOnlyRepository _orderReadOnlyRepository;
[ImportingConstructor]
public OrderViewModel(IOrderManagementService orderManagementService, IOrderReadOnlyRepository orderReadOnlyRepository)
{
_orderManagementService = orderManagementService;
_orderReadOnlyRepository = orderReadOnlyRepository;
}
}
public class OrderManagementService : IOrderManagementService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IOrderManagementBusiness _orderManagementBusiness;
[ImportingConstructor]
public OrderManagementService (IUnitOfWork unitOfWork, IOrderManagementBusiness orderManagementBusiness)
{
_unitOfWork= unitOfWork;
_orderManagementBusiness = orderManagementBusiness;
}
}
public class OrderManagementBusiness : IOrderManagementBusiness
{
private readonly IOrderReadOnlyRepository _orderReadOnlyRepository;
[ImportingConstructor]
public OrderManagementBusiness (IOrderReadOnlyRepository orderReadOnlyRepository)
{
_orderReadOnlyRepository = orderReadOnlyRepository;
}
}
public class OrderReadOnlyRepository : ReadOnlyRepositoryBase<DataModelContainer, Order>, IOrderReadOnlyRepository
{
[ImportingConstructor]
public OrderReadOnlyRepository (IUnitOfWork uow) : base(uow)
{
}
}
public abstract class ReadOnlyRepositoryBase<TContext, TEntity> : IReadOnlyRepository<TEntity>
where TEntity : class, IEntity
where TContext : DbContext
{
protected readonly TContext _context;
protected ReadOnlyRepositoryBase(IUnitOfWork uow)
{
_context = uow.Context;
}
protected DbSet<TEntity> DbSet
{
get { return _context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> GetAll(System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = DbSet.AsNoTracking();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
return query.ToList();
}
public virtual IQueryable<TEntity> All()
{
return DbSet.AsNoTracking();
}
public virtual IQueryable<TEntity> AllWhere(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate).AsNoTracking();
}
public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate).AsNoTracking().FirstOrDefault();
}
public virtual TEntity GetById(int id)
{
TEntity find = DbSet.Find(id);
_context.Entry(find).State = System.Data.EntityState.Detached;
return DbSet.Find(id);
}
We can see that the context is given to the repository in the constructor. Select methods use the "AsNoTracking ()" method to not cache entities. It's a temporary solution which is obviously not viable in long term.
public class UnitOfWork : IUnitOfWork
{
private DataModelContainer _context;
public UnitOfWork()
: this(new DataModelContainer())
{
}
public UnitOfWork(DataModelContainer context)
{
_context = context;
}
public DataModelContainer Context
{
get { return _context; }
}
public int Save()
{
return _context.SaveChanges();
}
}
During the first composition of a service with MEF, UnitOfWork will be instantiated with the default constructor which instantiate the context.
Some pieces of code have been omitted for readability.
The lifetime of the context is clearly an issue. Knowing that all calls within the same service method must share the same context.
How can we consider modifying the architecture to avoid having a single context ?
Feel free to ask questions ! If needed, I can attach a test project which highlight the issue.
1 Answer. First, DbContext is a lightweight object; it is designed to be used once per business transaction. Making your DbContext a Singleton and reusing it throughout the application can cause other problems, like concurrency and memory leak issues. And the DbContext class is not thread safe.
Don't dispose DbContext objects. Although the DbContext implements IDisposable , you shouldn't manually dispose it, nor should you wrap it in a using statement. DbContext manages its own lifetime; when your data access request is completed, DbContext will automatically close the database connection for you.
The AddDbContext extension method registers DbContext types with a scoped lifetime by default.
Entity Framework DB contexts are not thread safe. If an ASP.NET Core app wishes to process requests in a multi-threaded way while using Entity Framework Core, it's important to carefully manage DbContexts so that a single context isn't used on multiple threads simultaneously.
In your application there is only single unit of work but that is not the purpose of a unit a work. Instead, you need to create a unit of work each time "you work with the database". In your case the UnitOfWork
should not be part of the MEF container but you can create a UnitOfWorkFactory
and inject it from the container. Then the services can create a UnitOfWork
each time "work has to be done" with the database:
using (var unitOfWork = unitOfWorkFactory.Create()) {
// Do work ...
unitOfWork.Save();
}
I have modified UnitOfWork
so it implements IDisposable
. This will allow you to dispose the EF context and also perhaps rollback a transaction if Save
was not called. If you have no need for the extra transaction handling you can even get rid of the UnitOfWork
class because it simply wraps the EF context and instead you can used the EF context as a unit of work directly.
This change will force you to modify how the service and the repositories are structured but you really have to because your issue is that you have a single unit of work that exists for the entire duration of the application.
Outline clearly distinguished use cases, which would maintain own lifetime scope. This could help preventing other resources leaks as well (which are pretty frequent when using WPF).
Consider generic algorithm:
The problem is that your scope currently is your application.
Now imagine that you manage scope at view level. You allocate, display view, get user's input, save changes and then the whole object tree is disposed at once.
Obviously, you should be flexible with scopes. Sometimes it can be useful to use it at view level (like "Edit item"), sometimes it could spread across several views (like wizard, for example). You can even maintain data-driven scopes (imagine you open a project in Visual Studio; begin lifetime scope to manage all resources, which should be available while project 'lives').
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With