33

我有封装对域模型的数据访问的存储库(例如 ContactRepository、UserRepository 等)。

当我在寻找数据时,例如

  • 查找名字以 XYZ 开头的联系人
  • 生日在 1960 年之后的联系人

    (ETC),

我开始实现存储库方法,例如FirstNameStartsWith(string prefix)YoungerThanBirthYear(int year),基本上遵循了许多示例。

然后我遇到了一个问题——如果我必须合并多个搜索怎么办?我的每个存储库搜索方法,例如上面的,只返回一组有限的实际域对象。为了寻找更好的方法,我开始在 IQueryable<T> 上编写扩展方法,例如:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

现在我可以做一些事情,比如

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

然而,我发现自己在编写扩展方法(并且到处发明了诸如ContactsQueryableExtensions之类的疯狂类,并且通过将所有内容都放在适当的存储库中,我失去了“漂亮的分组”。

这真的是这样做的方法,还是有更好的方法来实现相同的目标?

4

4 回答 4

12

在开始我现在的工作之后,我最近一直在思考这个问题。我习惯了存储库,他们按照您的建议仅使用裸骨存储库进入完整的 IQueryable 路径。

我觉得 repo 模式是合理的,并且在描述您希望如何处理应用程序域中的数据方面做了半有效的工作。但是,您所描述的问题肯定会发生。它变得凌乱,快速,超出了简单的应用程序。

也许,有没有办法重新思考你为什么要以如此多的方式要求数据?如果不是,我真的觉得混合方法是最好的方法。为您重用的东西创建 repo 方法。实际上它有意义的东西。干燥等等。但是那些一次性?为什么不利用 IQueryable 和你可以用它做的性感事情呢?正如您所说,为此创建一种方法很愚蠢,但这并不意味着您不需要数据。DRY 并不真正适用,不是吗?

做好这件事需要纪律,但我真的认为这是一条合适的道路。

于 2009-09-13T01:20:43.083 回答
8

@Alex - 我知道这是一个老问题,但我要做的就是让存储库只做非常简单的事情。这意味着,获取表或视图的所有记录。

然后,在服务层(您使用的是 n 层解决方案,对吗?:))我将在那里处理所有“特殊”查询内容。

好的,示例时间。

存储层

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

很好很简单。SqlContext是你的EF Context.. 的实例,它上面有一个Entity名为Contacts.. 的实例,它基本上是你的 sql Contacts 类。

这意味着,该方法基本上是在做:SELECT * FROM CONTACTS......但它并没有用该查询访问数据库......它现在只是一个查询。

好的.. 下一层.. KICK ... 我们走吧(《盗梦空间》有人吗?)

服务层

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();
   
   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

完毕。

所以让我们回顾一下。首先,我们从一个简单的“从联系人获取所有内容”查询开始。现在,如果我们提供了姓名,让我们添加一个过滤器来按姓名过滤所有联系人。接下来,如果我们提供了年份,那么我们按年份过滤生日。等等。最后,我们点击数据库(使用这个修改后的查询),看看我们得到了什么结果。

笔记:-

  • 为简单起见,我省略了任何依赖注入。这不仅仅是强烈推荐。
  • 这都是伪代码。未经测试(针对编译器),但你明白了....

外卖点

  • 服务层处理所有智能。这就是您决定需要哪些数据的地方。
  • 存储库是一个简单的 SELECT * FROM TABLE 或一个简单的 INSERT/UPDATE 到 TABLE。

祝你好运 :)

于 2010-08-02T01:25:18.117 回答
5

我意识到这已经过时了,但我最近一直在处理同样的问题,我得出了与 Chad 相同的结论:通过一点纪律,扩展方法和存储库方法的混合似乎效果最好。

我在(实体框架)应用程序中一直遵循的一些一般规则:

订购查询

如果该方法仅用于订购,我更喜欢编写在IQueryable<T>IOrderedQueryable<T>(利用底层提供者)上操作的扩展方法。例如

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

现在我可以ThenByStudentName()在我的存储库类中根据需要使用。

返回单个实例的查询

如果该方法涉及通过原始参数进行查询,则通常需要一个ObjectContext并且不容易制作static。这些方法我留在我的存储库中,例如

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

但是,如果该方法涉及EntityObject使用其导航属性查询一个,它通常可以static很容易地实现,并作为扩展方法实现。例如

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

现在我可以方便地编写someStudent.GetLatestRegistration()而不需要当前范围内的存储库实例。

查询返回集合

如果该方法返回 some IEnumerable, ICollectionor IList,那么如果可能的话,我希望将其static保留在存储库中,即使它使用导航属性也是如此。 例如

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

这是因为我的GetAll()方法已经存在于存储库中,它有助于避免混乱的扩展方法。

不将这些“集合获取器”实现为扩展方法的另一个原因是它们需要更详细的命名才能有意义,因为没有隐含返回类型。例如,最后一个示例将变为GetTermRegistrationsByTerm(this Term term).

我希望这有帮助!

于 2012-02-14T21:06:42.950 回答
0

六年后,我确信@Alex 已经解决了他的问题,但在阅读了接受的答案后,我想加两分钱。

在存储库中扩展IQueryable集合以提供灵活性并授权其消费者自定义数据检索的一般目的。亚历克斯已经做了很好的工作。

服务层的主要作用是坚持关注点分离原则和处理与业务功能相关的命令逻辑。

在现实世界的应用程序中,查询逻辑通常不需要扩展存储库本身提供的检索机制(例如值更改、类型转换)。

考虑以下两种情况:

IQueryable<Vehicle> Vehicles { get; }

// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
    return query.Where(v => v.OwnerId == ownerId);
}

// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
    return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}

这两种方法都是简单的查询扩展,但无论多么微妙,它们都有不同的作用。第一个是一个简单的过滤器,而第二个意味着业务需求(例如维护或计费)。在一个简单的应用程序中,可能会在存储库中同时实现它们。在更理想化的系统UsedThisYear中最适合服务层(甚至可以实现为普通实例方法),它还可以更好地促进分离命令查询的CQRS策略。

主要考虑因素是 (a) 存储库的主要目的和 (b) 您希望在多大程度上坚持CQRSDDD理念。

于 2016-03-10T19:44:38.150 回答