假设我有2张桌子。 ProductCategory
和Product
。我有一个可以处理这两个表的通用存储库:
public class GenericRepository<T> : IRepository<T>
但是当使用工作单元模式时,我是否被迫为我的数据库中的所有表创建一个存储库?
public interface IUnitOfWork : IDisposable
{
int SaveChanges();
IRepository<ProductCategory> ProductCategoryRepository { get; }
IRepository<Product> ProductRepository { get; }
}
我是不是可以将通用存储库添加到工作单元中?
答案 0 :(得分:5)
您可以向IUnitOfWork接口添加通用方法:
public interface IUnitOfWork : IDisposable
{
int SaveChanges();
IRepository<T> Repository<T>();
}
但我不推荐它。它有点像服务定位器反模式和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 :(得分:1)
只展示一个类的解决方案
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();
}
}
我故意没有包含Repository类。让一个类封装每个实体的查询和命令是过度杀戮和不必要的抽象。它几乎是一个基本层面的设计缺陷。查询和命令是根本不同的问题。可以在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>
有不同的责任。
答案 2 :(得分:0)
有很多方法可以实现工作单元。我更喜欢让存储库在其构造函数中获取一个工作单元(通过依赖注入传递),然后您只为您的需要创建存储库。