封装ORM逻辑的存储库模式的替代方案?


24

我只需要切换一个ORM,这是一个相对艰巨的任务,因为查询逻辑到处都有泄漏。如果我不得不开发一个新的应用程序,我个人的喜好是封装所有查询逻辑(使用ORM)以对它进行将来的更改。存储库模式很难编码和维护,因此我想知道是否还有其他模式可以解决问题?

我可以预见到有关在实际需要时不增加额外复杂性,敏捷性等方面的帖子,但是我只对以更简单的方式解决类似问题的现有模式感兴趣。

我的第一个想法是拥有一个通用类型存储库,通过扩展方法,我可以根据需要向特定类型存储库类添加方法,但是对静态方法进行单元测试非常痛苦。IE浏览器:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
您发现编写和维护麻烦的存储库模式到底有什么用?
马修·弗林

1
那里还有其他选择。其中一种就是使用查询对象模式,尽管我没有使用过,但这似乎是一种不错的方法。如果正确使用存储库恕我直言是一个很好的模式,但不能全部使用并全部终止
dreza

Answers:


14

首先:通用存储库应被视为基类,而不是完整的实现。它应该可以帮助您熟悉通用的CRUD方法。但是您仍然必须实现查询方法:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

第二:

不要返回IQueryable。这是一个泄漏的抽象。尝试自己实现该接口,或者在不了解底层数据库提供程序的情况下使用它。例如,每个数据库提供程序都有其自己的API,用于热切加载实体。这就是为什么它是一个泄漏的抽象。

另类

或者,您可以使用查询(例如,由“命令/查询”分离模式定义的查询)。CQS在维基百科中有描述。

您基本上可以创建查询类,您可以调用它们:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

查询的优点在于,您可以对不同的查询使用不同的数据访问方法,而不会使代码混乱。例如,您可以在一个服务中使用Web服务,在另一个服务中使用hibernate,在另一个服务中使用ADO.NET。

如果您对.NET实施感兴趣,请阅读我的文章:http : //blog.gauffin.org/2012/10/griffin-decoupled-the-queries/


替代模式是否会建议您在大型项目上造成更多混乱,因为“存储库模式”中的方法现在已经成为一个完整的类了?
但丁

3
如果您对拥有小班级的定义比较混乱,那么可以。在那种情况下,您不应该尝试遵循SOLID原则,因为应用它们总是会产生更多(较小)的类。
jgauffin

2
较小的类更好,但是与浏览(域)存储库的智能感知来查看是否存在必要的功能(如果没有)相比,现有查询类的导航不会比说麻烦的多。
但丁

4
项目结构变得更加重要。如果将所有查询放在类似的名称空间中 YourProject.YourDomainModelName.Queries,将很容易导航。
jgauffin

我发现用CQS很难很好地完成的一件事是常见查询。例如,给定一个用户,一个查询将获取所有关联的学生(差异等级,自己的孩子等)。现在,另一个查询需要为用户获取子代,然后对其进行一些操作-因此查询2可以利用查询1中的通用逻辑,但是没有简单的方法可以做到这一点。我还没有找到任何可轻松实现此目的的CQS实现。存储库在这方面有很大帮助。不喜欢这两种方式,而只是分享我的看法。
Mrchief

8

首先,我想说的是,ORM在您的数据库上已经足够大了。一些ORM提供对所有常见关系数据库的绑定(NHibernate具有对MSSQL,Oracle,MySQL,Postgress等的绑定。)因此,在其之上构建新的抽象对我而言似乎并不有利。同样,总结一下,您需要“抽象化”此ORM是没有意义的。

如果您仍然想构建这种抽象,我将反对存储库模式。纯粹的SQL时代是伟大的,但是面对现代ORM却很麻烦。主要是因为您最终重新实现了大多数ORM功能,例如CRUD操作和查询。

如果要构建这样的抽象,我将使用这些规则/模式

  • CQRS
  • 尽可能使用现有的ORM功能
  • 只封装复杂的逻辑和查询
  • 尝试将ORM的“管道”隐藏在体系结构内

在具体实现中,我将直接使用ORM的CRUD操作,而无需进行任何包装。我还将直接在代码中进行简单查询。但是复杂的查询将封装在它们自己的对象中。为了“隐藏” ORM,我将尝试将数据上下文透明地注入到服务/ UI对象中,并对查询对象做同样的事情。

我要说的最后一件事是,许多人在不知道如何使用ORM以及如何从中获得最大“利润”的情况下使用ORM。人们推荐的存储库通常是这种类型的。

作为推荐读物,我想说Ayende的博客,尤其是本文


+1“最后我想说的是,许多人在使用ORM时却不知道如何使用它以及如何从中获得最大的“利润”。人们推荐的存储库通常是这种类型的”
Misters,2015年

1

泄漏实现的问题在于您需要许多不同的过滤条件。

如果您实现存储库方法FindByExample并像这样使用它,则可以缩小此api

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

考虑使扩展在IQueryable而不是IRepository上运行。

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

要进行单元测试:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

测试不需要花哨的模拟。您还可以根据需要组合这些扩展方法以创建更复杂的查询。


5
-1 a)IQueryable是坏母亲,或者说是上帝的接口。实现它非常困难,并且几乎没有(如果有的话)完整的LINQ to Sql提供程序。b)扩展方法使扩展(基本OOP原则之一)无法实现。
jgauffin

0

我在这里为NHibernate写了一个非常简洁的查询对象模式:https : //github.com/shaynevanasperen/NHibernate.Sessions.Operations

它通过使用如下接口来工作:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

给定这样的POCO实体类:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

您可以构建如下查询对象:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

然后像这样使用它们:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

1
尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。
Dan Pichelman 2015年

抱歉,我很着急。答案已更新以显示一些示例代码。
谢恩(Shayne)2015年

1
+1用于改善帖子的更多内容。
Songo
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.