简体   繁体   中英

Which lifetime-manager do I register my DbContext into Unity container when writing a WPF application?

I am writing a new C# application on the top of Prism 6.3 framework using the well-known MVVM design pattern. I am using Unity IoC container to help me manage my dependencies.

I am using Entity Framework Core to interact with the database. However, I don't want to tightly couple my application to Entity Framework Core, so I implemented Repository and UnitOfWork patterns to make it easy for me to swap out the Entity Framework Core implementation if needed.

My repository implementation provides a method called Save() which calls EF Core's SaveChanges() method. The repositories are injected into my business-service so that my business-service expose one method to do a single task. For example, if I want to create a new order, I would call the Create(orderViewModel) method which internally calls the Add() and the Save() method on the OrderRepository .

Additionally, the UnitOfWork provides Save() , BeginTransaction() , Commit() and Rollback() methods which allow me control the transaction behavior. In another words it will give me the flexibility to either commit or rollback the SQL transaction when needed.

To explain my use case better, here is an example of how I would add new order to my database directly using the business-service without transaction or unit-of-work.

OrdersService.Create(orderViewModel); // this will call the `Add` and the `Save()` methods on the OrderRepository;

Here is another example which demonstrate how I would add a new order and order-items to my database using the business-services while using unit-of-work to start transaction and control the transaction.

using(var transaction = UnitOfWork.BeginTransaction())
{
    try 
    {
        var order = OrdersService.Create(orderViewModel);
        OrdersService.CreateRange(order.Id, orderItemsViewModel);
        transaction.Commit();
    } 
    catch(Exception e)
    {
        Log.Add(e);
        transaction.RollBack();
    }
}

In the second example above, even-though the OrdersService.Save and OrdersService.SaveRange each call the SaveChanges() method the data are not committed to the database since I am wrapping them with a transaction.

Question : what LifeTimeManager should I register the DbContext , IUnitOfWork and each of my repositories with?

In a web environment, I would register everything using PerRequestLifetimeManager then during the request I am reusing the same DbContext and everything works fine and the DbContext is disposed at the end of the http request. But not sure how to register everything in a WPF application where I can still use transaction to control everything while allowing the repository to call the SaveChanges()

If needed here is my EntityRepository implementation

public class EntityRepository<TEntity, TKeyType> : IRepository<TEntity, TKeyType>
    where TEntity : class
    where TKeyType : struct
{
    protected readonly DbContext Context;
    protected readonly DbSet<TEntity> DbSet;

    public EntityRepository(DbContext context)
    {
        Context = context;
        DbSet = context.Set<TEntity>();
    }

    public TEntity Get(TKeyType id)
    {
        return DbSet.Find(id);
    }

    public IEnumerable<TEntity> GetAll()
    {
        return DbSet.ToList();
    }

    public bool Any(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.Any(predicate);
    }

    public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.Where(predicate);
    }

    public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.SingleOrDefault(predicate);
    }

    public virtual TEntity Add(TEntity entity)
    {
        var record = Context.Add(entity);
        record.State = EntityState.Added;

        return entity;
    }

    public virtual IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
    {
        Context.AddRange(entities);

        return entities;
    }

    public void Remove(TEntity entity)
    {
        Context.Remove(entity).State = EntityState.Deleted;
    }

    public void RemoveRange(IEnumerable<TEntity> entities)
    {
        Context.RemoveRange(entities);
    }

    public void Update(TEntity entity)
    {
        DbSet.Attach(entity);
        var record = Context.Entry(entity);
        record.State = EntityState.Modified;
    }

    public IQueryable<TEntity> Query()
    {
        return DbSet;
    }

    public void Save()
    {
        Context.SaveChanges();
    }
}

And here is my unit of work implementation

public sealed class UnitOfWork : IUnitOfWork
{
    private bool IsDisposed = false;
    private readonly DbContext Context;

    public IOrderRepository Orders { get; private set; }
    public IOrderItemRepository OrderItems { get; private set; }

    public UnitOfWork(DbContext context)
    {
        Context = context;
        Orders = new OrderRepository(context);
        OrderItems = new OrderItemRepository(context);
    }

    public int Save()
    {
        Context.SaveChanges();

        return 0;
    }

    public void Dispose()
    {
        Dispose(true);
    }

    public IDatabaseTransaction BeginTransaction()
    {
        return new EntityDatabaseTransaction(Context);
    }

    private void Dispose(bool disposing)
    {
        if (IsDisposed)
        {
            return;
        }

        if (disposing)
        {
            Context.Dispose();
        }

        IsDisposed = true;
    }
}

Transient (an instance per view) lifetime would be the way to go if your DI doesn't support scoping, but then you would need to abstract away your DbContext being passed through into the repo's and unitOfWork, otherwise new instances of the DbContext will be passed in there. On construction of the page, a new instance is created, and on moving away from that view, that DBContext should be disposed of. UnitOfWork would follow the same path as you wouldn't want a UnitOfWork spanning multiple instances of a DBContext. See http://blogs.microsoft.co.il/gilf/2010/02/07/entity-framework-context-lifetime-best-practices/ . Otherwise, if your DI has the concept of container hierarchies, and you're able to create a container scope per view, then a singleton would work in this instance and you wouldn't need any abstractions mentioned above and would be quite a bit easier to work with.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM