【问题标题】:How to use subqueries in ServiceStack ORMLite如何在 ServiceStack ORMLite 中使用子查询
【发布时间】:2013-08-21 03:31:52
【问题描述】:

我正在使用 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”未在范围内定义。 我想我只需要以某种方式在我的自定义方法中的第二次访问调用中添加一个参数定义,但我还没有弄清楚怎么做! 任何帮助表示赞赏!

【问题讨论】:

  • 虽然我知道ORMLite新版本可以处理连接,但我宁愿使用另一个ORM比如Dapper来解决两个或多个表之间的连接。我发现 Dapper 在这方面更强大。
  • 我在 Dapper 上没有看到任何支持 Linq to SQL 的文档。另外我觉得我对 ORMLite 代码理解得更好,写得也很好。

标签: linq-to-sql servicestack ormlite-servicestack


【解决方案1】:

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());

【讨论】:

  • 我更多的是寻找一种将所有内容都与 SqlServerExpressionVisitor 联系起来的解决方案。在 API 方面,如果我想让其他开发人员使用它,我宁愿坚持一种 API 流。如果开发人员需要使用 SqlServerExpressionVisitor 和另一个 JoinSqlBuilder 转到两个单独的路径。另外,对于我需要做的事情,不需要加入。我只需要子查询。我不确定 EF 是如何做到这一点的,但是他们通过彻底的 IQueryable 接口使这一切成为可能。
  • 如果您只是想使用 ExpressionVisitor,很遗憾您不能使用子查询。 EF 的工作方式是解析子表达式生成 Sql。如果您只想使用基于 Linq 的解决方案,我建议您查看 linq2db - nuget.org/packages/linq2db && github.com/linq2db/linq2db
猜你喜欢
  • 2014-09-22
  • 1970-01-01
  • 2012-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多