存储库方法与扩展 IQueryable
我有存储库(例如 ContactRepository、UserRepository 等),它们封装了对域模型的数据访问。
当我搜索数据时,例如
- 查找名字的联系人 以 XYZ
开头,
- 生日晚于联系人 1960年
(等),
我开始实现诸如 FirstNameStartsWith(string prefix) 和 YoungerThanBirthYear(intyear) 之类的存储库方法,基本上遵循了许多示例。
然后我遇到了一个问题 - 如果我必须组合多个搜索怎么办?我的每个存储库搜索方法(例如上面的)仅返回一组有限的实际域对象。为了寻找更好的方法,我开始在 IQueryable
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 全部结束,并且我通过将所有内容都放在适当的存储库中而失去了“良好的分组”,
这真的是这样做的方法,还是有更好的方法来实现相同的目标?
I have repositories (e.g. ContactRepository, UserRepository and so forth) which encapsulate data access to the domain model.
When I was looking at searching for data, e.g.
- finding a contact whose first name
starts with XYZ a contact whose birthday is after
1960(etc),
I started implementing repository methods such as FirstNameStartsWith(string prefix) and YoungerThanBirthYear(int year), basically following the many examples out there.
Then I hit a problem - what if I have to combine multiple searches? Each of my repository search methods, such as above, only return a finite set of actual domain objects. In search for a better way, I started writing extension methods on IQueryable<T>, e.g. this:
public static IQueryable<Contact> FirstNameStartsWith(
this IQueryable<Contact> contacts, String prefix)
{
return contacts.Where(
contact => contact.FirstName.StartsWith(prefix));
}
Now I can do things such as
ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);
However, I found myself writing extension methods (and inventing crazy classes such as ContactsQueryableExtensions all over, and I lose the "nice grouping" by having everything in the appropriate repository.
Is this really the way to do it, or is there a better way to achieve the same goal?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
自从开始现在的工作后,我最近一直在思考这个问题。我习惯了存储库,它们按照您的建议仅使用简单的存储库即可走完整的 IQueryable 路径。
我认为存储库模式是合理的,并且在描述您希望如何处理应用程序域中的数据方面做了半有效的工作。不过你所描述的问题肯定会发生。它变得混乱、快速,超出了简单的应用程序的范围。
也许有什么方法可以重新思考为什么你要以如此多的方式索取数据?如果没有,我真的认为混合方法是最好的方法。为您重用的内容创建存储库方法。实际上有意义的东西。干燥等等。但那些一次性的事情呢?为什么不利用 IQueryable 以及你可以用它做的有趣的事情呢?正如您所说,为此创建一种方法是愚蠢的,但这并不意味着您不需要数据。 DRY 在那里并不适用,不是吗?
做好这件事需要纪律,但我确实认为这是一条合适的道路。
I have been thinking about this a lot lately, after starting at my current job. I am used to Repositories, they go the full IQueryable path using just bare bones repositories as you suggest.
I feel the repo pattern is sound and does a semi-effective job at describing how you want to work with the data in the application domain. However the issue you are describing definitely occurs. It gets messy, fast, beyond a simple application.
Are there, perhaps, ways to rethink why you are asking for the data in so many ways? If not, I really feel that a hybrid approach is the best way to go. Create repo methods for the stuff you reuse. Stuff that actually it makes sense for. DRY and all that. But those one-offs? Why not take advantage of IQueryable and the sexy things you can do with it? It is silly, as you said, to create a method for that, but it doesn't mean you don't need the data. DRY doesn't really apply there does it?
It would take discipline to do this well, but I really think it's an appropriate path.
@Alex - 我知道这是一个老问题,但我要做的就是让存储库只做非常简单的事情。这意味着,获取表或视图的所有记录。
然后,在服务层(您使用的是 n 层解决方案,对吧?:))我将在那里处理所有“特殊”查询内容。
好的,示例时间。
存储库层
漂亮又简单。
SqlContext
是EF Context
的实例。它有一个名为Contacts
的Entity
。基本上是你的 sql Contacts 类。这意味着,该方法基本上正在执行:
SELECT * FROM CONTACTS
...但它没有使用该查询访问数据库......它现在只是一个查询。好的..下一层..KICK ...我们继续(Inception有人吗?)
服务层
完成。
让我们回顾一下。首先,我们从一个简单的“从联系人中获取所有内容”查询开始。现在,如果我们提供了姓名,那么让我们添加一个过滤器来按姓名过滤所有联系人。接下来,如果我们提供了年份,那么我们将按年份过滤生日。等等。最后,我们访问数据库(使用这个修改后的查询)并查看返回的结果。
注意:-
要点
祝你好运 :)
@Alex - i know this is an old question, but what I would be doing would be letting the Repository do really simple stuff only. This means, get all records for a table or view.
Then, in the SERVICES layer (you are using an n-tiered solution, right? :) ) i would be handling all the 'special' query stuff there.
Ok, example time.
Repository Layer
Nice and simple.
SqlContext
is the instance of yourEF Context
.. which has anEntity
on it calledContacts
.. which is basically your sql Contacts class.This means, that method basically is doing:
SELECT * FROM CONTACTS
... but it's not hitting the database with that query .. it's only a query right now.Ok .. next layer.. KICK ... up we go (Inception anyone?)
Services Layer
Done.
So lets recap. First, we start our with a simple 'Get everything from contacts' query. Now, if we have a name provided, lets add a filter to filter all contacts by name. Next, if we have a year provided, then we filter the birthday by Year. Etc. Finally, we then hit the DB (with this modified query) and see what results we get back.
NOTES:-
Takeaway points
Good luck :)
我意识到这已经很旧了,但我最近一直在处理同样的问题,并且得出了与 Chad 相同的结论:只要有一点纪律,扩展方法和存储库方法的混合似乎效果最好。
我在(实体框架)应用程序中遵循的一些一般规则:
排序查询
如果该方法仅用于排序,我更喜欢编写在
IQueryable
或上操作的扩展方法>IOrderedQueryable
(利用底层提供程序。)eg现在我可以根据需要在我的存储库类中使用
ThenByStudentName()
。返回单个实例的查询
如果方法涉及通过原始参数进行查询,则通常需要
ObjectContext
并且不能轻易地使其静态
。我将这些方法留在我的存储库中,例如但是,如果该方法涉及使用其 导航属性,通常可以很容易地将其设为
静态
,并作为扩展方法实现。 eg现在我可以方便地编写
someStudent.GetLatestRegistration()
而无需当前范围内的存储库实例。返回集合的查询
如果该方法返回一些
IEnumerable
、ICollection
或IList
,那么我喜欢将其设为static
如果可能,并将其保留在存储库中即使它使用导航属性。例如这是因为我的
GetAll()
方法已经存在于存储库中,并且它有助于避免混乱的扩展方法。不将这些“集合 getter”实现为扩展方法的另一个原因是,它们需要更详细的命名才有意义,因为没有隐含返回类型。例如,最后一个示例将变为
GetTermRegistrationsByTerm(this Term term)
。我希望这有帮助!
I realize this is old, but I've been dealing with this same issue lately, and I came to the same conclusion as Chad: with a little discipline, a hybrid of extension methods and repository methods seems to work best.
Some general rules I've been following in my (Entity Framework) application:
Ordering queries
If the method is used only for ordering, I prefer to write extension methods that operate on
IQueryable<T>
orIOrderedQueryable<T>
(to leverage the underlying provider.) e.g.Now I can use
ThenByStudentName()
as needed within my repository class.Queries returning single instances
If the method involves querying by primitive parameters, it usually requires an
ObjectContext
and can't be easily madestatic
. These methods I leave on my repository, e.g.However, if the method instead involves querying an
EntityObject
using its navigation properties, it can usually be madestatic
quite easily, and implemented as an extension method. e.g.Now I can conveniently write
someStudent.GetLatestRegistration()
without needing a repository instance in the current scope.Queries returning collections
If the method returns some
IEnumerable
,ICollection
orIList
, then I like to make itstatic
if possible, and leave it on the repository even if it uses navigation properties. e.g.This is because my
GetAll()
methods already live on the repository, and it helps to avoid a cluttered mess of extension methods.Another reason for not implementing these "collection getters" as extension methods is that they would require more verbose naming to be meaningful, since the return type isn't implied. For example, the last example would become
GetTermRegistrationsByTerm(this Term term)
.I hope this helps!
六年后,我确信@Alex 已经解决了他的问题,但在阅读了已接受的答案后,我想添加我的两分钱。
扩展存储库中的
IQueryable
集合的一般目的是为了提供灵活性并使其使用者能够自定义数据检索。亚历克斯已经做了很好的工作。服务层的主要作用是遵守关注点分离原则并处理与业务功能相关的命令逻辑。
在现实世界的应用程序中,查询逻辑通常不需要超出存储库本身提供的检索机制(例如值更改、类型转换)的扩展。
考虑以下两种场景:
两种方法都是简单的查询扩展,但无论多么微妙,它们都有不同的作用。第一个是简单的过滤器,而第二个则意味着业务需求(例如维护或计费)。在一个简单的应用程序中,人们可以在存储库中实现它们。在更理想的系统中,
UsedThisYear
最适合服务层(甚至可以作为普通实例方法实现),它还可以更好地促进分离 CQRS 策略。 em>命令和查询。关键考虑因素是 (a) 存储库的主要目的以及 (b) 您是否愿意遵守 CQRS 和 DDD 理念。
Six years later, I am certain @Alex has solved his problem, but after reading the accepted answer I wanted to add my two cents.
The general purpose of extending
IQueryable
collections in a repository to provide flexibility and empower its consumers to customize data retrieval. What Alex has already done is good work.The primary role of a service layer is to adhere to the separation of concerns principle and address command logic associated with business function.
In real world applications, query logic often needs no extension beyond the retrieval mechanics provided by the repository itself (ex. value alterations, type conversions).
Consider the two following scenarios:
Both methods are simple query extensions, but however subtle they have different roles. The first is a simple filter, while the second implies business need (ex. maintenance or billing). In a simple application one might implement them both in a repository. In a more idealistic system
UsedThisYear
is best suited for the service layer (and may even be implemented as a normal instance method) where it may also better facilitate CQRS strategy of separating commands and queries.Key considerations are (a) the primary purpose of your repository and (b) how much do you like to adhere to CQRS and DDD philosophies.