我一直在寻找了一段时间,很好地解决了由典型库模式出现的问题(日益增长的专门的查询方法等列表..看到: http://ayende.com/blog/3955/repository-是最新单 )。
我真的很喜欢使用命令查询,特别是通过使用该规范模式的想法。 然而,我与规范的问题是,它仅涉及到简单的选择的标准(基本上,where子句),并且不与查询的其他问题,如加入,分组,子集选择或投影等处理..基本上,所有的额外箍许多查询必须经过以获得正确的一组数据。
(注:我用的是“指挥”一词作为Command模式,也被称为查询对象我说的不是命令,在命令/查询分离那里是查询和命令之间进行区分(更新,删除,插入))
所以我在寻找封装了整个查询的替代品,但仍足够灵活,你不只是换面条库的命令类的爆炸。
我已经使用,比如Linqspecs,而我在能够有意义的名称指定选择标准找到一些价值,它只是不够的。 也许我正在寻找,结合多种方法的混合解决方案。
我找其他人可能已经发展到无论是解决这个问题,还是解决不同的问题,但仍然满足这些需求的解决方案。 在链接的文章,Ayende建议直接使用NHibernate的情况下,但我觉得你的业务层主要是复杂化,因为它现在还必须包含查询信息。
我会在这个只要等待期间可经过提供赏金。 所以,请让你的解决方案,赏金值得,具有很好的解释和我将选择最佳的解决方案,最多给予好评的选手。
注:我正在寻找的东西是基于ORM。 不必是EF或NHibernate的明确,但这些是最常见的,并会配合最好的。 如果它可以很容易地适应其他的ORM的,这将是一个奖金。 LINQ的兼容也将是不错。
更新:我真的很惊讶,没有很多好的建议在这里。 好像人要么完全CQRS,或者他们在仓库营完全是。 我的大多数应用程序是不是很复杂,值得CQRS(东西大多数CQRS主张爽快地说,你不应该使用它)。
更新:似乎这里有点慌乱。 我不是在寻找一个新的数据访问技术,而是业务和数据之间的合理设计良好的接口。
理想情况下,我正在寻找的是某种形式的查询对象,规范模式和存储库之间的交叉。 正如我前面所说,规格图案只能用where子句方面的交易,而不是查询的其他方面,如连接,子查询等。库处理整个查询,但失控的一段时间后。 查询对象还应对整个查询,但我不想简单地查询对象的爆炸代替库。
免责声明:由于没有任何伟大的解答,我决定发布从一个伟大的博客帖子我刚才读的一部分,复制几乎一字不差。 你可以找到完整的博客文章在这里 。 所以在这里,它是:
我们可以定义以下两个接口:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
所述IQuery<TResult>
指定用于定义与它返回使用的数据的特定的查询消息TResult
通用类型。 与先前定义的接口,我们可以定义这样的查询信息:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
这个类定义与两个参数,这将导致在阵列的查询操作User
的对象。 处理这个消息中的类可以被定义如下:
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
接口:
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
(和所有其他消费者,界面)。
所述IQuery<TResult>
指定或注入时接口给我们的编译时支持IQueryHandlers
在我们的代码。 当我们改变FindUsersBySearchTextQuery
返回UserInfo[]
代替(通过实施IQuery<UserInfo[]>
时, UserController
将无法被编译,由于在通用的类型约束IQueryHandler<TQuery, TResult>
将不能够映射FindUsersBySearchTextQuery
到User[]
注射IQueryHandler
界面变成然而消费者,有仍然需要解决一些不太明显的问题。 我们的消费者的依赖关系的数量可能会过大,会导致构造过注射 - 当一个构造函数采用的参数太多。 查询类执行频繁变化,这就需要不断的变化成的构造函数的参数个数数。
我们可以修复其注入了太多的问题IQueryHandlers
有额外的抽象层。 我们创建了消费者和查询处理程序之间坐在一个中介:
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
所述IQueryProcessor
是一个非通用接口与一个通用的方法。 正如你可以在接口定义看到, IQueryProcessor
取决于上IQuery<TResult>
接口。 这让我们有我们的消费者依赖于编译时支持IQueryProcessor
。 让我们重写UserController
使用新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<User[]>
界面,我们可以把它传递给通用Execute<TResult>(IQuery<TResult> query)
方法。 由于C#类型推断,编译器能够确定泛型类型,这节省了我们有明确声明的类型。 所述的返回类型Process
方法也是已知的。
现在是执行的责任IQueryProcessor
找到合适的IQueryHandler
。 这需要一些动态类型,以及可选的使用依赖注入框架,并能与所有的代码,只需几行来完成:
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>
类型的基于所提供的查询实例的类型。 这种类型是用来询问提供的容器类来获得该类型的一个实例。 不幸的是,我们需要调用Handle
使用反射(通过使用在这种情况下,C#4.0 dymamic关键字)方法,因为在这一点上是不可能的铸处理程序实例,因为一般的TQuery
说法是无法在编译时。 然而,除非Handle
方法被重命名或获取其他的参数,这个调用永远不会失败,如果你想,这是很容易为此类编写单元测试。 使用反射会给略有下降,但没有什么真正担心。
要回答你的问题之一:
所以我在寻找封装了整个查询的替代品,但仍足够灵活,你不只是换面条库的命令类的爆炸。
采用这种设计的结果是会有很多小班制度,但有很多小/集中班(有明确的名称)是一件好事。 这种方法就是具有与在信息库中同一方法不同的参数,大量重载显然要好的多,因为您可以将那些在一个查询类。 所以,你仍然得到少了很多查询类,比在存储库的方法。
我处理这个方式实际上是简单化和ORM无关。 我对储存的看法是这样的:库的工作是提供了上下文所需要的模型的应用,因此应用程序只是要求回购它所希望,但不告诉它如何得到它。
我公司供应的仓库方法与标准(是的,DDD风格),这将通过回购可以用来创建查询(或任何需要 - 这可能是一个Web服务请求)。 加入和团体恕我直言,是如何,而不是什么和标准应该是唯一的基地建立一个where子句的详细信息。
模型=由应用neede最终对象或数据结构。
public class MyCriteria
{
public Guid Id {get;set;}
public string Name {get;set;}
//etc
}
public interface Repository
{
MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
}
也许你可以使用ORM标准(NHibernate的)直接,如果你想要它。 该仓库实现应该知道如何使用标准与底层存储或DAO。
我不知道你的域和模型的要求,但它会很奇怪,如果最好的办法是应用程序构建查询本身。 该模型的变化这么多,你无法定义的东西稳定吗?
该解决方案显然需要一些额外的代码,但它的不夫妇其余的ORM或任何你正在使用访问存储。 存储库,它的工作作为一个门面和国际海事组织它的清洁和“标准转换”的代码是可重复使用
我已经做到了这一点,这支持和撤消此。
主要的问题是这样的:不管你怎么做,增加的抽象不会获得你的独立性。 它将由定义泄漏。 从本质上讲,你发明了一整层只是为了让你的代码看起来很可爱......但它并不减少维护,提高可读性或增加你的任何类型的模型不可知论。
有趣的部分是,你回答了你自己的问题响应奥利弗的回答是:“这基本上是重复的LINQ的功能,而你的LINQ得到的所有好处”。
问问自己:怎么会没有呢?
您可以使用流利的接口。 其基本思想是,一个类的方法返回当前实例这个非常具有类执行一些动作之后。 这允许您链方法调用。
通过创建一个合适的类层次结构,您可以创建的访问的方法的逻辑流程。
public class FinalQuery
{
protected string _table;
protected string[] _selectFields;
protected string _where;
protected string[] _groupBy;
protected string _having;
protected string[] _orderByDescending;
protected string[] _orderBy;
protected FinalQuery()
{
}
public override string ToString()
{
var sb = new StringBuilder("SELECT ");
AppendFields(sb, _selectFields);
sb.AppendLine();
sb.Append("FROM ");
sb.Append("[").Append(_table).AppendLine("]");
if (_where != null) {
sb.Append("WHERE").AppendLine(_where);
}
if (_groupBy != null) {
sb.Append("GROUP BY ");
AppendFields(sb, _groupBy);
sb.AppendLine();
}
if (_having != null) {
sb.Append("HAVING").AppendLine(_having);
}
if (_orderBy != null) {
sb.Append("ORDER BY ");
AppendFields(sb, _orderBy);
sb.AppendLine();
} else if (_orderByDescending != null) {
sb.Append("ORDER BY ");
AppendFields(sb, _orderByDescending);
sb.Append(" DESC").AppendLine();
}
return sb.ToString();
}
private static void AppendFields(StringBuilder sb, string[] fields)
{
foreach (string field in fields) {
sb.Append(field).Append(", ");
}
sb.Length -= 2;
}
}
public class GroupedQuery : FinalQuery
{
protected GroupedQuery()
{
}
public GroupedQuery Having(string condition)
{
if (_groupBy == null) {
throw new InvalidOperationException("HAVING clause without GROUP BY clause");
}
if (_having == null) {
_having = " (" + condition + ")";
} else {
_having += " AND (" + condition + ")";
}
return this;
}
public FinalQuery OrderBy(params string[] fields)
{
_orderBy = fields;
return this;
}
public FinalQuery OrderByDescending(params string[] fields)
{
_orderByDescending = fields;
return this;
}
}
public class Query : GroupedQuery
{
public Query(string table, params string[] selectFields)
{
_table = table;
_selectFields = selectFields;
}
public Query Where(string condition)
{
if (_where == null) {
_where = " (" + condition + ")";
} else {
_where += " AND (" + condition + ")";
}
return this;
}
public GroupedQuery GroupBy(params string[] fields)
{
_groupBy = fields;
return this;
}
}
你会这样称呼它
string query = new Query("myTable", "name", "SUM(amount) AS total")
.Where("name LIKE 'A%'")
.GroupBy("name")
.Having("COUNT(*) > 2")
.OrderBy("name")
.ToString();
您只能创建的新实例Query
。 其他类有一个受保护的构造。 层次结构的关键是“禁用”的方法。 例如,所述GroupBy
方法返回GroupedQuery
这是基类的Query
和不具有Where
方法(其中方法在声明Query
)。 因此,它不可能叫Where
后GroupBy
。
然而并非十全十美。 有了这个类层次结构则可连续隐藏成员,但没有表现出新的问题。 因此, Having
抛出时,它之前被调用异常GroupBy
。
请注意,这是可以调用Where
几次。 这增加了新的条件与AND
现有条件。 这使得它更容易编程方式从单一的条件下构建滤波器。 这同样是可能Having
。
接受字段列出的方法具有的参数params string[] fields
。 它可以让你无论是通过单个字段名或一个字符串数组。
流利的接口是非常灵活,并不需要你创造了很多具有不同的参数组合方法重载的。 我的例子也适用于字符串,但是这种方法可以扩展到其他类型。 你也可以声明为接受自定义类型的特殊情况或方法预定义的方法。 您还可以添加类似方法ExecuteReader
或ExceuteScalar<T>
这将允许你这样定义查询
var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
.Where(new CurrentMonthCondition())
.Where(new DivisionCondition{ DivisionType = DivisionType.Production})
.OrderBy(new StandardMonthlyReportSorting())
.ExecuteReader();
甚至构建这种方式可以有命令参数,从而避免SQL注入问题,并在同一时间允许命令把由数据库服务器被缓存SQL命令。 这不是一个O / R映射器的替代品,但可以在你创建一个使用简单的字符串连接,否则命令的情况下帮助。