WPF/MVVM with Entity Framework, Repository and Unit of Work pattern

Posted on

Problem

I’m building WPF/MVVM application and I’m struggling implementing Unit of work pattern.

The part, about which I’m not sure is UnitOfWork class. Here is my thought:

It’s coupled with my repositories and MyDatabaseContext. At first impression, it doesn’t sound good. But, I think, UnitOfWork should be coupled with DbContext. Technically it should be named MyDatabaseUnitOfWork. What do you think?

Unit of work:

public interface IUnitOfWork : IDisposable 
{
    int Complete();
    ICustomerRepository Customers { get; }
    IPhoneRepository Phones { get; }
    IAddressRepository Address { get; }
}

public class UnitOfWork<TContext> : IUnitOfWork
    where TContext : MyDatabaseContext, new()
{
    public ICustomerRepository Customers { get; }
    public IPhoneRepository Phones { get; }
    public IAddressRepository Address { get; }

    public UnitOfWork()
    {
        context = new TContext();
        Customers = new CustomerRepository(context);
        Phones = new PhoneRepository(context);
        Address = new AddressRepository(context);
    } 

    public int Complete()
    {
        return context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

public interface IUnitOfWorkFactory
{
    IUnitOfWork Create();
}

public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    public IUnitOfWork Create()
    {
        return new UnitOfWork<MyDatabaseContext>();
    }
}

Useage:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        try
        {
            UnityContainer unityContainer = new UnityContainer();
            unityContainer.RegisterType<IUnitOfWorkFactory, UnitOfWorkFactory>();
            unityContainer.RegisterType<MainWindowViewModel>();

            var window = new MainWindow()
            {
                DataContext = unityContainer.Resolve<MainWindowViewModel>()
            };
            window.Show();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}


public class MainWindowViewModel : ObservableObject
{
    private readonly IUnitOfWorkFactory unitOfWorkFactory;

    public MainWindowViewModel(IUnitOfWorkFactory uowFactory)
    {
        unitOfWorkFactory = uowFactory;
    }

    private int DoSomething()
    {
        using (var unitOfWork = unitOfWorkFactory.Create())
        {


        }
    }
}

Sample repository:

public interface IRepository<TEntity> where TEntity : class
{
    TEntity Get(int id);
    IEnumerable<TEntity> Get(Func<TEntity, bool> predicate);
    IEnumerable<TEntity> GetAll();

    void Add(TEntity entity);
    void AddRange(IEnumerable<TEntity> entities);

    void Remove(TEntity entity);
    void RemoveRange(IEnumerable<TEntity> entities);
}

public interface ICustomerRepository : IRepository<Customer>
{
    IEnumerable<Customer> GetTopCustomers(int count);
}

internal class CustomerRepository : Repository<Customer>, ICustomerRepository
{
    private MyDatabaseContext MyDatabaseContext => Context as MyDatabaseContext;

    internal CustomerRepository(MyDatabaseContext context)
        : base(context)
    {
    }

    public IEnumerable<Customer> GetTopCustomers(int count)
    {
        return MyDatabaseContext.Customers.ToList();
    }
}

Here is my second option: Is it necessary?

public interface IUnitOfWork : IDisposable 
{
    int Complete();
}


public interface IMyDatabaseUnitOfWork : IUnitOfWork 
{
    int Complete();
    ICustomerRepository Customers { get; }
    IPhoneRepository Phones { get; }
    IAddressRepository Address { get; }
}

public class MyDatabaseUnitOfWork<TContext> : IMyDatabaseUnitOfWork
    where TContext : MyDatabaseContext, new()
{
    public ICustomerRepository Customers { get; }
    public IPhoneRepository Phones { get; }
    public IAddressRepository Address { get; }

    public MyDatabaseUnitOfWork()
    {
        context = new TContext();
        Customers = new CustomerRepository(context);
        Phones = new PhoneRepository(context);
        Address = new AddressRepository(context);
    } 

    public int Complete()
    {
        return context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

Solution

You should consider using loosely coupled code. IoC / DI are a great way to go. Example:

public class ImmutableUnitOfWork
{
    private readonly ICustomerRepository _customers;
    private readonly IPhoneRepository _phones;
    private readonly IAddressRepository _address;

    public ImmutableUnitOfWork(
        ICustomerRepository customers,
        IPhoneRepository phones,
        IAddressRepository address)
    {
        _customers = customers;
        _phones = phones;
        _address = address;
    } 
}

using StructureMap your dependency resolution should look something in the lines of:

        ObjectFactory.Initialize(x =>
        {
            x.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssemblyContainingType<ICustomerRepository>();  // Core
                scan.AssemblyContainingType<CustomerRepository>();   // Infrastructure
                scan.AssemblyContainingType<MyDatabaseContext>();    // Just in case 
            });
            x.For(typeof(IRepository<>)).Use(typeof(Repository<>));

            // WithDefaultConventions basically says if you name interfaces with
            // same name, but with "I" prefix you do not need following:
            x.For(typeof(IMyDatabaseContext)).Use(typeof(MyDatabaseContext));
            x.For(typeof(ICustomerRepository)).Use(typeof(CustomerRepository));
        });

but I understand if it is for learning purposes.


From code perspective :

ICustomerRepository is a service, not a repository. IRepository<TEntity> – is leaky, especially IEnumerable<TEntity> Get(Func<TEntity, bool> predicate);. If you feel that you really need it, try using specification pattern instead. This is just bad on many levels:

    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *