1

我正在使用 ServiceStack ORMLite,需要执行如下查询:

SqlServerExpressionVisitor<Contact> sql = new SqlServerExpressionVisitor<Contact>();
SqlServerExpressionVisitor<Account> accSql = new SqlServerExpressionVisitor<Account>();

var finalSql = sql.Where(a=> 
   (from a1 in accSql where a1.Type == "Client" 
   && a1.Id==a.AccountId select a1).Any());

执行此查询时,我得到一个 lambda 错误“a”未在范围内定义。这里的“a”是对父 Where 方法调用定义的变量的引用。

如何使用 ExpressionVisitor 在 WHERE 子句中执行子查询?

更新:我创建了自己的 SqlServiceExpressionVisitor,它允许我自定义 ORM 如何生成 SQL 语句。我还添加了如下类:

public static class SQL
{
    public static bool ExistsIn<T>(T Value, string subquery, params T[] parameters)
    {
        return true;
    }

    public static bool ExistsIn<T, TItem>(T Value, SqlExpressionVisitor<TItem> innerSql)
    {
        return true;
    }

    public static SqlExpressionVisitor<T> Linq<T>() 
    {
        return OrmLiteConfig.DialectProvider.ExpressionVisitor<T>();
    }
}

然后扩展 VisitMethodCall 以考虑我的新类并相应地调用我的自定义方法:

internal object VisitSQLMethodCall(MethodCallExpression m)
{
    string methodName = m.Method.Name;
    if (methodName == "ExistsIn")
    {
        string quotedColName = Visit(m.Arguments[0] as Expression).ToString();
        dynamic visit = Visit(m.Arguments[1]);

        string innerQuery = (visit is string) ? visit.ToString().Trim('"') : visit.ToSelectStatement();
        if (m.Arguments[2] != null)
        {
            List<object> fields = VisitExpressionList((m.Arguments[2] as NewArrayExpression).Expressions);
            int count = 0;
            foreach (var field in fields)
            {
               innerQuery = innerQuery.Replace("@" + count.ToString(), field.ToString());
               count++;
            }
        }

        return new PartialSqlString(string.Format("{0} IN ({1})", quotedColName, innerQuery));
    }
}

结果非常有希望,以下是它的使用方法:

.Where(a => SQL.ExistsIn(a.AccountId, SQL.Linq<Account>()
    .Where(acc => acc.Name.Contains("a")).Select(acc => acc.Id)))

上面生成了正确的内部 SQL,但是如果我包含来自父查询的引用,系统再次调用 return Expression.Lambda(m).Compile().DynamicInvoke(); 产生相同的错误!

SQL.Linq<Contact>().Where(a => SQL.ExistsIn(a.AccountId, SQL.Linq<Account>()
  .Where(acc => acc.Id == a.AccountId).Select(acc => acc.Id)))

以上产生错误:参数“a”未在范围内定义。我想我只需要以某种方式在我的自定义方法中的第二次访问调用中添加一个参数定义,但我还没有弄清楚怎么做!任何帮助表示赞赏!

4

1 回答 1

4

Wiki 中没有文档;您需要查看代码以了解如何使用连接功能。对于您的问题,代码应如下所示:

        var jb = new JoinSqlBuilder<Contact, Account>()
            .Join<Contact, Account>(x => x.AccountId, x => x.Id)
            .Where<Account>(x => x.Type == "Client")
            .SelectCount<Contact>(x => x.Id);
        var sqlStr = jb.ToSql();
        bool isAvailable = dbManager.Connection.SqlScalar<int>(sqlStr) > 0;

对于复杂的子查询,当前版本的 JoinBuilder 没有用处。我个人使用来自http://www.nuget.org/packages/DbExtensions/的 DbExtensions 和 Ormlite。由于我已经扩展了 Ormlite 的 T4 以自动生成列名 Table,因此我使用了这样的 DbExtension:

        SqlBuilder builder = new SqlBuilder();
        builder = builder.SELECT("*").
                FROM(Contact.TABLE_NAME).WHERE(Contact.COLUMN_AccountId +" = " + id.ToString());
        var sql = builder.ToString();
        return dbConnection.FirstOrDefault<Contact>(sql, builder.ParameterValues.ToArrayEx());
于 2013-08-27T18:40:02.820 回答