存储库方法对比扩展 IQueryable
-
06-07-2019 - |
题
我有存储库(例如ContactRepository、UserRepository 等)封装了对域模型的数据访问。
当我在看的时候 搜索数据, ,例如
- 找到一个以XYZ开头的名字的联系人
1960年之后的生日的联系人
(ETC),
我开始实施存储库方法,例如 FirstNameStartsWith(字符串前缀) 和 比出生年份更年轻(int 年), ,基本上遵循那里的许多例子。
然后我遇到了一个问题 - 如果我必须组合多个搜索怎么办?我的每个存储库搜索方法(例如上面的)仅返回一组有限的实际域对象。为了寻找更好的方法,我开始写作 扩展方法 在 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);
然而,我发现自己正在编写扩展方法(并发明了疯狂的类,例如 联系人可查询扩展 一切都结束了,我因为将所有内容都放在适当的存储库中而失去了“良好的分组”。
这真的是这样做的方法吗,还是有更好的方法来实现相同的目标?
解决方案
@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
...但它没有通过该查询访问数据库..现在只是一个查询。
好的 ..下一层.. 踢 ...我们上去(盗梦空间 任何人?)
服务层
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();
}
完毕。
让我们回顾一下。首先,我们从一个简单的'从联系人中获取一切' 询问。现在,如果我们提供了姓名,那么让我们添加一个过滤器来按姓名过滤所有联系人。接下来,如果我们提供了年份,那么我们将按年份过滤生日。ETC。最后,我们访问数据库(使用修改后的查询)并查看返回的结果。
笔记:-
- 为了简单起见,我省略了任何依赖注入。强烈推荐。
- 这都是伪代码。未经测试(针对编译器),但你明白了......
外卖积分
- 服务层处理所有的智能。您可以在此处决定需要哪些数据。
- 存储库是一个简单的 SELECT * FROM TABLE 或简单的 INSERT/UPDATE 到表中。
祝你好运 :)
其他提示
自从开始现在的工作后,我最近一直在思考这个问题。我习惯了存储库,它们按照您的建议仅使用简单的存储库即可走完整的 IQueryable 路径。
我认为存储库模式是合理的,并且在描述您希望如何处理应用程序域中的数据方面做了半有效的工作。不过你所描述的问题肯定会发生。它变得混乱、快速,超出了简单的应用程序的范围。
也许有什么方法可以重新思考为什么你要以如此多的方式索取数据?如果没有,我真的认为混合方法是最好的方法。为您重用的内容创建存储库方法。实际上有意义的东西。干燥等等。但那些一次性的事情呢?为什么不利用 IQueryable 以及用它可以做的有趣的事情呢?正如您所说,为此创建一种方法是愚蠢的,但这并不意味着您不需要数据。DRY 在那里并不适用,不是吗?
做好这件事需要纪律,但我确实认为这是一条合适的道路。
我意识到这已经很老了,但我最近一直在处理同样的问题,并且我得出了与乍得相同的结论:只要有一点纪律,扩展方法和存储库方法的混合似乎效果最好。
我在(实体框架)应用程序中遵循的一些一般规则:
订购查询
如果该方法仅用于排序,我更喜欢编写可操作的扩展方法 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()
不需要当前范围内的存储库实例。
返回集合的查询
如果该方法返回一些 IEnumerable
, ICollection
或者 IList
, ,然后我喜欢做它 static
如果可能的话,并将其保留在存储库中 即使它使用导航属性。 例如
public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
var termReg = term.TermRegistrations;
return (ordered)
? termReg.AsQueryable().OrderByStudentName().ToList()
: termReg.ToList();
}
这是因为我的 GetAll()
方法已经存在于存储库中,它有助于避免混乱的扩展方法。
不将这些“集合 getter”实现为扩展方法的另一个原因是,它们需要更详细的命名才有意义,因为没有隐含返回类型。例如,最后一个例子将变成 GetTermRegistrationsByTerm(this Term term)
.
我希望这有帮助!
六年后,我确信@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
最适合服务层(甚至可以作为普通实例方法实现),它也可以更好地促进 连续QRS 分离策略 命令 和 查询.
关键考虑因素是 (a) 存储库的主要目的以及 (b) 您愿意遵守多少 连续QRS 和 DDD 哲学。