Reputation: 1131
I am using ServiceStack ORMLite and need to perform a query such below:
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());
When perform this query, i get a lambda error "a" is not defined in the scope. "a" here is the reference to the variable defined for the parent Where method call.
How can I use the ExpressionVisitor to perform subqueries in the WHERE clause?
UPDATE: I created my own SqlServiceExpressionVisitor which allows me to customize how ORM generates the SQL statements. I also added class such as below:
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>();
}
}
Then extended the VisitMethodCall to take my new class into account and call my custom method accordingly:
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));
}
}
The results are very promising, here is how it can be used:
.Where(a => SQL.ExistsIn(a.AccountId, SQL.Linq<Account>()
.Where(acc => acc.Name.Contains("a")).Select(acc => acc.Id)))
Above generates the proper inner SQL, however if I include a reference from the parent Query, again the system calls the return Expression.Lambda(m).Compile().DynamicInvoke(); which produce the same error!
SQL.Linq<Contact>().Where(a => SQL.ExistsIn(a.AccountId, SQL.Linq<Account>()
.Where(acc => acc.Id == a.AccountId).Select(acc => acc.Id)))
The above generates the error: parameter "a" is not defined in the scope. I guess i only need to somehow add a parameter definition to the second Visit call in my custom method but I have not figured out how yet! Any help is appreciated!
Upvotes: 1
Views: 3803
Reputation: 414
There is no documentation in the Wiki; you need to look at the code to see how to use the join feature. For your problem the code should look something like this :
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;
For complex subqueries, the current version of JoinBuilder is not useful. I personally use DbExtensions from http://www.nuget.org/packages/DbExtensions/ along with Ormlite. Since I have extended Ormlite's T4 to automatically generate the Column name, Table, I use the DbExtension like this :
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());
Upvotes: 4