网络应用程序中的抽象查询策略

本文关键字:查询 策略 抽象 应用程序 网络 | 更新日期: 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就可以做到这一点。您定义了明确的方法,说明它正在检索什么以及如何检索。此外,对于只想公开聚合根的存储库,这些根实体是所有其他数据挂起的根实体,可以是CustomerSupplier。你不会试图直接到达一个地址——你会加载一个客户,然后查询他的地址。你可以根据供应商提供的东西加载一个供应商列表,但你不会通过"项目库"。我的例子可能不是最好的,但它们给了你一个想法。

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