C# 设计良好的查询命令和/或规范
对于典型的存储库模式(用于专门查询的方法越来越多,等等,请参见:)所提出的问题,我已经搜索了相当长的一段时间,寻找一个好的解决方案 我非常喜欢使用命令查询的想法,特别是通过使用规范模式。然而,我对规范的问题是,它只涉及简单选择的标准(基本上是where子句),而不涉及查询的其他问题,例如连接、分组、子集选择或投影等。。基本上,为了获得正确的数据集,许多查询必须经历所有额外的困难 (注意:我在命令模式中使用术语“command”,也称为查询对象。我不是在命令/查询分离中谈论命令,在命令/查询分离中,查询和命令(更新、删除、插入)是有区别的。) 因此,我正在寻找封装整个查询的替代方案,但仍然具有足够的灵活性,不只是将意大利面存储库替换为大量的命令类 例如,我使用了linqspec,虽然我发现能够为选择条件指定有意义的名称有些价值,但这还不够。也许我正在寻找一种结合多种方法的混合解决方案 我正在寻找其他人可能已经开发的解决方案,以解决此问题,或解决不同的问题,但仍然满足这些要求。在链接的文章中,Ayende建议直接使用nHibernate上下文,但我觉得这在很大程度上使您的业务层复杂化,因为它现在还必须包含查询信息 等待期一过,我就悬赏。因此,请让您的解决方案值得赏金,并提供良好的解释,我将选择最好的解决方案,并投票超过亚军 注意:我正在寻找基于ORM的东西。不必显式地使用EF或nHibernate,但它们是最常见的,并且最适合。如果它可以很容易地适应其他ORM,那将是一个额外的好处。Linq兼容也不错 更新:我真的很惊讶这里没有很多好的建议。看起来人们要么完全是CQR,要么完全是知识库阵营。我的大多数应用程序都不够复杂,不足以保证CQR(大多数CQR拥护者都会说,你不应该将其用于任何目的) 更新:这里似乎有点混乱。我不是在寻找一种新的数据访问技术,而是在业务和数据之间寻找一种设计合理的接口C# 设计良好的查询命令和/或规范,c#,repository-pattern,command-pattern,specification-pattern,C#,Repository Pattern,Command Pattern,Specification Pattern,对于典型的存储库模式(用于专门查询的方法越来越多,等等,请参见:)所提出的问题,我已经搜索了相当长的一段时间,寻找一个好的解决方案 我非常喜欢使用命令查询的想法,特别是通过使用规范模式。然而,我对规范的问题是,它只涉及简单选择的标准(基本上是where子句),而不涉及查询的其他问题,例如连接、分组、子集选择或投影等。。基本上,为了获得正确的数据集,许多查询必须经历所有额外的困难 (注意:我在命令模式中使用术语“command”,也称为查询对象。我不是在命令/查询分离中谈论命令,在命令/查询分离中
理想情况下,我要寻找的是查询对象、规范模式和存储库之间的某种交叉。如上所述,规范模式只处理where子句方面,而不处理查询的其他方面,如连接、子选择等。。存储库处理整个查询,但过一段时间就会失控。查询对象也处理整个查询,但我不想简单地用查询对象的爆炸代替存储库。您可以使用流畅的界面。其基本思想是,类的方法在执行某些操作后返回该类的当前实例。这允许您链接方法调用 通过创建适当的类层次结构,可以创建可访问方法的逻辑流
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();
var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
.Where(new CurrentMonthCondition())
.Where(new DivisionCondition{ DivisionType = DivisionType.Production})
.OrderBy(new StandardMonthlyReportSorting())
.ExecuteReader();
您只能创建Query
的新实例。其他类有一个受保护的构造函数。层次结构的要点是“禁用”方法。例如,GroupBy
方法返回一个GroupedQuery
,它是Query
的基类,没有Where
方法(Where方法在Query
中声明)。因此,无法在GroupBy
之后调用Where
然而,它并不完美。使用该类层次结构,可以连续隐藏成员,但不能显示新成员。因此,Having
在GroupBy
之前调用时抛出异常
请注意,可以多次调用Where
。这会将带有和的新条件添加到现有条件中。这使得从单个条件以编程方式构造过滤器变得更容易。如果具有
,则可能出现同样的情况
接受字段列表的方法有一个参数params string[]fields
。它允许您传递单个字段名或字符串数组
Fluent接口非常灵活,不需要创建大量具有不同参数组合的方法重载。我的示例使用字符串,但是该方法可以扩展到其他类型。您还可以为特殊情况或接受自定义类型的方法声明预定义方法。您还可以添加方法,如ExecuteReader
或ExceuteScalar
。这将允许您定义这样的查询
string query = new Query("myTable", "name", "SUM(amount) AS total")
.Where("name LIKE 'A%'")
.GroupBy("name")
.Having("COUNT(*) > 2")
.OrderBy("name")
.ToString();
var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
.Where(new CurrentMonthCondition())
.Where(new DivisionCondition{ DivisionType = DivisionType.Production})
.OrderBy(new StandardMonthlyReportSorting())
.ExecuteReader();
var reader=new查询(new MonthlyReportFields{IncludeSalary=true})
.Where(新的CurrentMonthCondition())
.Where(新DivisionCondition{DivisionType=DivisionType.Production})
.OrderBy(新标准MonthlyReportSorting())
.ExecuteReader();
即使以这种方式构造的SQL命令也可以具有命令参数,从而避免SQL注入问题,同时允许数据库服务器缓存命令。这不是O/R映射器的替代品,但在使用简单字符串连接创建命令的情况下会有所帮助。我处理这一问题的方法实际上过于简单,与ORM无关。我对存储库的看法是:存储库的工作是为应用程序提供上下文所需的模型,因此应用程序只向回购协议询问它想要什么,但没有
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
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();
}
}
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);
}
}
public interface IQueryProcessor
{
TResult Process<TResult>(IQuery<TResult> query);
}
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);
}
}
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);
}
}