Несколько универсальных репозиториев в unitofwork? - PullRequest
2 голосов
/ 21 марта 2012

Допустим, у меня есть 2 таблицы. ProductCategory и Product. У меня есть 1 общий репозиторий, который может обрабатывать обе таблицы:

public class GenericRepository<T> : IRepository<T>

Но при использовании шаблона единицы работы я вынужден создавать репозиторий для ВСЕХ таблиц в моей базе данных?

public interface IUnitOfWork : IDisposable
{
    int SaveChanges();

    IRepository<ProductCategory> ProductCategoryRepository { get; }
    IRepository<Product> ProductRepository { get; }
}

Нет ли способа добавить общий репозиторий в класс единицы работы?

Ответы [ 3 ]

5 голосов
/ 21 марта 2012

Вы можете добавить универсальный метод в интерфейс IUnitOfWork:

public interface IUnitOfWork : IDisposable
{
    int SaveChanges();

    IRepository<T> Repository<T>();
}

Но я не рекомендую это. Это пахнет как анти-паттерн Service Locator и нарушение SRP. Лучше всего удалить все хранилища из интерфейса IUnitOfWork, поскольку предоставление доступа к хранилищу не входит в обязанности UnitOfWork. Я рекомендую отделить репозиторий от UnitOfWork и внедрить их в пользователя самостоятельно.

public class Consumer
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IRepository<Product> _products;

    public Consumer(IUnitOfWork unitOfWork, IRepository<Product> products)
    {
        _unitOfWork = unitOfWork;
        _products = products;
    }

    public void Action()
    {
        var product = _products.GetOne();

        product.Name = "new name";
        _products.Update(product);

        _unitOfWork.SaveChanges();
    }
}

UDATE:

UnitOfWork и Repository могут совместно использовать экземпляр контекста. Вот образец кода:

public class EfUnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;

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

    public void SaveChanges()
    {
        _context.SaveChanges();
    }
}

public class EfRepository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;

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

    //... repository methods...
}

public class Program
{
    public static void Main()
    {
        //poor man's dependency injection
        var connectionString = "northwind";

        var context = new DbContext(connectionString);
        var unitOfWork = new EfUnitOfWork(context);
        var repository = new EfRepository<Product>(context);
        var consumer = new Consumer(unitOfWork, repository);
        consumer.Action();
    }
}
1 голос
/ 27 марта 2012

Демонстрация решения только с одним классом будет

public class Session : ISession
{
    private readonly DbContext _dbContext;
    public Session(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> expression) where TEntity : class
    {
        return _dbContext.Set<TEntity>().SingleOrDefault(expression);
    }

    public IQueryable<TEntity> Query<TEntity>() where TEntity : class
    {
        return _dbContext.Set<TEntity>().AsQueryable();
    }

    public void Commit()
    {
        try { _dbContext.SaveChanges(); }
        catch (DbEntityValidationException ex)
        {
            var m = ex.ToFriendlyMessage();
            throw new DbEntityValidationException(m);
        }
    }

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

    public void Add<TEntity>(IEnumerable<TEntity> items) where TEntity : class
    {
        items.ToList().ForEach(Add);
    }

    public void Add<TEntity>(TEntity item) where TEntity : class
    {
        _dbContext.Set<TEntity>().Add(item);
    }

    public void Remove<TEntity>(TEntity item) where TEntity : class
    {
        _dbContext.Set<TEntity>().Remove(item);
    }

    public void Remove<TEntity>(Expression<Func<TEntity, bool>> expression) where TEntity : class
    {
        var items = Query<TEntity>().Where(expression);
        Remove<TEntity>(items);
    }

    public void Remove<TEntity>(IEnumerable<TEntity> items) where TEntity : class
    {
        items.ToList().ForEach(Remove);
    }
}

, и тогда вы сможете использовать

public class User
{
    public int? Id { get; set; }
    public string Name { get; set; }
    public DateTime Dob { get; set; }
}
public class Usage
{
    private readonly ISession _session;
    public Usage(ISession session) { _session = session; }

    public void Create(User user)
    {
        _session.Add(user);
        _session.Commit();
    }
    public void Update(User user)
    {
        var existing = _session.Single<User>(x => x.Id == user.Id);

        // this gets cumbursome for an entity with many properties. 
        // I would use some thing like valueinjecter (nuget package)
        // to inject the existing customer values into the one retreived from the Db.
        existing.Name = user.Name;
        existing.Dob = user.Dob;

        _session.Commit();
    }
}

Я специально не включил класс репозитория.Наличие класса, инкапсулирующего как запросы, так и команды для каждой сущности, является чрезмерным уничтожением и ненужной абстракцией.Это почти недостаток дизайна на фундаментальном уровне.Запросы и команды - это принципиально разные проблемы.Самые простые запросы можно создавать как методы расширений в интерфейсе ISession.Команды могут быть выполнены с использованием нескольких классов, таких как:

public interface ICommand<in TSource>
{
    void ApplyTo(TSource source);
}
public interface ICommandHandler<out TSource>
{
    void Handle(ICommand<TSource> command);
}
public class LinqCommandHandler : ICommandHandler<IStore>
{
    private readonly ISession _session;

    public LinqCommandHandler(ISession session)
    {
        _session = session;
    }
    public void Handle(ICommand<IStore> command)
    {
        command.ApplyTo(_session);
        _session.Commit();
    }
}
public class UpdateDobForUserName : ICommand<IStore>
{
    public string UserName { get; set; }
    public DateTime Dob { get; set; }
    public void OnSend(IStore store)
    {
        var existing = store.Query<User>().SingleOrDefault(x => x.Name == UserName);
        existing.Dob = Dob;
    }
}

public class Usage
{
    private readonly ICommandHandler<IStore> _commandHandler;

    public Usage(ICommandHandler<IStore> commandHandler)
    {
        _commandHandler = commandHandler;
    }

    public void Update()
    {
        var command = new UpdateDobForUserName {UserName = "mary", Dob = new DateTime(1960, 10, 2)};
        _commandHandler.Handle(command);
    }
}

Выше IStore такой же, как класс Session, за исключением того, что он не реализует интерфейс IDisposable иУ меня нет Commit() метода.ISession тогда явно наследуется от IStore, а также реализует IDisposable и имеет один метод Commit().Это гарантирует, что ICommand<IStore> никогда не сможет открывать или удалять соединения и не сможет зафиксировать.Его обязанность - определить команду и определить, как она применяется.Кто применяет это и что происходит, а что нет в командном приложении - это другая ответственность, которая связана с ICommandHandler<IStore>.

0 голосов
/ 21 марта 2012

Существует множество способов реализации Единицы работы.Я предпочитаю, чтобы репозитории брали Unit of Work в своем конструкторе (который передается через Inpendency Injection), тогда вы создаете репозитории только для своих нужд.

...