存储库方法与扩展IQueryable

时间:2009-09-13 01:13:21

标签: c# .net extension-methods repository-pattern iqueryable

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

当我查看搜索数据时,例如

  • 找到一个名字的联系人 从XYZ开始
  • 生日之后的联系人 1960年

    (等),

我开始实现存储库方法,例如 FirstNameStartsWith(字符串前缀) 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 个答案:

答案 0 :(得分:12)

在我目前的工作开始后,我最近一直在考虑这个问题。我已经习惯了Repositories,他们会像你建议的那样只使用裸骨库来完整的IQueryable路径。

我觉得回购模式是合理的,并且在描述您希望如何使用应用程序域中的数据方面做了一个半有效的工作。但是,您所描述的问题肯定会发生。除了简单的应用程序之外,它会变得混乱,快速。

或许有可能重新思考为什么要以这么多方式询问数据?如果没有,我真的觉得混合方法是最好的方法。为您重复使用的东西创建repo方法。这实际上是有道理的东西。干,等等。但那些一次性的?为什么不利用IQueryable和你可以用它做的性感的东西呢?正如你所说,愚蠢的是为它创建一个方法,但这并不意味着你不需要数据。 DRY真的不适用吗?

要做到这一点需要纪律,但我认为这是一条合适的道路。

答案 1 :(得分:6)

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

然后,在SERVICES层(你正在使用一个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 ......我们去( Inception 任何人?)

服务层

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();
}

完成。

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

注意: -

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

外卖点

  • 服务层处理所有智能。这就是您决定所需数据的地方。
  • Repository是一个简单的SELECT * FROM TABLE或一个简单的INSERT / UPDATE到TABLE。
祝你好运:)

答案 2 :(得分:4)

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

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

订购查询

如果该方法仅用于排序,我更喜欢编写在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);
}

但是,如果该方法涉及使用navigation properties查询EntityObject,则通常可以非常轻松地将其设为static,并将其作为扩展方法实施。 e.g。

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

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

查询返回集合

如果该方法返回一些IEnumerableICollectionIList,那么我希望尽可能将其设为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)

我希望这有帮助!

答案 3 :(得分:0)

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

存储库中扩展IQueryable个集合的一般目的是为了提供灵活性并使其消费者能够自定义数据检索。亚历克斯已经做了很多好事。

服务层的主要作用是遵守与业务功能相关的关注点分离原则和地址命令逻辑。 / p>

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

考虑以下两种情况:

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)您希望遵守 CQRS DDD 哲学的程度。