网络应用程序中的抽象查询策略
本文关键字:查询 策略 抽象 应用程序 网络 | 更新日期: 2023-09-27 18:28:43
在web应用场景中,通常有两个关于数据检索的要求:1.所有查询都需要选择性分页2.用户需要很多"过滤"查询(例如,按姓名、邮件和年龄搜索人员)3.某些客户端网格隐含的排序查询
当然,组合的情况是:过滤的、分页的查询,这些查询经过排序:)
这些需求可能导致数据层中有很多存储库方法,每个方法都有很多参数。是否有任何常见的模式可以让这个过程具有更动态的行为(例如,根据域类属性自动生成查询)?
我知道存储库方法应该是干净的并且定义良好的。但目前,每当我向MVC视图或可排序的分页表添加一些过滤表单时,我都想写很多样板代码。
其他开发人员如何处理此类需求?
提前感谢
存储库、通用存储库和暴露IQueryable的存储库是一个非常激烈的争论。
底线是一个通用或公开的存储库IQueryable根本不是一个真正的存储库,它只是数据层的抽象
现在,这并不是一件坏事,但不要把它称为存储库,直接称之为存储库。数据层抽象可以让你在读写实体的UI中快速插入一些东西,而不会把数据层框架泄露到UI中。当然,你可以只注入一个ISession
就可以了
public interface IRepository<T> {}
public class NHibernateRepository<T> : IRepository<T>
{
private ISession session;
public NHibernateRepository(ISession session)
{
this.session = session;
}
T Get(object id) { return session.GetById<T>(id)); }
IQueryable<T> Query { get { return session.Query<T>(); }
}
new NHibernateRepository<Customer>(session).Query().Where(customer => customer.Name == Fred);
但是,如果您想捕获一些可重用的逻辑,并在您的服务或UI和数据层之间提供一个明确的契约,那么Repository就可以做到这一点。您定义了明确的方法,说明它正在检索什么以及如何检索。此外,对于只想公开聚合根的存储库,这些根实体是所有其他数据挂起的根实体,可以是Customer
和Supplier
。你不会试图直接到达一个地址——你会加载一个客户,然后查询他的地址。你可以根据供应商提供的东西加载一个供应商列表,但你不会通过"项目库"。我的例子可能不是最好的,但它们给了你一个想法。
public class CustomerRepository
{
public Customer GetCustomerWithName(string name);
}
public class SupplierRepository
{
public IEnumerable<Supplier> GetSuppliersWhoStockItem(string itemName)
}
最后,如果你有冒险精神,你可能想看看CQRS,这是一个很大的主题,但这里有很多例子。
第一个实现起来更快,第二个提供了更清晰的可重用代码,第三个提供了UI层之间的分离,但需要更多的基础工作。这取决于你需要和想要什么,可能应该按照这个顺序来解决。
在我们的项目中,我们也使用了存储库,每个实体都使用存储库,但我不喜欢。当您编写具有大量交互实体的复杂查询时,它可能会带来很多问题。我认为它将是一个用于基本操作的通用存储库,所有查询都应该以查询对象模式呈现,类似于每个查询的单独类,尽管可以查看:
http://richarddingwall.name/2010/06/15/brownfield-cqrs-part-1-commands/
http://codebetter.com/gregyoung/2009/01/20/ddd-specification-or-query-object/
http://devlicio.us/blogs/casey/archive/2009/02/13/ddd-command-query-separation-as-an-architectural-concept.aspx
http://blog.jonathanoliver.com/2009/10/dddd-why-i-love-cqrs/
http://www.udidahan.com/2007/03/28/query-objects-vs-methods-on-a-repository/
您使用存储库来枚举聚合根。通常情况下,您的控制器使用聚合根,您可以根据用户的需要对其进行筛选、排序等。
因此,我按照前面提到的内容使用了一个存储库。
然而,有时我需要在更复杂的规范中工作,在这些规范中,使用聚合根和/或一大堆存储库要么很痛苦,要么效率低下,要么根本不可能。例如,您可能需要运行大型业务报告,或者执行批处理命令。
在这类情况下,我还定义了ICommand
/IQuery
,使用基于NH的实现来处理管道方面的事务(就像普通的Repository
一样)。
然后,我要做的是创建一个接口,表示规范的契约,公开我可能需要帮助构建所需参数的任何成员。然后,我实现了该规范,使用NH作为主干,使用任何最合适的技术(HQL语句、原始SQL、Criteria、QueryOver…等等)来实现该规范。
这是我的意思的粗略说明。请注意,我使用了一个任意的ICommandProvider
,它是一些根据需要创建命令的新实例的对象(以防您需要在一个操作中发出多个命令)。我会用IoC注册我的命令,并让提供者使用它来创建命令实例。
public interface ICommand
{
}
public interface ICommandProvider
{
TCommand Create<TCommand>()
where TCommand : ICommand;
}
public interface IQuery<TResult> : ICommand
{
TResult Execute();
}
public class NhCommand : ICommand
{
// plumbing stuff here, like finding the current session
}
public class DelinquentAccountViewModel
{
public string AccountName { get; set; }
public decimal Amount { get; set; }
}
public interface IDelinquentAccountsQuery : IQuery<IEnumerable<DelinquentAccountViewModel>>
{
void AmountGreaterThan(decimal amount);
// you could define members for specifying sorting, etc. here
}
public class DelinquentAccountsQuery : NhCommand
{
public IEnumerable<DelinquentAccountViewModel> Execute()
{
// build HQL and execute results, resulting in a list of DelinquentAccountViewModels
// using _amountGreaterThan as a parameter
return null;
}
private Decimal _amountGreaterThan;
public void AmountGreaterThan(Decimal amount)
{
_amountGreaterThan = amount;
}
}
控制器中的用法可能是这样的:
public class DelinquentAccountsController : Controller
{
protected ICommandProvider CommandProvider { get; private set; }
public DelinquentAccountsController(ICommandProvider commandProvider)
{
CommandProvider = commandProvider;
}
public ActionResult Index(decimal amount)
{
var query = CommandProvider.Create<IDelinquentAccountsQuery>();
query.AmountGreaterThan(amount);
return View(query.Execute());
}
}
没有什么可以说你不能使用命令/查询来完成所有的数据访问,但这比我需要的工作更多。我发现标准的存储库方法(针对NHibernate使用LINQ)可以处理应用程序所需的95%左右的数据访问。
您可以让您的存储库提供一个IQueryable,并让ActionMethod决定显示什么。例如:
public System.Linq.IQueryable<Models.MyModel> Query()
{
return mSession.Query<Models.MyModel>();
}
Imho orm已经足够抽象了。除此之外,您不需要存储库。如果您要通过一些设置动态更改orm,那么您只需要抽象。Where、Skip、Take、OrderBy等与形式无关,可以通过公开IQueryable来使用。但有些特性是特定于orm的(比如fetch与include),这些特性会让存储库变得非常丑陋(百万方法或具有百万参数的方法)
我通常只做的扩展方法
public static class QueryExtensions
{
public static IQueryable<T> Published(this IQueryable<T> pages) where T : IPage
{
return pages.Where(p => p.State == PageState.Public && p.Published <= DateTime.UtcNow);
}
public static IQueryable<T> By(this IQueryable<T> pages, User author) where T : IPage
{
return pages.Where(p => p.Author == author);
}
public static IEnumerable<Foo> AdvancedThing(this ISession session, string text)
{
// I get all the power of NHibernate :)
return session.CreateQuery("...").SetString("text", text).List<Foo>();
}
}
并在行动方法中直接使用ISession
var posts = session.Query<Post>().By(user).Published().FetchMany(p => p.Tags).ToList();