Multiple generic repositories in unitofwork?

后端 未结 3 1318
南笙
南笙 2021-01-07 05:38

Lets say I have 2 tables. ProductCategory and Product. I have 1 generic repository that can handle both tables:

public class Generi         


        
相关标签:
3条回答
  • 2021-01-07 05:52

    There are many ways to implement Unit of work. I prefer having the repositories take a Unit of Work in its constructor (which is passed via Dependency Injection), then you only create repositories for your needs.

    0 讨论(0)
  • 2021-01-07 05:57

    Demonstrating a solution with only one class would be

    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);
        }
    }
    

    and then your usage can be

    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();
        }
    }
    

    I have deliberately not included a Repository class. To have a class encapsulate both queries and commands for every entity is an over kill and a needless abstraction. Its almost a design flaw at a fundamental level. Queries and commands are fundamentally different concerns. Queries in the most simplest manner can be created as extensions methods on the ISession interface. Commands can be done using a few classes like such..

    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);
        }
    }
    

    The IStore above is the same as the Session class, except that it doesn't implement the IDisposable interface and doesn't have a Commit() method. The ISession then obviously inherits from an IStore and also implements IDisposable and has one method Commit(). This ensures an ICommand<IStore> can never open or dispose connections and cannot commit. Its responsibility is to define a command and define how its applied. Who applies it and what happens and what not on command application is a different responsibility which is with the ICommandHandler<IStore>.

    0 讨论(0)
  • 2021-01-07 06:08

    You can add a generic method to the IUnitOfWork interface:

    public interface IUnitOfWork : IDisposable
    {
        int SaveChanges();
    
        IRepository<T> Repository<T>();
    }
    

    But i don't recommend it. It's smells like Service Locator anti-pattern and SRP violation. Better way is to remove all repositories from the IUnitOfWork interface, because providing access to repository is not UnitOfWork's responsibility. I recommend to separate repository from UnitOfWork and inject their into the consumer by itself.

    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 and Repository can share context instance. Here the sample of code:

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题