如果“存储库模式”对于现代ORM(EF,nHibernate)过大,那么更好的抽象是什么?


12

我最近阅读了很多关于将存储库模式与功能强大的ORM(例如Entity Framework)一起使用的争论,因为它结合了类似存储库的功能以及工作单元功能。

另一个反对在单元测试之类的情况下使用该模式的论点是,存储库模式是一种泄漏抽象,因为更通用的实现利用了IQueryable。

反对使用存储库模式的论点对我来说很有意义,但是建议的替代抽象方法通常更令人困惑,并且看起来像问题一样矫kill过正。

吉米·鲍嘉(Jimmy Bogards)的解决方案似乎既要吹散抽象,又要介绍自己的体系结构。 https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

另一个不必要的存储库示例....但是使用我的体系结构! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

另一个... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

我还没有找到一种明显的替代或替代方法,而该替代方法本身并不具有更多的体系结构,因此可以替代“过于复杂”的存储库模式。


4
您到底要达到什么目标?抽象应该有目的。如果您正在编写CRUD应用程序,那么ORM可能足够抽象。
JacquesB '16

@JacquesB我正在尝试通过健壮的域模型来避免对象关系阻抗问题,但是还要在mvc实现中将其从我的视图模型中抽象出来。
AnotherDeveloper

里德·科斯佩(Reed Cospey)在这里对IQuerable有很多积极的话要说:stackoverflow.com/questions/1578778/using-iqueryable-with-linq 这意味着在传输层会更好。就抽象而言,当我需要注入EntityType但仍想维护通用方法时,我发现通用存储库模式的使用效果很好。另一方面,我本人在MSDN LINQ论坛上认为EF是一种存储库模式,因为它全部在内存中。一个项目使用大量的Where子句作为方法调用,效果很好。
约翰·彼得斯

但是我研究但被拒绝的一个领域是表达式树业务,有些真的很喜欢它,但是.....在发现有用之前,必须认真研究。
约翰·彼得斯

2
我们应该回过头来从控制器调用SQL
bobek

Answers:


12

我认为您正在混合存储库和通用存储库。

一个基本的存储库只是连接您的数据存储并提供返回数据的方法

IRepository {
   List<Data> GetDataById(string id);
}

它不会通过IQueryable或其他传递随机查询的方式将数据层泄漏到您的代码中,并且提供了定义良好的可测试和可注入的方法表面。

通用存储库使您可以像ORM一样传递查询

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

我同意在ORM(基本上只是另一个通用存储库)之上使用通用存储库没有多大意义。

答案是使用基本的存储库模式来隐藏您的ORM


1
ORM的ORM?我试图变得有趣。为什么需要抽象ORM?
约翰尼

1
同样的原因,您提取任何东西。避免使用专有类污染您的代码
Ewan

6

您提到的大多数参数错误地归因于它没有的存储库模式功能。

从概念上讲,最初在DDD中定义的存储库只是可以搜索或添加到的对象的集合。它背后的持久性机制被抽象出来,因此,作为消费者,您会幻想它是内存中的集合。

具有泄漏抽象(IQueryables例如,公开)的存储库实现是较差的存储库实现。

不仅仅公开收集操作(例如,工作单元功能)的存储库实现是较差的存储库实现。

是否有替代存储库的数据访问方式?是的,但是它们与您在问题中提出的问题无关。



1

对我而言,存储库与ORM或其他DB持久性层结合在一起具有以下缺点:

  1. 掩盖工作单位。UoW必须由程序员进行编码,并且很少能在后台实现,就像用户在不定义UoW边界以及可能的提交点的情况下简单地进行查询和修改一样。有时,通过在每种存储库访问方法中将其简化为微型UoW(例如NHibernate会话)而放弃了UoW。
  2. 掩盖,或者在最坏的情况下,破坏持久性无知:诸如“ Load()”,“ Get()”,“ Save()”或“ Update()”之类的方法建议立即执行单个对象操作,就像发送单个对象一样SQL / DML,或者就像处理文件一样。实际上,例如,具有这些具有误导性的名称的NHibernate方法通常不进行单独访问,而是排队进行延迟加载或插入/更新批处理(Persistence Ignorance)。有时,程序员想知道为什么他们不立即执行数据库操作,而强行破坏了对持久性的无知,从而导致性能下降,并花了很大的力气使系统(很多!)变得更糟。
  3. 不受控制的增长。一个简单的存储库可能会积累越来越多的方法来满足特定需求。

如:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4.上帝的危险对象:您可能很想创建一个上帝类,覆盖所有模型或数据访问层。存储库类不仅包含Car方法,而且还包含所有实体的方法。

我认为,最好至少提供一些查询机会,以避免许多单一目的方法造成的混乱。无论是LINQ,自己的查询语言,还是直接取自ORM的东西(好的,都是耦合问题……)。


4
如果不使用存储库模式,所有这些方法将流向何方?
约翰尼


1

如果存储库接口的目的是为了嘲笑离开数据库中的一个单元测试(=隔离测试)最好的抽象的东西,很容易嘲笑。

难以模拟基于IQueryable结果的存储库接口。

从单元测试的角度来看

IRepository {
   List<Data> GetDataById(string id);
}

可以很容易地嘲笑

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

仅当模拟忽略查询参数的内容时,才可以轻松模拟。

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

不能轻易嘲笑


0

如果您使用lambda函数进行查询,则我认为存储库模式并不过分。特别是当您必须抽象ORM(我认为总是应该如此)时,我将不在乎存储库本身的实现细节。

例如:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.