Короткий синтаксис ADO .NET (часть III)
Короткий синтаксис ADO .NET (часть IV)
В предыдущей части я привел перечень действий, необходимых для выполнения запроса и корректного овсобождения ресурсов. Начнем с открытия и закрытия соединения. В пределе хочется иметь сущность, которая бы:
- анализировала состояние указанного соединения, и открывала бы его, если оно не открыто;
- гарантированно закрывала бы соединение после использования, в случае если соединение было первоначально закрыто;
- обращение к такой конструкции не занимало бы много места.
public class ConnectionSession : IDisposable { public ConnectionSession(IDbConnection dbConnection) { if (dbConnection == null) { throw new ArgumentNullException("dbConnection"); } DbConnection = dbConnection; WasOpened = ConnectionState.Open == (DbConnection.State & ConnectionState.Open); if (!WasOpened) { DbConnection.Open(); } } public IDbConnection DbConnection { get; private set; } public bool WasOpened { get; private set; } public void Dispose() { if (!WasOpened) { DbConnection.Close(); } } }Прежде чем начну комментировать код, хочу заметить, что в связи с грядущим переходом на
Как я уже сказал, интерфейс IDisposable реализован чисто для поддержания короткого синтаксиса using. Сей объект не нуждается в финализации и DbConnection сам будет освобождать необходимые ресурсы при сборке мусора. Быть может когда-нибудь я уделю связке using/IDisposable больше внимания.
Почему я использовал IDbConnection интерфейс, вместо базового класса DbConnection? Очень просто. При тестировании я могу подменить объект без средств тестирования, использующих инструментацию сборок. Для генерации mock объектов и заглушек (stub) я использую
В постах я буду приводить некоторые тесты, но далеко не все, которые пишу.
[TestMethod] public void UsingConstructionWithOpenedConnectionTest() { var connection = MockRepository.GenerateStub<IDbConnection>(); connection.Stub(x => x.State).Return(ConnectionState.Closed); using (new ConnectionSession(connection)) { connection.AssertWasCalled(x => x.Open()); connection.AssertWasNotCalled(x => x.Close()); } connection.AssertWasCalled(x => x.Close()); } [TestMethod] public void UsingConstructionWithClosedConnectionTest() { var connection = MockRepository.GenerateStub<IDbConnection>(); connection.Stub(x => x.State).Return(ConnectionState.Open); using (new ConnectionSession(connection)) { connection.AssertWasNotCalled(x => x.Open()); } connection.AssertWasNotCalled(x => x.Close()); }Несмотря на то, что в каждом тесте рекомендуется проверять не более одного условия, я напичкал их множеством условий, потому как я тестировал поведение ConnectionSession в рамках конструкции using. Это уже даже не unit тест, а некий интеграционный. Unit тесты класса ConnectionSession я опустил. Их больше, чем хотелось бы вставлять в пост. Оговорки в условиях к ConnectionSession (вначале поста) нужны для безопасного использования соединения в области действия нескольких вложенных блоков using для ConnectionSession объектов. В рамках C# 3.0 будет удобен следующй extension метод:
public static class ConnectionSessionExtensions { public static ConnectionSession CreateSession(this IDbConnection connection) { return new ConnectionSession(connection); } }это позволит писать код в стиле
using (connection.CreateSession()) { return command.ExecuteNonQuery(); }Довольно емкий смысл вложен в одну строчку:
если соединение не открыто, то открыть его, если было закрыто, то закрыть после выхода из блока.Уже этот код позволит многим сделать обращения к ADO (и не только) более лаконичными. Но на этом не остановимся, продолжение следует.
Не совсем понятно, почему в методе Dispose класса ConnectionSession стоит проверка
ОтветитьУдалитьif (!WasOpened) {...}
Может быть, должно быть
if (WasOpened) {...} ?
WasOpened хранит признак того, было ли открыто соединение до создания экземпляра ConnectionSession. И если оно было открыто, то за закрытие соединения должен отвечать тот, кто его открыл. А если оно не было открыто и его открыл именно этот экземпляр сессии, то в этом случае следует закрыть соединение.
ОтветитьУдалитьТакая проверка позволяет использовать несколько вложенных сессий с одним соединением.