C# 存储库方法与扩展IQueryable

C# 存储库方法与扩展IQueryable,c#,.net,extension-methods,repository-pattern,iqueryable,C#,.net,Extension Methods,Repository Pattern,Iqueryable,我有存储库(如ContactRepository、UserRepository等),它们封装了对域模型的数据访问 当我在寻找数据时,例如 正在查找名为的联系人 以XYZ开头 生日过后的联系人 1960年 (等) 我开始实现存储库方法,例如第一个名称开始使用(字符串前缀)和年轻人出生年份(整数年),基本上遵循了许多示例 然后我遇到了一个问题-如果我必须组合多个搜索怎么办?我的每个存储库搜索方法(如上面所述)只返回一组有限的实际域对象。为了寻找更好的方法,我开始在IQueryable上编写扩展方

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

当我在寻找数据时,例如

  • 正在查找名为的联系人 以XYZ开头
  • 生日过后的联系人 1960年

    (等)

我开始实现存储库方法,例如第一个名称开始使用(字符串前缀)年轻人出生年份(整数年),基本上遵循了许多示例

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

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        
然而,我发现自己到处都在编写扩展方法(并发明了疯狂的类,如ContactsQueryableExtensions),而由于将所有内容都放在适当的存储库中,我失去了“良好的分组”


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

最近,在开始我目前的工作后,我一直在思考这个问题。我已经习惯了存储库,它们按照您的建议,只使用简单的存储库就可以实现完整的IQueryable路径

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

也许有什么方法可以重新思考你为什么要用这么多的方法来获取数据?如果没有,我真的觉得混合方法是最好的方法。为你重复使用的东西创建回购方法。那些真正有意义的东西。干的等等。但是那些一次性的东西呢?为什么不利用你能做的那些易懂和性感的东西呢是吗?正如你所说,为它创建一个方法是愚蠢的,但这并不意味着你不需要数据。DRY在那里并不真正适用,是吗


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

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

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

好的,时间就是一个例子

库层 完成了

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

注:-

  • 为了简单起见,我省略了任何依赖注入。这是非常推荐的
  • 这都是psedoo代码。未经测试(针对编译器),但您了解了
外卖点
  • 服务层处理所有智能。这就是您决定需要什么数据的地方
  • 存储库是一个简单的SELECT*FROM表或一个简单的INSERT/UPDATE into表

祝你好运:)

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

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

排序查询 如果该方法仅用于排序,我更喜欢编写在
IQueryable
IOrderedQueryable
上操作的扩展方法(以利用底层提供程序)

但是,如果该方法涉及使用its查询
EntityObject
,则通常可以非常轻松地将其设置为
静态
,并作为扩展方法实现。e、 g

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

返回集合的查询 如果该方法返回一些
IEnumerable
ICollection
IList
,那么我希望尽可能将其设置为
静态
,并将其保留在存储库中,即使它使用了导航属性。e、 g

公共静态IList GetByTerm(Term-Term,布尔排序)
{
var termReg=term.term注册;
退货(已订购)
?termReg.AsQueryable().OrderByStudentName().ToList()
:termReg.ToList();
}
这是因为我的
GetAll()

不将这些“集合getter”实现为扩展方法的另一个原因是,它们需要更详细的命名才能有意义,因为返回类型不是隐含的。例如,最后一个示例将成为
GetTermRegistrationsByTerm(此术语)


我希望这有帮助

六年后,我确信@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);
}
IQueryable车辆
ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}
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();
}
public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}
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);
}
public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}
public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}
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);
}