ASP.NETMVC的最佳存储库模式是什么?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (78)

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

但现在我正在编写一些我自己的MVC应用程序。我并不完全了解我公司使用的存储库的方式和原因,我试图确定实现数据访问的最佳方法。

我正在使用C#和实体框架(包含所有最新版本)。

我看到了三种处理数据访问的一般方法。

  1. 每次访问数据时,使用语句中的常规数据库上下文。这很简单,它工作正常。但是,如果两个位置需要在一个请求中读取相同的数据,则必须读取两次数据。(每个请求使用一个存储库,两个地方都会使用同一个实例,并且我理解第二次读取会简单地从第一次读取返回数据。)
  2. 典型的存储库模式。由于我不明白的原因,这种典型的模式涉及到为数据库中使用的每个表创建一个包装类。这对我来说似乎是错误的。实际上,因为它们也作为接口来实现,所以我在技术上为每个表创建两个包装类。EF为我创建表格。我不相信这种方法是有道理的。
  3. 还有一个通用的存储库模式,其中创建单个存储库类来为所有实体对象提供服务。这对我来说更有意义。但是对别人有意义吗?链接是否是最好的方法?

我很想在这个主题上得到别人的意见。你是在编写自己的存储库,使用上述之一还是完全不同。请分享。

提问于
用户回答回答于

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

如果域较小或需要限制哪些实体允许查询域,我想#2或#3定义了实体特定的存储库接口,它们自己实现了一个通用存储库 - 这是合理的。然而,我发现为每个我想查询的实体编写一个接口和一个具体的实现是耗尽和不必要的。有什么好处public interface IFooRepository : IRepository<Foo>(同样,除非我需要将开发人员限制在一组允许的聚合根上)?

我只是定义我的通用仓库界面,AddRemoveGetGetDeferredCount,和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;
}

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

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
    Decimal MinimumBalance { get; set; }
}

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

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

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

var overdueAccounts = query.Execute();

也可以使用Specification模式IQuery来构建有意义的,用户输入驱动的查询,而不是具有包含数百万混淆属性的接口,但是假设没有发现自己的权利混淆规范模式。

难题的最后一部分是您的存储库需要执行特定的预存储和后置存储库操作。现在,可以非常轻松地为特定实体创建通用存储库的实现,然后覆盖相关方法并执行需要执行的操作,并更新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);
}

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

下面是一个软删除行为的例子(软删除意味着当有人要求删除一个实体时,我们只需将其标记为已删除,以便它不能再次返回,但实际上从未实际删除)。

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.
   }
}

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

用户回答回答于

我会建议数字1,有一些注意事项。数字2似乎是最常见的,但以我的经验来看,存储库只是结束了查询的一个凌乱的倾倒场。如果你使用通用的仓库(2),它只是一个围绕DBContext的简单包装,除非你打算改变ORM的(坏主意),否则有点没有意义。

但是当我直接访问DBContext时,我更喜欢使用管道和过滤器模式,以便可以重用常用逻辑

items = DBContext.Clients
    .ByPhoneNumber('1234%')
    .ByOrganisation(134);

ByPhoneNumber和by Organization只是扩展方法。

扫码关注云+社区

领取腾讯云代金券