Dapper的嵌套`using`语句 - 澄清?

14

我正在学习Dapper在幕后是如何工作的。

然而,我看到了这个不理解的释放模式。

大致上来说 - 这里QueryAsync 的实现方式:

/*1*/   public async Task<IEnumerable<T>> QueryAsync<T>(string sql, Func<IDataRecord, T> projector, DbConnection _conn, dynamic param = null)
/*2*/   {
/*3*/   
/*4*/       DbDataReader reader = null;
/*5*/       bool wasClosed = _conn.State == ConnectionState.Closed;
/*6*/       try
/*7*/       {
/*8*/   
/*9*/           using (var cmd = _conn.CreateCommand())
/*10*/          {
/*11*/          if (param!=null)
/*12*/              foreach (var prop in param.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
/*13*/              {
/*14*/                  var parameter = cmd.CreateParameter();
/*15*/                  parameter.ParameterName = prop.Name;
/*16*/                  parameter.Value = prop.GetValue(param, null);
/*17*/                  cmd.Parameters.Add(parameter);
/*18*/              }
/*19*/   
/*20*/              await _conn.OpenAsync().ConfigureAwait(false);
/*21*/              cmd.CommandTimeout = 100000;
/*22*/              cmd.CommandText = sql;
/*23*/              reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
/*24*/              List<T> buffer = new List<T>();
/*25*/              while (await reader.ReadAsync().ConfigureAwait(false)) buffer.Add(projector(reader));
/*26*/              return buffer;
/*27*/          }
/*28*/   
/*29*/      }
/*30*/      finally
/*31*/      {
/*32*/          using (reader) { }
/*33*/          if (wasClosed) _conn.Close();
/*34*/      }
/*35*/   }

我可以理解为什么他没有在连接上使用 using ,那是因为他想通过 wasClosed 变量有条件地关闭连接。
为了做到这一点-他必须使用 try/finally 语句。(这样有条件的关闭将在finally语句中进行)

但我的问题是关于第32行。

他可以做的不是在 finally 语句中使用 using ,而是:

using (DbDataReader reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false))
{
    List<T> buffer = new List<T>();
    while (await reader.ReadAsync().ConfigureAwait(false)) buffer.Add(projector(reader));
    return buffer;
}

所以finally子句被留下:

finally
{
    //using (reader) { } //removed
    if (wasClosed) _conn.Close();
}

问题

我在 Dapper 中多次看到 finally 子句中使用 using 从句。

可能是我遗漏了什么,但这种模式实现了什么,而我的建议却没有?


2
这看起来像是一个缺陷。你的建议更好。可能没有什么充分的理由来支持这样做。 - usr
1个回答

7
我虽然不像@MarcGravell那样专业,但我认为你缺少了一件事情。你粘贴的代码与你引用的链接并不完全匹配,相关的代码路径如下:
try
{
     if (command.Buffered)
     {
         List<T> buffer = new List<T>();
         while (await reader.ReadAsync(cancel).ConfigureAwait(false))
         {
             buffer.Add((T)func(reader));
         }
         while (await reader.NextResultAsync().ConfigureAwait(false)) { }
         command.OnCompleted();
         return buffer;
     }
     else
     {
         // can't use ReadAsync / cancellation; but this will have to do
         wasClosed = false; // don't close if handing back an open reader; 
                            // rely on the command-behavior.

         var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
         reader = null; // to prevent it being disposed before the caller gets to see it
         return deferred;
     }
}
finally
{
    using (reader) { } // dispose if non-null
    if (wasClosed) cnn.Close();
}

该方法可以返回缓冲结果(由command.Buffered标志指示)或延迟迭代器。如果Marc使用using语句包装读取器并返回迭代器,则在调用站点执行它时,读取器将被处理掉。通过在返回延迟结果之前的行中将读取器设置为null,他防止了读取器被处理,因为finally块中的using将被转换为这个形式:
finally
{
    IDisposable disposable = reader;
    try
    {
    }
    finally
    {
        if (dispoable != null)
        {
            disposable.Dispose();
        }
    }
}

当他将读取器设置为null时,它并没有被释放,迭代器中的引用仍然存在,并指向读取器。这样一来,他既可以在正常代码路径中处理读取器,又可以在请求延迟迭代器时保持其存活状态。

不错的发现。顺便问一下,你知道他为什么写“无法使用ReadAsync”吗?除了取消标记之外,还会发生什么事情?我不确定我理解这里缓冲的作用。 - Royi Namir
@RoyiNamir 我认为 无法使用 ReadAsync 是因为他无法返回一个异步迭代器。你不能yield return一个异步操作。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接