LINQ to SQL * скомпилированные * запросы и когда они выполняются - PullRequest
9 голосов
/ 06 июля 2011

У меня есть следующий скомпилированный запрос.

private static Func<Db, int, IQueryable<Item>> func =
        CompiledQuery.Compile((Db db, int id) => 
            from i in db.Items
            where i.ID == id
            select i
            );

Это выполняется в базе данных немедленно, когда я

var db = new Db()
var query = func(db, 5);  // Query hits the database here

Как и в до в

var result = query.SingleOrDefault(); // Happens in memory

Но если этот запрос не был скомпилирован, как в

var query = from i in db.Items
            where i.ID == id
            select i

затем выполняется в базе данных после , выполняющего

   var result = query.SingleOrDefault();

Это ожидаемое поведение?

Примечание. Это дубликат Когда выполняется скомпилированный запрос, который возвращает IQueryable? , но все ответы там, похоже, не согласны с моими выводами. Я разместил там свой ответ, но не знаю, как привлечь к нему внимание людей, так как ему более 2 лет.

Ответы [ 3 ]

8 голосов
/ 06 июля 2011

Интересный вопрос. Принимая это к декомпилированным источникам, когда вы компилируете запрос, вот что происходит:

public static Func<TArg0, TArg1, TResult> Compile<TArg0, TArg1, TResult>(Expression<Func<TArg0, TArg1, TResult>> query) where TArg0 : DataContext
{
  if (query == null)
    System.Data.Linq.Error.ArgumentNull("query");
  if (CompiledQuery.UseExpressionCompile((LambdaExpression) query))
    return query.Compile();
  else
    return new Func<TArg0, TArg1, TResult>(new CompiledQuery((LambdaExpression) query).Invoke<TArg0, TArg1, TResult>);
}

Метод UseExpressionCompile определяется следующим образом:

private static bool UseExpressionCompile(LambdaExpression query)
{
  return typeof (ITable).IsAssignableFrom(query.Body.Type);
}

Это значение равно false для выражения, которое вы определили, поэтому используется регистр else.

Invoke выглядит так:

private TResult Invoke<TArg0, TArg1, TResult>(TArg0 arg0, TArg1 arg1) where TArg0 : DataContext
{
  return (TResult) this.ExecuteQuery((DataContext) arg0, new object[2]
  {
    (object) arg0,
    (object) arg1
  });
}

ExecuteQuery похож на:

private object ExecuteQuery(DataContext context, object[] args)
{
  if (context == null)
    throw System.Data.Linq.Error.ArgumentNull("context");
  if (this.compiled == null)
  {
    lock (this)
    {
      if (this.compiled == null)
        this.compiled = context.Provider.Compile((Expression) this.query);
    }
  }
  return this.compiled.Execute(context.Provider, args).ReturnValue;
}

В этом случае нашим провайдером является класс SqlProvider, а SqlProvider.CompiledQuery - это класс, реализующий ICompiledQuery. Выполнить на этом классе реализовано:

  public IExecuteResult Execute(IProvider provider, object[] arguments)
  {
    if (provider == null)
      throw System.Data.Linq.SqlClient.Error.ArgumentNull("provider");
    SqlProvider sqlProvider = provider as SqlProvider;
    if (sqlProvider == null)
      throw System.Data.Linq.SqlClient.Error.ArgumentTypeMismatch((object) "provider");
    if (!SqlProvider.CompiledQuery.AreEquivalentShapes(this.originalShape, sqlProvider.services.Context.LoadOptions))
      throw System.Data.Linq.SqlClient.Error.CompiledQueryAgainstMultipleShapesNotSupported();
    else
      return sqlProvider.ExecuteAll(this.query, this.queryInfos, this.factory, arguments, this.subQueries);
  }

SqlProvider.ExecuteAll вызывает SqlProvider.Execute, который является довольно большим методом, поэтому я опубликую основные моменты:

private IExecuteResult Execute(Expression query, SqlProvider.QueryInfo queryInfo, IObjectReaderFactory factory, object[] parentArgs, object[] userArgs, ICompiledSubQuery[] subQueries, object lastResult)
{
  this.InitializeProviderMode();
  DbConnection dbConnection = this.conManager.UseConnection((IConnectionUser) this);
  try
  {
    DbCommand command = dbConnection.CreateCommand();
    command.CommandText = queryInfo.CommandText;
    command.Transaction = this.conManager.Transaction;
    command.CommandTimeout = this.commandTimeout;
    this.AssignParameters(command, queryInfo.Parameters, userArgs, lastResult);
    this.LogCommand(this.log, command);
    ++this.queryCount;
    switch (queryInfo.ResultShape)
    {
      case SqlProvider.ResultShape.Singleton:
        DbDataReader reader1 = command.ExecuteReader();
...
      case SqlProvider.ResultShape.Sequence:
        DbDataReader reader2 = command.ExecuteReader();
...
      default:
        return (IExecuteResult) new SqlProvider.ExecuteResult(command, queryInfo.Parameters, (IObjectReaderSession) null, (object) command.ExecuteNonQuery(), true);
    }
  }
  finally
  {
    this.conManager.ReleaseConnection((IConnectionUser) this);
  }
}

В промежутке между установлением и освобождением соединения он использует команды sql. Так что я бы сказал, что ты прав. Вопреки распространенному мнению, скомпилированные запросы не ведут себя так же, как некомпилированные запросы, когда речь идет об отложенном выполнении.

Я почти уверен, что вы можете загрузить реальный исходный код из MS, но у меня его нет под рукой, и у Resharper 6 есть потрясающий переход к декомпилированной функции, поэтому я просто использовал это.

1 голос
/ 12 июля 2011

Мне нечего добавить к ответу Эндрю Барретта, кроме этого:

  • Это верно (т. Е. Запрос обращается к базе данных), когда вы вызываете делегат, возвращенный CompiledQuery.Compile () только для LINQ to SQL.
  • Если вы используете LINQ to Entities, это НЕ верно.Запрос не попадает в базу данных при вызове делегата, он делает это только тогда, когда вы начинаете извлекать данные.Поведение в соответствии с некомпилированными запросами.
0 голосов
/ 06 июля 2011

Да, это правильно.Он не пойдет и ничего не получит, пока вы не попросите об этом.

Проверьте MSDN на Отложенная или немедленная загрузка .В частности, вы можете включить / выключить ленивую загрузку .

Посмотрите на самый голосующий ответ на этот вопрос, где создается окончательный список .Там есть оператор select, который отправляет его и запрашивает результат.LINQ будет ждать как можно дольше, чтобы отправить запрос в базу данных.

Кстати, вы можете легко исследовать это, установив свойство DataContext.Log:

db.Log = Console.Out;

ТогдаВы можете смотреть операторы SQL на консоли.Проходя по программе, вы можете точно определить, когда оператор SQL попадает в базу данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...