2013-08-21 5 views
1

Я использую ServiceStack ORMLite и необходимо выполнить запрос, например ниже:Как использовать подзапросы в 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()); 

Когда выполнить этот запрос, я получаю ошибку лямбда «а» не определен в рамках , «a» здесь ссылка на переменную, определенную для вызова метода Where.

Как я могу использовать ExpressionVisitor для выполнения подзапросов в предложении WHERE?

UPDATE: Я создал свой собственный 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, однако, если я включать ссылку из родительского запроса, A получить системные вызовы 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» не определен в области видимости. Я предполагаю, что мне нужно лишь каким-то образом добавить определение параметра ко второму вызову посещения в моем пользовательском методе, но я еще не понял, как это сделать! Любая помощь приветствуется!

+0

Хотя я знаю ORMLite новой версии может обрабатывать соединения, я предпочел бы использовать другой ORM, такие как Dapper решить объединение между двумя или более таблицами. Я считаю Dapper более сильным в этой части. – coffekid

+0

Я не видел никакой документации по Dapper, которая поддерживала бы Linq to SQL. Также я чувствую, что лучше понимаю код ORMLite, и он написан очень красиво. – sam360

ответ

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 не является полезным. Я лично использую DbExtensions от http://www.nuget.org/packages/DbExtensions/ вместе с Ormlite. Так как я продлил T4 Ormlite для автоматического создания имени столбца, таблица, я использую 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()); 
+0

Я больше искал решение, которое связывает все с SqlServerExpressionVisitor. Что касается API, если я хочу, чтобы другие разработчики использовали его, я предпочел бы придерживаться одного потока API. В случае, если разработчик должен перейти на два отдельных пути, используя SqlServerExpressionVisitor и другой JoinSqlBuilder. Плюс к тому, что мне нужно сделать, соединение не требуется. Мне нужен только подзапрос. Я не уверен, как это делает EF, но с полным интерфейсом IQueryable они сделали все это возможным. – sam360

+1

Если вы просто хотите использовать ExpressionVisitor, к сожалению, вы не можете использовать подзапросы с ним. Способ работы EF заключается в том, что он анализирует дочерние выражения, генерирующие Sqls. Если вы просто хотите использовать решение Linq, я бы предложил вам посмотреть на linq2db - http://www.nuget.org/packages/linq2db/ && https://github.com/linq2db/linq2db –

Смежные вопросы