2
\$\begingroup\$

As a follow-up to this blog post and this question, I wanted to post my idea of replacing the IUnitOfWork with a factory pattern. It seems to me that in the case of this blog post the IUnitOfWork seems to be a type of sudo-factory pattern with each repository type and their accessors hardcoded. So my goal was to come up with a fully dynamic factory. I haven't had the chance to try this yet but wanted to post the idea to get some opinions.

IGenericRepository

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace DataAccess
{
    public interface IGenericRepository<T>
    {
        IQueryable<T> AsQueryable();

        IEnumerable<T> GetAll();
        IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
        T Single(Expression<Func<T, bool>> predicate);
        T SingleOrDefault(Expression<Func<T, bool>> predicate);
        T First(Expression<Func<T, bool>> predicate);
        T GetById(int id);

        void Add(T entity);
        void Delete(T entity);
        void Attach(T entity);
    }
}

EFGenericRepository

using System.Data.Entity;
using DataAccess;

namespace EFDataAccess
{
    public class EFGenericRepository<T> : IGenericRepository<T> where T : class
    {

        private readonly DbSet<T> _dbSet;

        public EFGenericRepository(DbSet<T> dbSet)
        {
            _dbSet = dbSet;
        }

        // Implement IGenericRepository Methods here...
    }
}

RepositoryFactory

using System;
using System.Data.Entity;
using DataAccess;

namespace EFDataAccess
{
    class RepositoryFactory
    {

        private readonly DbContext _context;

        public RepositoryFactory(DbContext context)
        {
            _context = context;
        } 

        public IGenericRepository<T> Get<T>() where T : Type
        {
            return new EFGenericRepository<T>(_context.Set<T>());
        }
    }
}

Edit: Created a github repo here

\$\endgroup\$
  • \$\begingroup\$ Can you say something of your goals and expectations? What is the problem that this approach will solve? I have the feeling that the internet is full of similar approached and equally many stories of why we shouldn't in the first place. \$\endgroup\$ – Thomas Eyde Oct 6 '15 at 11:51
4
\$\begingroup\$

Bad idea...?

Generally, I belong to the camp thinking generic repositories are a bad idea:

  • The abstraction will be leaky at best.
  • If you really need a repository per type, why not let the client new it up directly?
  • Except from basic CRUD, there is no generic functionality, so a generic interface will reduce your options.

The abstractions doesn't seem right. I know it follows the usual conventions and examples given on EF, but do you want the database to dictate the shape of your domain objects?

If you split the responsibilities between domain logic and persistence, it may be clearer what to do. A split like that will also open new possibilities on how to implement and optimize.

Here are some ideas:

Handle different responsibilities in different places

Given an imaginary customer and two use cases:

  1. Upgrade to gold customer.
  2. Get a list of gold customers.

Those are different responsibilities which can be expressed like:

public class Responsibilities
{
    public void UpdateCustomer()
    {
        var repository = new Repository();
        var customer = repository.GetCustomerById(42);

        customer.UpgradeToGold();

        repository.Save(customer);
    }

    public void QueryGoldCustomers()
    {
        var query = new CustomerQuery();
        var goldCustomers = query.GetGoldCustomers();
    }
}

Repository and state management

This is a naive implementation; it will only work with customers already stored in the database, plus it generates multiple calls to the database. But the implementation is separated from everything else, so we are free to optimize the database access when it starts to be a problem.

I haven't used EF in a while, so the Save method may be way off.

public class Repository
{
    readonly Database context = new Database();

    public Customer GetCustomerById(int id)
    {
        var state = context.CustomerState.Single(x => x.Id == id);
        var address = context.Addresses.Single(x => x.Id == state.AddressId);
        var snapshot = new Customer.Snapshot
            {
                State = state,
                Address = address
            };

        return new Customer(snapshot);
    }

    public void Save(Customer customer)
    {
        var provider = (IProvideSnapshot<Customer.Snapshot>) customer;
        var snapshot = provider.GetSnapshot();

        context.Addresses.Attach(snapshot.Address);
        context.CustomerState.Attach(snapshot.State);
        context.SaveChanges();
    }
}

Isolated queries

Same thing here; there are better ways to implement the query. Note that this query is only meaningful inside of the domain boundary, like when you want to send special offers to all gold customers.

UI views, reports and such would probably benefit from using dtos and not the domain object directly.

If it turns out to be hard to generate efficient queries with EF, we are free to select other tools, even inline SQL.

public class CustomerQuery
{
    readonly Database context = new Database();

    public IEnumerable<Customer> GetGoldCustomers()
    {
        var states = context.CustomerState.Where(state => state.IsGold);
        var addressIds = states.Select(state => state.AddressId);
        var addresses = context.Addresses.Where(address => addressIds.Contains(address.Id));

        return states
            .Select(state => new Customer.Snapshot
                {
                    State = state,
                    Address = addresses.Single(address => address.Id == state.AddressId)
                })
            .Select(snapshot => new Customer(snapshot))
            .ToList();
    }
}

Other classes for completeness

In this example, the shape of the domain class, the Customer, is not dictated by the database.

The shape of the Snapshot is, though. But that could be a good thing, because the object-to-database impedance mismatch is lower.

public class Customer : IProvideSnapshot<Customer.Snapshot>
{
    readonly Snapshot snapshot;

    public Customer(Snapshot snapshot)
    {
        this.snapshot = snapshot;
    }

    public void UpgradeToGold()
    {
        snapshot.State.IsGold = true;
    }

    Snapshot IProvideSnapshot<Snapshot>.GetSnapshot()
    {
        return snapshot;
    }

    public class Snapshot
    {
        public CustomerState State { get; set; }
        public Address Address { get; set; }
    }
}

public interface IProvideSnapshot<out T>
{
    T GetSnapshot();
}

public class Database : DbContext
{
    public DbSet<CustomerState> CustomerState { get; set; }
    public DbSet<Address> Addresses { get; set; }
}

public class CustomerState
{
    public int Id { get; set; }
    public int AddressId { get; set; }
    public bool IsGold { get; set; }
}

public class Address
{
    public int Id { get; set; }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.