ORM или простое наполнение класса данными из хранимой процедуры

    Привет всем хабраюзерам, решил написать первую свою статью, которая, продолжает серию очерков про взаимодействие с БД. Так случилось что подвернулся небольшой вэб-проект для реализации. В качестве платформы был выбран ASP.NET MVC + ExtJS но вот решение для ORM сходу не нашлось. Проблема заключалась в том что привлекать большое промышленное ORM решение типа NHibernate или Entity Framework не хотелось, так как проект будет иметь от силы два-три десятка хранимых процедур. Одновременно с этим использовать обвертку от Microsoft DAAB тоже не получается, т.к. в MVC фреймворке модели являются по сути копиями таблиц БД (ну просто для упрощения будем так считать) в результате Reader-ы, DataSet-ы и DataTable-ы нам мало чем могут помочь. Возможно хорошим решением было бы использовать LinqToSql, но мне очень не нравиться когда запросы написаны не на SQL а на C#, я твердо убеждён что общение между приложением и БД должно происходить только посредством хранимых процедур и функций. Другими словами нам нужен маппинг класа на result set хранимой процедуры и простота его использования, что бы просто вызвать хелпер-метод и он, используя рефлексию, вернул коллекцию экземпляров наполненными данными из процедуры. Думаю суть проблемы я изложил достаточно понятно итак приступим к реализации.

    Для начала объявим наш класс который будем наполнять данными, само собой, что он будет полностью копировать поля которые возвращает хранимая процедура:
    public class Book
    {
     public int ID { get; set; }
     public string Title { get; set; }
     public string Author { get; set; }
     public DateTime PublicationDate { get; set; }
    }


    * This source code was highlighted with Source Code Highlighter.

    А вот и хелпер-метод для маппинга:
    public static List<T> GetSpResultset<T>(string spName)
    {
      List<T> list = new List<T>();
      SqlConnection connection = new SqlConnection("строка соединения");
     
      connection.Open();

      using (SqlCommand command = new SqlCommand(spName, connection))
      {
       command.CommandType = CommandType.StoredProcedure;
       using (IDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
       {
        PropertyInfo[] fields = typeof(T).GetProperties();
        while (reader.Read())
        {
          T record = Activator.CreateInstance<T>();
          foreach (PropertyInfo pi in fields)
          {
           if (pi.PropertyType.Name == typeof(Int32).Name)
           {
            if (pi.CanWrite)
            {
             int value = reader.GetInt32(reader.GetOrdinal(pi.Name));
             pi.SetValue(record, value, null);
            }
           }
           else if (pi.PropertyType.Name == typeof(Int16).Name)
           {
            if (pi.CanWrite)
            {
             short value = Convert.ToInt16(reader.GetValue(reader.GetOrdinal(pi.Name)));
             pi.SetValue(record, value, null);
            }
           }
           else if (pi.PropertyType.Name == typeof(String).Name)
           {
            if (pi.CanWrite)
            {
             string value = reader.GetString(reader.GetOrdinal(pi.Name));
             pi.SetValue(record, value, null);
            }
           }
           else if (pi.PropertyType.Name == typeof(DateTime).Name)
           {
            if (pi.CanWrite)
            {
             DateTime value = reader.GetDateTime(reader.GetOrdinal(pi.Name));
             pi.SetValue(record, value, null);
            }
           }
          }
          list.Add(record);
        }
       }
      }
      return list;
    }


    * This source code was highlighted with Source Code Highlighter.

    Метод реализован как generic method который возвращает коллекцию System.Collections.Generic.List  наполненную экземплярами нашего класса, для простоты реализации в нем опущена передача параметров в процедуру и реализована работа всего с 4 типами данных Int32, Int16, String и DateTime, но вы с легкость сможете его расширить и дополнить функционально.

    Ну и конечно же пример использования:
    List<Book> books = GetSpResultset<Book>("sp_GetBooks");

    * This source code was highlighted with Source Code Highlighter.

    Ну вот собственно и все что я хотел рассказать в своей статье, с нетерпением жду замечаний и конструктивной критики.
    Поделиться публикацией

    Комментарии 19

      0
      Это код можно использовать только как прототип, если осмелитесь пустить оставить это в production, то вас посадят за растрату (копайте в сторону reflection).
        0
        Я в качестве прототипа для проектов, которые обладают небольшой структурой БД и не сводятся к CRUD, использую списки кортежей, а в качестве запросов использую list comprehension.
        0
        А как быть в случае хранимок с параметрами? например sp_GetBooksByAuthor?
          0
          Ну для простоты я не реализовывал в примере, но делается это очень просто, добавляется еще один аргумент в метод GetSpResultset(string spName, params SqlParameter[] parameter) а в теле метода просто передается этот массив в command вот так command.Parameters.AddRange(parameter);
            0
            Спасибо за пояснение, попробую в своем небольшом проекте.
          0
          Как то у вас все запутано, везде хранимые процедуры, пусть на них все мапится… откуда такая предвзятость?
            0
            и уж тем более знать имена сторед процедур надо везде с вашим подходом, какой же это мапинг?
              0
              а в чем собственно проблема с именами процедур? если совсем не хочеться хардкодить строку с именем то можно реализовать метод для построения имени процедуры, опять таки используя рефлексию брать имя класса-сущности например, а под маппингом я понимаю наложение результата процедуры на класс модели
                0
                как то это по моему геморойно все
                попробуйте линктусиквел, посмотрите какие запросы он генерирует
                посчитайте время которое вы затрачиваете на то что бы сделать свои хранимые процедуры (которых у вас катастрофически много будет получаться кстати с вашим подходом) и время которое понадобиться сделать это например с линкью
                а потом посмотрите на сколько реально будет выигрыш в производительности у вашего подхода
                вообще овчинка стоит выделки? по моему нет
                тем более у вас пока не видно удобной работы со связными сущностями
                а начнете думать как это сделать, и опять получится линкью или еф =)

                  0
                  как уже писал что мне не нравиться linqtosql так как сложный запрос вы на нем не напишете, проект не большой и процедур будет всего 2-3 десятка, чем процедуры лучше запросов генерируемых линкью я уже писать не буду, думаю это и так поятно, для небольший проектов этот метод подойдет очень хорошо, если проект большой то лучше использовать Entity Framework, linqtosql лучше неиспользовать совсем. (из своего опыта, так как сейчас пишу проект использующий linqtosql)
                    0
                    а что за запрос вы на нем не напишете? можно пример?
                    а по поводу EF — его можно и на маленьких проектах использовать
                    проблема основная которую вижу я — это неправильное их (линкью и еф) использование
                    они реализуют паттерн еденица работы, а их пытаются делать синглтонами и оч много бед и глюков из за этого
                    но все таки приведите пример что у вас не получается сделать с линкью?
                      +1
                      ммм, как-то интересно. А разве linq2sql не поддерживает хранимки? Какие сложные запросы, о чем Вы? весь запрос у Вас в хранимке, вот и вызывайте ханимку с помощью linq2sql
                        0
                        Если в хранимке результат выводиться из временных таблиц или табличной перевенной то линкью не сгенерирует правильно dbml файл, описывать класс со структурой и наполнением прийдется всеравно руками, во вторых если использоыввать хранимки то зачем вообще тогда нужен линкью? проще написать хелпер метод как в статье и юзать его без всяких линкью, или вы используете всегда технологию лиш бы было, особо не разбираясь нужна ли такая функциональность в проекте или нет?
              0
              Всё-таки использование nHibernate или Entity Framework упростило бы вам жизнь.
                0
                Использовать NH или EF на проекте с пару десяткой процедур и таблиц это все равно что с пушки по воробьям
                  0
                  А создать пару десятков своих велосипедов и затем их поддерживать проще? Тот же nHibernate в освоении очень прост. За несколько часов можно разобраться и прикрутить к проекту.
                    0
                    Всеравно я не согласен с Вами, надо понимать когда нужно писать свой велосипед, а когда лучше взять готовое решение типа NH, а бездумно пихать эти решения где только возможно это не совсем правильно
                      0
                      т.е. вы считаете, что проект, имеющий «пару десяток процедур и таблиц», недостаточно большой по объёму, чтобы переложить все хлопоты по манипуляции с бизнес сущностями на orm, а возиться руками и тратить на это время? Да и переносить бизнес логику на слой DA не всегда дело благодарное.

                      Я согласен, что пихать мощные штуки куда хочется не надо, но в описанной вами ситуации я бы прикрутил orm.
                        0
                        добавлю что NHibernate если таблиц сотня поднимается ТАК ДОЛГО, что использовать его в приложении уже невозмжно практически

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