ASP.NET MVC的最佳存储库模式

Jon*_*ood 65 c# asp.net-mvc repository repository-pattern

我最近学习了ASP.NET MVC(我喜欢它).我正在与一家使用依赖注入的公司合作,在每个请求中加载Repository实例,我熟悉使用该存储库.

但现在我正在编写自己的几个MVC应用程序.我不完全理解我公司使用的存储库的方法和原因,我正在尝试确定实现数据访问的最佳方法.

我正在使用C#和Entity Framework(包含所有最新版本).

我看到了处理数据访问的三种通用方法.

  1. 每次访问数据时,using语句中的常规DB上下文.这很简单,工作正常.但是,如果两个位置需要在一个请求中读取相同的数据,则必须读取两次数据.(每个请求使用一个存储库,两个地方都会使用相同的实例,我理解第二次读取只会返回第一次读取的数据.)

  2. 典型的存储库模式.由于我不理解的原因,这种典型的模式涉及为数据库中使用的每个表创建一个包装类.这对我来说似乎不对.事实上,由于它们也是作为接口实现的,我在技术上会为每个表创建两个包装类.EF为我创建表格.我不相信这种方法是有道理的.

  3. 还有一个通用存储库模式,其中创建单个存储库类以服务所有实体对象.这对我来说更有意义.但对别人有意义吗?链接是最好的方法吗?

我很想从其他人那里得到关于这个话题的一些意见.您是在编写自己的存储库,使用上述方法之一,还是完全不同的方式.请分享.

Hac*_*ese 36

我使用了#2和#3的混合,但如果可能的话,我更喜欢严格的通用存储库(比#3的链接更严格).#1不好,因为它在单元测试中表现不佳.

如果您有一个较小的域或需要限制您的域允许查询的实体,我认为#2-或#3定义了自己实现通用存储库的实体特定存储库接口 - 这是有意义的.但是,我发现为我想要查询的每个实体编写一个接口和一个具体的实现是很费劲的.有什么好处public interface IFooRepository : IRepository<Foo>(再次,除非我需要将开发人员限制在一组允许的聚合根)?

我只是定义我的通用仓库界面,Add,Remove,Get,GetDeferred,Count,和Find方法(FIND返回的IQueryable界面,允许LINQ),创建一个具体的通用实现,而收工.我严重依赖FindLINQ.如果我需要多次使用特定查询,我使用扩展方法并使用LINQ编写查询.

这涵盖了我95%的持久性需求.如果我需要执行某种通常无法完成的持久性操作,我会使用自行开发的ICommandAPI.例如,假设我正在使用NHibernate,我需要执行复杂的查询作为我的域的一部分,或者我可能需要执行批量命令.API看起来大致如下:

// marker interface, mainly used as a generic constraint
public interface ICommand
{
}

// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
   void Execute();
}

// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
   TResult Execute();
}

// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
    int Count();
}

// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
   TCommand Create<TCommand>() where TCommand : ICommand;
}
Run Code Online (Sandbox Code Playgroud)

现在我可以创建一个表示特定命令的接口.

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
    Decimal MinimumBalance { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我可以创建一个具体的实现并使用原始SQL,NHibernate HQL,等等,并将其注册到我的服务定位器.

现在在我的业务逻辑中,我可以做这样的事情:

var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;

var overdueAccounts = query.Execute();
Run Code Online (Sandbox Code Playgroud)

您还可以使用规范模式IQuery来构建有意义的,用户输入驱动的查询,而不是具有百万个令人困惑的属性的接口,但这假设您没有发现规范模式本身令人困惑;).

最后一个难题是当您的存储库需要执行特定的pre-and-post存储库操作时.现在,您可以非常轻松地为特定实体创建通用存储库的实现,然后覆盖相关方法并执行您需要执行的操作,并更新您的IoC或服务定位器注册并完成它.

但是,有时这种逻辑是交叉的,并且通过覆盖存储库方法很难实现.所以我创建了IRepositoryBehavior,它基本上是一个事件接收器.(下面只是我脑海中的粗略定义)

public interface IRepositoryBehavior
{
    void OnAdding(CancellableBehaviorContext context);
    void OnAdd(BehaviorContext context);

    void OnGetting(CancellableBehaviorContext context);
    void OnGet(BehaviorContext context);

    void OnRemoving(CancellableBehaviorContext context);
    void OnRemove(BehaviorContext context);

    void OnFinding(CancellableBehaviorContext context);
    void OnFind(BehaviorContext context);

    bool AppliesToEntityType(Type entityType);
}
Run Code Online (Sandbox Code Playgroud)

现在,这些行为可以是任何东西.审计,安全检查,软删除,强制执行域约束,验证等.我创建一个行为,将其注册到IoC或服务定位器,并修改我的通用存储库以接收已注册的IRepositoryBehaviors 的集合,并检查每个行为当前存储库类型并将操作包装在每个适用行为的前/后处理程序中.

这是一个示例软删除行为(软删除意味着当有人要求删除实体时,我们只是将其标记为已删除,因此无法再次返回,但实际上从未实际删除过).

public SoftDeleteBehavior : IRepositoryBehavior
{
   // omitted

   public bool AppliesToEntityType(Type entityType)
   {
       // check to see if type supports soft deleting
       return true;
   }

   public void OnRemoving(CancellableBehaviorContext context)
   {
        var entity = context.Entity as ISoftDeletable;
        entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated

        context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
   }
}
Run Code Online (Sandbox Code Playgroud)

是的,这基本上是NHibernate事件监听器的简化和抽象实现,但这就是我喜欢它的原因.A)我可以在不将NHibernate带入图片的情况下对行为进行单元测试B)我可以在NHibernate之外使用这些行为(比如存储库是包装REST服务调用的客户端实现)C)NH的事件监听器可能是一个真正的痛苦屁股;)


Cra*_*aig 12

我会建议1号,但有一些警告.2号似乎是最常见的,但根据我的经验,存储库最终会成为查询的混乱倾倒场.如果你使用通用存储库(2),它只是围绕DBContext的一个薄包装,除非你打算改变ORM(糟糕的主意),否则真的有点无意义.

但是当我直接访问DBContext时,我更喜欢使用管道和过滤器模式,因此您可以重用常见的逻辑,例如

items = DBContext.Clients
    .ByPhoneNumber('1234%')
    .ByOrganisation(134);
Run Code Online (Sandbox Code Playgroud)

ByPhoneNumber和By Organization只是扩展方法.

  • @Johnathan:使用依赖注入,这样任何需要DBContext的东西都会在每个请求的生命周期内接收相同的上下文. (4认同)