C# fluentapi中的递归

C# fluentapi中的递归,c#,sql,fluent,C#,Sql,Fluent,我正在设计一个用于编写SQL的流畅API。请记住,我的目标之一是让API不建议在链的该部分无法调用的函数。例如,如果您刚刚在select子句中定义了一个字段,那么在第一次调用之前,您不能调用Where。一个简单的查询如下所示: string sql = SelectBuilder.Create() .Select() .Fld("field1") .From("table1")

我正在设计一个用于编写SQL的流畅API。请记住,我的目标之一是让API不建议在链的该部分无法调用的函数。例如,如果您刚刚在select子句中定义了一个字段,那么在第一次调用之前,您不能调用Where。一个简单的查询如下所示:

        string sql = SelectBuilder.Create()
            .Select()
                .Fld("field1")
            .From("table1")
            .Where()
                .Whr("field1 > field2")
                .Whr("CURRENT_TIMESTAMP > field3")
            .Build()
            .SQL;
我的问题来自SQL代码中的递归。假设您希望一个字段包含另一个SQL语句,如下所示:

        string sql = SelectBuilder.Create()
            .Select()
                .Fld("field1")
                .SQLFld()
                    .Select
                        .Count("field6")
                    .From("other table")
                .EndSQLFld()
                .FLd("field2")
            .From("table1")
            .Where()
                .Whr("field1 > field2")
                .Whr("CURRENT_TIMESTAMP > field3")
            .Build()
            .SQL;
我正在使用方法链接来构建我的fluent API。它在很多方面都是一个状态机,分布在代表每个状态的许多类中。要添加此功能,我需要复制我已经拥有的每个状态,并将它们包装在两个SQLFld和ENDSSQLFLD状态周围。如果您再往下一层,并将SQL语句嵌入到已嵌入SQL语句的字段中,我还需要另一个副本。这是无限的,因此对于无限深的嵌入式SQL查询,我需要无限多的类来表示无限的状态

我曾考虑编写一个SelectBuilder查询,该查询被带到构建方法的点,然后将该SelectBuilder嵌入到另一个SelectBuilder中,从而解决我的无穷大问题,但它不是非常优雅,这就是这个API的要点

我也可以抛弃API只在适当的时候提供函数的想法,但我真的不愿意这样做。我觉得这可以帮助您更好地了解如何使用API。在许多fluentapi中,调用什么顺序并不重要,但我希望API尽可能接近实际的SQL语句,并强制执行其语法


有人知道如何解决这个问题吗?

很高兴看到您尝试使用流畅的界面,我认为它们非常优雅且富有表现力

构建器模式不是fluent接口的唯一实现。考虑这个设计,让我们知道你的想法=)

这是一个示例,我将把最终实现的细节留给您

界面设计示例:

 public class QueryDefinition
    {
        // The members doesn't need to be strings, can be whatever you use to handle the construction of the query.
        private string select;
        private string from;
        private string where;

        public QueryDefinition AddField(string select)
        {
            this.select = select;
            return this;
        }

        public QueryDefinition From(string from)
        {
            this.from = from;
            return this;
        }

        public QueryDefinition Where(string where)
        {
            this.where = where;
            return this;
        }

        public QueryDefinition AddFieldWithSubQuery(Action<QueryDefinition> definitionAction)
        {
            var subQueryDefinition = new QueryDefinition(); 
            definitionAction(subQueryDefinition);

            // Add here any action needed to consider the sub query, which should be defined in the object subQueryDefinition.

            return this;
        }
公共类查询定义
{
//成员不需要是字符串,可以是用于处理查询构造的任何内容。
私有字符串选择;
来自的私有字符串;
私有字符串在哪里;
公共查询定义添加字段(字符串选择)
{
this.select=select;
归还这个;
}
公共查询定义来源(字符串来源)
{
this.from=from;
归还这个;
}
公共查询定义Where(字符串Where)
{
this.where=where;
归还这个;
}
公共查询定义AddFieldWithSubQuery(操作定义操作)
{
var subQueryDefinition=新的QueryDefinition();
定义动作(子查询定义);
//此处添加考虑子查询所需的任何操作,这些子查询应在对象子查询定义中定义。
归还这个;
}
用法示例:

static void Main(string[] args)
        {
            // 1 query deep
            var def = new QueryDefinition();
            def
                .AddField("Field1")
                .AddField("Filed2")
                .AddFieldWithSubQuery(subquery =>
                {
                    subquery
                        .AddField("InnerField1")
                        .AddField("InnerFiled2")
                        .From("InnerTable")
                        .Where("<InnerCondition>");
                })
                .From("Table")
                .Where("<Condition>");


            // 2 queries deep
            var def2 = new QueryDefinition();
            def2
                .AddField("Field1")
                .AddField("Filed2")
                .AddFieldWithSubQuery(subquery =>
                {
                    subquery
                        .AddField("InnerField1")
                        .AddField("InnerField2")
                        .AddFieldWithSubQuery(subsubquery =>
                            {
                                subsubquery
                                    .AddField("InnerInnerField1")
                                    .AddField("InnerInnerField2")
                                    .From("InnerInnerTable")
                                    .Where("<InnerInnerCondition>");
                            })
                        .From("InnerInnerTable")
                        .Where("<InnerCondition>");
                })
                .From("Table")
                .Where("<Condition>");
        }
static void Main(字符串[]args)
{
//1查询深度
var def=新的querydefination();
def
.AddField(“Field1”)
.AddField(“Filed2”)
.AddFieldWithSubQuery(子查询=>
{
子查询
.AddField(“InnerField1”)
.AddField(“InnerFiled2”)
.From(“InnerTable”)
.其中(“”);
})
.来自(“表格”)
.其中(“”);
//2.深度查询
var def2=新的QueryDefinition();
def2
.AddField(“Field1”)
.AddField(“Filed2”)
.AddFieldWithSubQuery(子查询=>
{
子查询
.AddField(“InnerField1”)
.AddField(“InnerField2”)
.AddFieldWithSubQuery(subsubquery=>
{
子查询
.AddField(“innerfield1”)
.AddField(“innerfield2”)
.From(“InnerTable”)
.其中(“”);
})
.From(“InnerTable”)
.其中(“”);
})
.来自(“表格”)
.其中(“”);
}
如果没有子结构的子API或清除所有内部结构层(选择列、WHERE子句中的表达式、子查询)的括号/结尾,就不能“只有适用的方法可用”

即使这样,通过一个API运行它也需要它是有状态的,带有“括号”方法的“模态”,以跟踪您所在的decl中的位置。错误报告和正确获取这些信息将是乏味的

在我看来,用“fluent”方法结束括号似乎不流畅且难看。这将导致出现
EndSelect
EndWhere
EndSubquery
等难看的外观。我更喜欢将子结构(例如select的子查询)构建到局部变量中并添加它

我不喜欢
EndSQLFld()
习惯用法,它通过终止字段来隐式终止子查询。我更喜欢&我想最好的设计是终止子查询本身,它是嵌套结构的复杂部分,而不是字段

老实说,试图为“声明性”语言(SQL)强制执行“声明性”API的顺序似乎是浪费时间


可能是我认为更接近于理想用法:

SelectBuilder select = SelectBuilder.Create("CUSTOMER")
        .Column("ID")
        .Column("NAME")
        /*.From("CUSTOMER")*/      // look, I'm just going to promote this onto the constructor.
        .Where("field1 > field2")
        .Where("CURRENT_TIMESTAMP > field3");

SelectBuilder countSubquery = SelectBuilder.Create("ORDER")
        .Formula("count(*)");
        .Where("ORDER.FK_CUSTOMER = CUSTOMER.ID");
        .Where("STATUS = 'A'");

select.Formula( countSubquery, "ORDER_COUNT");
string sql = SelectBuilder.SQL;

向Hibernate Criteria API致歉:)

这里我还以为您在谈论递归CTE。请注意,子查询可以包含引用