вторник, 3 ноября 2009 г.

Использование C# dynamic для упрощения доступа к ADO.NET данным

Сценарий таков, что вы не используете какой-либо ORM такие как LINQ to SQL или Entity Framework, но вы напрямую используете ADO.NET для выполнения SQL-команд. Это вообще не принятый мною способ, но много народа предпочитает его более высокому уровня доступа к данным.
Давайте посмотрим на пример, который мы собираемся улучшить. Возьмем пример из MSDN о SqlCommand:

 string commandText = "SELECT OrderID, CustomerID FROM dbo.Orders;";  
 using (var connection = new SqlConnection(Settings.Default.NorthwindConnectionString)) {  
   using (var command = new SqlCommand(commandText, connection)) {  
     connection.Open();  
     using (SqlDataReader reader = command.ExecuteReader()) {  
       while (reader.Read()) {  
         Console.WriteLine(String.Format("{0}, {1}", reader[0], reader[1]));  
       }  
     }  
   }  
 }  

Давайте сейчас предположим, что мы мы заинтересованы только в одном select-запросе, который даст возможность нам несколько абстрагироваться от деталей в SQL. Написав несколько небольших хелперов, который используют dynamic, мы сможем написать довольно простой код:

 string commandText = "SELECT OrderID, CustomerID FROM dbo.Orders;";  
 foreach (var row in SimpleQuery.Execute(Settings.Default.NorthwindConnectionString, commandText)) {  
   Console.WriteLine(String.Format("{0}, {1}", row.OrderID, row.CustomerID));  
 }  

Некоторые примечания:
Нам достаточно сделать единственный вызов и прямо получить объекты с которыми мыможем работать. Сравните это с использованием SqlConnection, SqlCommand и SqlDataReader.
Мы используем стандартный шаблон перечисления, в котором SqlDataReader делает вызов reader.Read() на каждой итерации, что выглядит ужасно.
И еще одно важное: мы получаем прямой доступ к свойствам объекта строки благодаря динамическому объекту! Например мы можем написать row.OrderID вместо reader[0](или reader[“OrderID”])
Как же это работает? Первое, давайте посмотрим на SimpleQuery.Execute метод:

 public static IEnumerable<dynamic> Execute(string connString, string commandText) {  
   using (var connection = new SqlConnection(connString)) {  
     using (var command = new SqlCommand(commandText, connection)) {  
       connection.Open();  
       using (SqlDataReader reader = command.ExecuteReader()) {  
         while (reader.Read()) {  
           yield return new DataRecordDynamicWrapper(reader);  
         }  
       }  
     }  
   }  
 }  

Это собственно тоже самое что и в MSDN коде за исключением что оно оборачивает возвращаемый reader в DataRecordDynamicWrapper, который делает всю волшебную динамическую работу. Также заметьте что метод возвращает IEnumerable благодаря которому собственно мы и можем писать 'var row' в тестовом коде (который по-моему выглядит лучше чем 'dynamic row').
Так что сейчас все что осталось так это взглянуть на DataRecordDynamicWrapper, который невероятно прост:
 public class DataRecordDynamicWrapper : DynamicObject {  
   private IDataRecord _dataRecord;  
   public DataRecordDynamicWrapper(IDataRecord dataRecord) { _dataRecord = dataRecord; }  
   public override bool TryGetMember(GetMemberBinder binder, out object result) {  
     result = _dataRecord[binder.Name];  
     return result != null;  
   }  
 }  

Все что он делает, так это получает данные по индексу в _dataRecord для заданного имени свойства.
Единственная последняя вещь, которая ничего не стоит для того чтобы сделать реальной, мы должны добавить поддержку SQL параметров, которые дадут простоту написания SQL кода, не подверженного SQL-инъекциям. Это может быть просто сделано передавая параметры в SimpleQuery.Execute.
Вольный перевод статьи David Ebbo (Исходный код там же)

Комментариев нет:

Отправить комментарий