精心设计的查询命令和/或技术规格 [英] Well designed query commands and/or specifications

查看:103
本文介绍了精心设计的查询命令和/或技术规格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在寻找了一段时间,很好地解决了典型Repository模式带来的问题(日益增长的专门查询的方法等..名单见:的 http://ayende.com/blog/3955/repository-is-the-new-singleton )。

I've been searching for quite some time for a good solution to the problems presented by the typical Repository pattern (growing list of methods for specialized queries, etc.. see: http://ayende.com/blog/3955/repository-is-the-new-singleton).

我真的很喜欢使用命令查询,特别是通过使用该规范模式的想法。然而,我与规格的问题是,它仅涉及简单的选择的标准(基本上,where子句),并且不与查询的其他问题,例如加入,分组,子集选择或突起等处理..基本上,所有的额外篮球许多查询必须经过以得到正确的数据集。

I really like the idea of using Command queries, particularly through use of the Specification pattern. However, my problem with specification is that it only relates to the criteria of simple selections (basically, the where clause), and does not deal with the other issues of queries, such as joining, grouping, subset selection or projection, etc.. basically, all the extra hoops many queries must go through to get the correct set of data.

(注:我用的是指挥一词作为Command模式,也被称为查询对象我不是在命令谈论命令/查询分离那里是查询和命令加以区分(更新,删除,插入))

(note: I use the term "command" as in the Command pattern, also known as query objects. I'm not talking about command as in command/query separation where there is a distinction made between queries and commands (update, delete, insert))

所以我在寻找封装了整个查询的替代品,但仍不够灵活那你不只是换面条存储库命令类的爆炸。

So I'm looking for alternatives that encapsulate the entire query, but still flexible enough that you're not just swapping spaghetti Repositories for an explosion of command classes.

我已经使用,比如Linqspecs,而我的是能不能找到一些价值分配有意义的名称来选择标准,它只是不够的。也许我正在寻找,结合多种方法的混合解决方案。

I've used, for instance Linqspecs, and while I find some value in being able to assign meaningful names to selection criteria, it's just not enough. Perhaps i'm seeking a blended solution that combines multiple approaches.

我要寻找其他人可能已经发展到不是解决这个问题,还是解决不同的问题,但解决方案仍满足这些要求。在链接的文章,Ayende建议直接使用NHibernate的背景下,但我觉得你的业务层主要是复杂化,因为现在还必须包含查询信息。

I am looking for solutions that others may have developed to either address this problem, or address a different problem but still satisfies these requirements. In the linked article, Ayende suggests using the nHibernate context directly, but I feel that largely complicates your business layer because it now also has to contain query information.

我会尽快等候时间,经过对这个提供的赏金。所以请您的解决方案赏金值得的,具有很好的解释,我会选择最佳的解决方案,最多给予好评亚军

I'll be offering a bounty on this, as soon as the waiting period elapses. So please make your solutions bounty worthy, with good explanations and I will select the best solution, and upvote the runners up.

请注意:我正在寻找的东西是ORM为主。不必是EF或的nHibernate明确,但这些是最常见的,并将适合最好的。如果它可以很容易地适应其他的ORM的,这将是一个奖金。 LINQ的兼容也将是不错

NOTE: I'm looking for something that is ORM based. Doesn't have to be EF or nHibernate explicitly, but those are the most common and would fit the best. If it can be easily adapted to other ORM's that would be a bonus. Linq compatible would also be nice.

更新:我真的很惊讶,没有很多好的建议在这里。好像人要么完全CQRS,或者他们在库营完全是。我的大多数应用程序是不是很复杂,值得CQRS(东西大多数CQRS主张爽快地说,你不应该使用它)。

UPDATE: I'm really surprised that there aren't many good suggestions here. It seems like people are either totally CQRS, or they're completely in the Repository camp. Most of my apps are not complex enough to warrant CQRS (something with most CQRS advocates readily say that you should not use it for).

更新:似乎这里有点慌乱。我不是在寻找一个新的数据访问技术,而是业务和数据之间的合理设计良好的接口。

UPDATE: There seems to be a little confusion here. I'm not looking for a new data access technology, but rather a reasonably well designed interface between business and data.

在理想情况下,我正在寻找的是某种查询对象,规范的模式和存储库之间的交叉。正如我上面所说的,规范的模式只能用where子句方面的交易,而不是查询的其他方面,如连接,子查询等。库处理整个查询,但一发不可收拾过了一会。查询对象还应对整个查询,但我不想简单地替换查询对象的爆炸库。

Ideally, what i'm looking for is some kind of cross between Query objects, Specification pattern, and repository. As I said above, Specification pattern only deals with the where clause aspect, and not the other aspects of the query, such as joins, sub-selects, etc.. Repositories deal with the whole query, but get out of hand after a while. Query objects also deal with the whole query, but I don't want to simply replace repositories with explosions of query objects.

推荐答案

声明:由于没有任何伟大的解答,我决定发布从一个伟大的博客帖子我刚才读的一部分,复制几乎一字不差。你可以在这里找到完整的博客文章。所以在这里,它是:

Disclaimer: Since there aren't any great answers yet, I decided to post a part from a great blog post I read a while ago, copied almost verbatim. You can find the full blog post here. So here it is:

我们可以定义以下两个接口:

We can define the following two interfaces:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}



IQUERY< TResult> 指定定义了它返回使用 TResult 泛型类型的数据的特定查询的消息。与先前定义的接口,我们可以这样定义一个查询消息:

The IQuery<TResult> specifies a message that defines a specific query with the data it returns using the TResult generic type. With the previously defined interface we can define a query message like this:

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; set; }
    public bool IncludeInactiveUsers { get; set; }
}

这类定义了一个查询操作有两个参数,这将导致阵列用户对象。 FindUsersBySearchTextQuery,用户[; IQueryHandler<:

This class defines a query operation with two parameters, which will result in an array of User objects. The class that handles this message can be defined as follows:

public class FindUsersBySearchTextQueryHandler
    : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    private readonly NorthwindUnitOfWork db;

    public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
    {
        this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
        return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
    }
}

我们现在可以让消费者依赖于普通的 IQueryHandler 接口:

We can now let consumers depend upon the generic IQueryHandler interface:

public class UserController : Controller
{
    IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;

    public UserController(
        IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
    {
        this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        User[] users = this.findUsersBySearchTextHandler.Handle(query);    
        return View(users);
    }
}



紧随这种模式给了我们很大的灵活性,因为我们现在可以决定如何注入到 UserController的。我们可以注入一个包装真正实现了完全不同的实现,或者一个,而无需进行更改 UserController的(和所有其他消费者,界面)。

Immediately this model gives us a lot of flexibility, because we can now decide what to inject into the UserController. We can inject a completely different implementation, or one that wraps the real implementation, without having to make changes to the UserController (and all other consumers of that interface).

IQUERY< TResult>指定或注射时接口为我们提供了编译时支持 IQueryHandlers 在我们的代码。当我们改变 FindUsersBySearchTextQuery 返回的UserInfo [] 代替(通过实施 IQUERY<的UserInfo [ > )的 UserController的将无法编译,因为对一般类型的约束 IQueryHandler< TQuery的,TResult> 将无法映射 FindUsersBySearchTextQuery 用户[]

The IQuery<TResult> interface gives us compile-time support when specifying or injecting IQueryHandlers in our code. When we change the FindUsersBySearchTextQuery to return UserInfo[] instead (by implementing IQuery<UserInfo[]>), the UserController will fail to compile, since the generic type constraint on IQueryHandler<TQuery, TResult> won't be able to map FindUsersBySearchTextQuery to User[].

注射的 IQueryHandler 界面变成一个消费者却拥有仍需要解决一些不太明显的问题。我们的消费者的依赖关系的数量可能会过大,可能会导致过度的构造注射 - 当一个构造函数的参数太多。查询类执行时可以经常改变,这需要不断的变化成的构造函数的参数个数数。

Injecting the IQueryHandler interface into a consumer however, has some less obvious problems that still need to be addressed. The number of dependencies of our consumers might get too big and can lead to constructor over-injection - when a constructor takes too many arguments. The number of queries a class executes can change frequently, which would require constant changes into the number of constructor arguments.

我们可以修复其注入了太多的问题, IQueryHandlers 有额外的抽象层。我们创建了消费者和查询处理程序之间坐着一位调解人:

We can fix the problem of having to inject too many IQueryHandlers with an extra layer of abstraction. We create a mediator that sits between the consumers and the query handlers:

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}



IQueryProcessor 是非通用接口与一个通用的方法。正如你可以在接口定义看到的, IQueryProcessor 依赖于 IQUERY< TResult> 接口。这让我们有我们的消费者依赖于 IQueryProcessor 编译时的支持。让我们重写 UserController的来使用新的 IQueryProcessor

The IQueryProcessor is a non-generic interface with one generic method. As you can see in the interface definition, the IQueryProcessor depends on the IQuery<TResult> interface. This allows us to have compile time support in our consumers that depend on the IQueryProcessor. Let's rewrite the UserController to use the new IQueryProcessor:

public class UserController : Controller
{
    private IQueryProcessor queryProcessor;

    public UserController(IQueryProcessor queryProcessor)
    {
        this.queryProcessor = queryProcessor;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        // Note how we omit the generic type argument,
        // but still have type safety.
        User[] users = this.queryProcessor.Process(query);

        return this.View(users);
    }
}



UserController的现在取决于 IQueryProcessor ,可以处理所有的查询。在 UserController的 SearchUsers 方法调用 IQueryProcessor.Process 方法传入一个初始化的查询对象。因为 FindUsersBySearchTextQuery 实施 IQUERY<使用者[]> 界面,我们可以把它传递给普通的执行< TResult>(IQUERY< TResult>查询)方法。由于C#类型推断,编译器能够确定泛型类型,这节省了我们有明确声明的类型。返回类型过程方法也称为的。

The UserController now depends on a IQueryProcessor that can handle all of our queries. The UserController's SearchUsers method calls the IQueryProcessor.Process method passing in an initialized query object. Since the FindUsersBySearchTextQuery implements the IQuery<User[]> interface, we can pass it to the generic Execute<TResult>(IQuery<TResult> query) method. Thanks to C# type inference, the compiler is able to determine the generic type and this saves us having to explicitly state the type. The return type of the Process method is also known.

现在执行的责任 IQueryProcessor 来找到合适的 IQueryHandler 。这需要一些动态类型,以及可选的使用依赖注入的框架,并且都可以用的代码,只需几行来完成:

It is now the responsibility of the implementation of the IQueryProcessor to find the right IQueryHandler. This requires some dynamic typing, and optionally the use of a Dependency Injection framework, and can all be done with just a few lines of code:

sealed class QueryProcessor : IQueryProcessor
{
    private readonly Container container;

    public QueryProcessor(Container container)
    {
        this.container = container;
    }

    [DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>)
            .MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = container.GetInstance(handlerType);

        return handler.Handle((dynamic)query);
    }
}



QueryProcessor 类构造一个特定的 IQueryHandler< TQuery的,TResult>根据所提供的查询实例的类型键入。这种类型用于要求提供的容器类来获得该类型的一个实例。不幸的是,我们需要调用处理方法使用反射(通过使用C#4.0 dymamic关键字在这种情况下),因为在这一点上是不可能的铸处理程序实例,因为通用的TQuery 参数不可用在编译的时候。然而,除非处理方法被重命名或获取其他的参数,这个调用永远不会失败,如果你想,这是很容易为此类编写单元测试。 。使用反射会给略有下降,但没有什么真正担心

The QueryProcessor class constructs a specific IQueryHandler<TQuery, TResult> type based on the type of the supplied query instance. This type is used to ask the supplied container class to get an instance of that type. Unfortunately we need to call the Handle method using reflection (by using the C# 4.0 dymamic keyword in this case), because at this point it is impossible to cast the handler instance, since the generic TQuery argument is not available at compile time. However, unless the Handle method is renamed or gets other arguments, this call will never fail and if you want to, it is very easy to write a unit test for this class. Using reflection will give a slight drop, but is nothing to really worry about.

要回答你的问题之一:

所以我在寻找封装了整个查询的替代品,但
仍然具有足够的灵活性,你不只是换面条
库的命令类的爆炸。

So I'm looking for alternatives that encapsulate the entire query, but still flexible enough that you're not just swapping spaghetti Repositories for an explosion of command classes.

采用这种设计的结果是会有很多小在系统中,但有很多小/集中班(有明确的名称)班是一件好事。这种方法就是具有与在库中相同的方法不同的参数很多重载显然要好的多,因为您可以将那些在一个查询类。所以,你仍然得到少了很多查询类,不是在一个存储库的方法。

A consequence of using this design is that there will be a lot of small classes in the system, but having a lot of small/focused classes (with clear names) is a good thing. This approach is clearly much better then having many overloads with different parameters for the same method in a repository, as you can group those in one query class. So you still get a lot less query classes than methods in a repository.

这篇关于精心设计的查询命令和/或技术规格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆