我正在尝试为工作单元和存储库模式设计一个定义良好但简单的界面。我的UoW暴露给服务和服务,然后“获取需要查询的存储库”。我知道将IQueryable<T>返回存储库是一场宗教战争。因为存储库仅公开给服务,所以所有查询都在服务内部执行,因此我可以测试查询。我需要为这些接口进行任何更改吗?感谢所有批评!!

public interface IUnitOfWork : IDisposable
{
    bool IsActive { get; }

    bool WasCommitted { get; }

    /// <summary>
    /// Commits all changes made on the unit of work.
    /// </summary>
    void Commit();

    bool WasRolledBack { get; }

    /// <summary>
    /// Rolls back all changes made on the unit of work.
    /// </summary>
    void Rollback();

    /// <summary>
    /// Returns an instance of an entity with the specified key that is attached to the unit of work without
    /// loading the entity from a repository.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    T Load<T>(int id)
        where T : class;

    void Attach<T>(T entity)
        where T : class, IIdentifiable;

    void Detach<T>(T entity)
        where T : class;

    IRepository<T> GetRepository<T>()
        where T : class;
}

public interface IRepository<T>
    where T : class
{

    IUnitOfWork UnitOfWork { get; }

    void Add(T entity);

    void Remove(T entity);

    /// <summary>
    /// Returns an instance of an entity with the specified key that is attached to the unit of work by loading
    /// the entity from the repository.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    T Get(int id);

    IQueryable<T> All();
}


#1 楼

我认为您的接口大部分都是定义明确的,但有一些例外:

我认为Load,Attach和Detach不应该是IUnitOfWork的成员,而应该是IRepository。看来,管理T类型对象的存储库将是放置对该类型起作用的方法的最佳位置。

IRepository上的UnitOfWork属性不属于该类型。不允许修改一个存储库的东西去让其他存储库自行修改。如果他们需要它们,则将它们明确传递。否则,您会向潜在的调用者隐瞒一个事实,即您的实现实际上不仅依赖于一个存储库,而且像让您感到羞愧一样隐藏您的依赖关系是我最不喜欢的代码味道之一。

评论


\ $ \ begingroup \ $
关于UoW属性的要点是,它完全没有用,我忘了我仍然在那里。 Attach和Detach之所以在UoW上,是因为您本身不是在使用存储库:它说“确定停止跟踪更改”和“确定重新跟踪更改”。另一方面,我现在不太确定。您对此还有其他子弹吗?我可以看到将其移动到存储库中是合理的(它正在与对象一起工作-但不会加载其图)。
\ $ \ endgroup \ $
–TheCloudlessSky
2011年1月29日14:21

\ $ \ begingroup \ $
另外-您如何看待WasCommitted等。您认为有必要吗?
\ $ \ endgroup \ $
–TheCloudlessSky
2011年1月29日14:22

#2 楼

我将按照以下方式进行调整:

public interface IEntity
{
    GUID Id { get; }
}

public interface IUnitOfWork<TEntity> where TEntity : IEntity, class 
    : IDisposable
{
    void Commit();
    void Discard();
    void Track(TEntity entity);
    void Delete(TEntity entity);
}

public interface IRepository<TEntity> where TEntity : IEntity, class 
    : IDisposable
{
    IUnitOfWork<TEntity> UnitOfWork { get; }

    TEntity CreateNew();
    T FindOne(ISpecification criteria);
    IEnumerable<T> FindMandy(ISpecification criteria);
    IEnumerable<T> FetchAll();
}

public interface ISpecification
{
    // encapsulates everything what is needed for a query 
    // this might be an SQL-statement, LINQ-functor... 

    string CommandText { get; }
    object[] Arguments { get; set; }
    bool IsSatisfiedBy(object candidate);
}


存储库本身是只读的,UnitOfWork是只写的。我看不到这种设计如何打破Demeter定律。

#3 楼

您的界面违反了Demeter定律。或简单地说,如果我不了解IUnitOfWork UnitOfWork { get; },就不能在IRespository上使用IUnitOfWork。 IUnitOfWork,是真的需要UnitOfWork吗。

您是如何创建界面的?

这是您应该执行TDD的地方。首先编写有关您希望代码执行的测试。然后应创建您的界面以完成测试。

例如,如果您调用方法Rollback();并且使用WasRollbacked属性无法编写测试,则它们可能不需要该属性。

不要放入过多的内容到任何界面。它们的界面变得杂乱无章。我建议阅读罗伯特·马丁(Robert Martin)的“清洁代码”,以更深入地了解这些思想。

评论


\ $ \ begingroup \ $
我读了干净的代码。我不确定这怎么讲。存储库模式严格负责成为数据层的接口。将IUnitOfWork公开为资源库中的属性可能没有任何意义,但我只是将其留在了那里。将这些方法放在存储库接口上根本没有意义。
\ $ \ endgroup \ $
–TheCloudlessSky
2011年1月27日18:48

#4 楼

我不记得确切的实现位置,但很多是从Spring框架中借用的。

IUnitOfWork接口公开的仅有的三种方法是Initialize(),Commit( )和Rollback()。然后,我继承自一个INhibernateUnitOfWork,它也只是通过一个属性公开了NHibernate会话...我想这部分可能取决于您用于持久性存储机制的内容...

但是,然后允许我在存储库类中执行的操作只是将工作单元作为可注入的依赖项。本质上,我的Get(id),Load(id),Find(),Save(),Update()等方法中的每一个都只对Repository.UnitOfWork.Session属性进行了调用。

从本质上讲,它实际上与您已经实现的内容非常接近。。。不确定为什么要使用GetRepository方法,但是您可能会有一些用例,而我没有。对我而言,我的UnitOfWork是请求生命周期对象,其初始化()和Commit()/ Rollback()功能由挂接到BeginRequest和EndRequest事件的HTTP模块处理。就是说,UnitOfWork实例仅被所述HTTP模块以及仅使用它来获取对当前Session对象的引用的存储库所触及。