Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/kotlin/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 设计良好的查询命令和/或规范_C#_Repository Pattern_Command Pattern_Specification Pattern - Fatal编程技术网

C# 设计良好的查询命令和/或规范

C# 设计良好的查询命令和/或规范,c#,repository-pattern,command-pattern,specification-pattern,C#,Repository Pattern,Command Pattern,Specification Pattern,对于典型的存储库模式(用于专门查询的方法越来越多,等等,请参见:)所提出的问题,我已经搜索了相当长的一段时间,寻找一个好的解决方案 我非常喜欢使用命令查询的想法,特别是通过使用规范模式。然而,我对规范的问题是,它只涉及简单选择的标准(基本上是where子句),而不涉及查询的其他问题,例如连接、分组、子集选择或投影等。。基本上,为了获得正确的数据集,许多查询必须经历所有额外的困难 (注意:我在命令模式中使用术语“command”,也称为查询对象。我不是在命令/查询分离中谈论命令,在命令/查询分离中

对于典型的存储库模式(用于专门查询的方法越来越多,等等,请参见:)所提出的问题,我已经搜索了相当长的一段时间,寻找一个好的解决方案

我非常喜欢使用命令查询的想法,特别是通过使用规范模式。然而,我对规范的问题是,它只涉及简单选择的标准(基本上是where子句),而不涉及查询的其他问题,例如连接、分组、子集选择或投影等。。基本上,为了获得正确的数据集,许多查询必须经历所有额外的困难

(注意:我在命令模式中使用术语“command”,也称为查询对象。我不是在命令/查询分离中谈论命令,在命令/查询分离中,查询和命令(更新、删除、插入)是有区别的。)

因此,我正在寻找封装整个查询的替代方案,但仍然具有足够的灵活性,不只是将意大利面存储库替换为大量的命令类

例如,我使用了linqspec,虽然我发现能够为选择条件指定有意义的名称有些价值,但这还不够。也许我正在寻找一种结合多种方法的混合解决方案

我正在寻找其他人可能已经开发的解决方案,以解决此问题,或解决不同的问题,但仍然满足这些要求。在链接的文章中,Ayende建议直接使用nHibernate上下文,但我觉得这在很大程度上使您的业务层复杂化,因为它现在还必须包含查询信息

等待期一过,我就悬赏。因此,请让您的解决方案值得赏金,并提供良好的解释,我将选择最好的解决方案,并投票超过亚军

注意:我正在寻找基于ORM的东西。不必显式地使用EF或nHibernate,但它们是最常见的,并且最适合。如果它可以很容易地适应其他ORM,那将是一个额外的好处。Linq兼容也不错

更新:我真的很惊讶这里没有很多好的建议。看起来人们要么完全是CQR,要么完全是知识库阵营。我的大多数应用程序都不够复杂,不足以保证CQR(大多数CQR拥护者都会说,你不应该将其用于任何目的)

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


理想情况下,我要寻找的是查询对象、规范模式和存储库之间的某种交叉。如上所述,规范模式只处理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);
    }
}