What is the use/advantage of using CommandBehavior.CloseConnection in ExecuteReader()

后端 未结 6 1961
甜味超标
甜味超标 2020-12-14 09:38

Can anyone tell me what is the CommandBehavior.CloseConnection and what is the use/benefit of passing this as a parameter in com.ExecuteReader(CommandBeha

6条回答
  •  一生所求
    2020-12-14 09:53

    Re : What is the advantage of CommandBehavior.CloseConnection?

    Long lived data readers can be useful when you do not necessarily want to retrieve and materialize all data that the query would otherwise return, all-at-once. Although it is possible for your app to directly retain the reference to a long-lived Connection, this can lead to messy architecture, where Data Tier dependencies such as ISqlConnection 'bleeds' up into the business and presentation concerns of your application.

    Lazy data retrieval (i.e. retrieving data only when it is needed) has the benefit that the caller can keep asking for more data until it has satisfied its data requirement - this is common in scenarios requiring data paging or take-while lazy evaluation, until some satisfying condition is met.

    Long-lived connection / lazy data retrieval practices were arguably more commonplace in legacy architectures such as Fat-Client, where a user would scroll through data whilst keeping a connection open, however, there are still uses in modern code.

    There is something of a trade off here: Although there are memory, and network resource overheads for both the App / Client side needed for the duration of the Reader (and Connection), and for keeping 'state' in the RDBMS database side (buffers, or even Cursors, if a PROC using Cursors is executed), there are also benefits:

    • Network IO between consuming app and Database is reduced, as only data which is required is retrieved
    • App memory overhead is reduced, because data which isn't going to be needed isn't retrieved (and if object abstractions are used on top of the DataReader, this will also potentially save unnecessary deserialization / materialization into Entity / DTO / POCOs)
    • By not materializing all data in the query 'all at once', it allows the app to release memory (e.g. DTO's) no longer needed as it moves through the reader

    Preventing IDataReader bleeding into your app

    Nowadays, most apps wrap data access concerns into a Repository pattern or by using an ORM to abstract data access - this usually results in data retrieval returning Entity object(s) rather than working natively with low level IDataReader API's throughout your app.

    Fortunately, it is still possible to use a lazy generator (i.e. method returning IEnumerable AND still retain control over the DataReader (and thus Connection) lifespan, by using a pattern such as this (this is an async version, but obviously sync code would also work, albeit be more thread hungry)

    public Task> LazyQueryAllFoos()
    {
       var sqlConn = new SqlConnection(_connectionString);
       using (var cmd = new SqlCommand(
            "SELECT Id, Col2, ... FROM LargeFoos", sqlConn))
       {
          await mySqlConn.OpenAsync();
          var reader = cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
          // Return the IEnumerable, without actually materializing Foos yet
          // Reader and Connection remain open until caller is done with the enumerable
          return LazyGenerateFoos(reader);
       }
    }
    
    // Helper method to manage lifespan of foos
    private static IEnumerable GenerateFoos(IDataReader reader)
    {
        // Lifespan of the reader is scoped to the IEnumerable
        using(reader)
        {
           while (reader.Read())
           {
              yield return new Foo
              {
                  Id = Convert.ToInt32(reader["Id"]),
                  ...
              };
           }
        } // Reader is Closed + Disposed here => Connection also Closed.
    }
    

    Notes

    • Just as with SqlConnections and DataReaders, the code calling LazyQueryAllFoos still needs to be mindful not to hold onto the Enumerable (or an Iterator of it) for longer than needed, as this will keep the underlying Reader and Connection open.
    • Jon Skeet analyses yield return generators in depth here - the takeaway is that the finally of using blocks will complete in yield iterator blocks once the enumerable has either run to completion, OR thrown an exception (e.g. network failure), OR even if the caller doesn't complete iterating the iterator and it goes out of scope.
    • As at C#6, async code cannot currently also use yield return, hence the need to split the helper method out of the async query (The helper could I believe be moved into a Local Function, however). Things might change in future, e.g. IAsyncEnumerator

提交回复
热议问题