Блог Александра Божко
Архивы
Рубрики
Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Размышления на тему обращения к полям DataSet

Читать чужой код – занятие, порой, весьма увлекательное. Иногда узнается нечто новое, а иногда появляется иное видение, казалось бы хорошо знакомых вещей.

В этом посте речь,  пойдет о том как в коде приложения удобнее всего обращаться к полям DataSet. Вариантов, казалось бы не так уж и много, да и все они давно и хорошо изучены. Но,  все-равно есть о чем задуматься…

В некоторой степени этот вопрос уже был затронут в болге Delphi Notes.

В начале немного теории. По умолчанию, при размещении экземпляра TDataSet на форме, Delphi автоматически создает наследников TFields для каждого поля в наборе данных, с учетом типов этих полей. В режиме проектирования (design mode) можно заменить эти поля на постоянные с помощью редактора полей. Лично я всегда предпочитаю всегда создавать поля в редакторе. Кроме того, мы можем добавить вычисляемые (calculated) и выпадающие (lookup) поля.

В коде мы можем обращаться к полям следующим образом:

MyDataSet.FieldByName(‘MyFirstField’).asString :=’Any Value';

или так:

MyDataSet.Fields[1].AsString:=’Any Value';

или же так:

MyDataSetMyFirstField.asString:=’Any Value'; – если мы сами определили набор полей.

Здесь MyDataSetMyFirstField – имя, присваиваемое созданному в редакторе полей экземпляру TField по умолчанию. Если быть точным, то тип MyDataSetMyFirstField – TStringField, который наследуется от TField. Однако, если вы переименуете DataSet, то название поля уже не измениться.

Какой из способов обращения к полю использовать – дело личное. Понятно, что обращение по индексу довольно не удобно, да и не безопасно, уж сильно легко ошибиться.

В своей практике я практически всегда обращался к полю по его названию, не используя FieldByName. Почему? Да очень просто. CodeInsight убирает необходимость постоянно смотреть название полей в таблицах. Как говориться – нажимай да дуй. Процесс разработки заметно ускоряется.

Но относительно недавно я увидел примерно такой код:

procedure TfMain.btn1Click(Sender: TObject);
var
DataSet: TDataSet;
begin
DataSet:=MyDBGrid.DataSource.DataSet;
DataSet.Insert;
DataSet.FieldByName(fldTitle).AsString:= ‘New Title';
DataSet.Post;
end;

Где fldTitle – константа, содержащая название поля.

Улавливаете замысел автора?

Мне показалось, что изначально была попытка организовать приложение следующим образом… Мы можем поменять БД (например, вместо Access использовать Interbase) и поменять набор компонентов доступа к БД. Но единственное, что нам останется сделать, это создать новые компоненты доступа к данным и переподключить к ним уже имеющиеся DataSourc’ы. Код не потребует изменений.

Конечно же речь идет все о том же приложении, в котором я менял не компоненты доступа к БД, а, как раз наоборот, гриды. И мало того, что пришлось искать соответствующие обработчики событий для cxGrid и GridEh, но и каждый раз переписывать рассмотренную выше строку.

Хотели как лучше, получилось как всегда (с).

Тем не менее, код действительно заставил призадуматься. Поверьте, этот код писал довольно грамотный разработчик, и он действительно хотел как лучше. И то, что гриды будут меняться он предвидеть не мог, а скорее допускал смену СУБД (… Access, я бы на его месте делал те же самые допущения).

Мне совершенно не понятно зачем выносить в константы имена полей. Если уж копировать структуру базы, то разумно это делать “один в один”. И уж переименование поля в самой базе – полная бессмыслица. Да и если выносить имена полей в константы, то делать это надо в отдельном модуле, а не в месте со строковыми константами, которые будут переводиться в Translation Manager’е.

Что мы выигрываем от такой конструкции: DataSet:=MyDBGrid.DataSource.DataSet;?

Проще DataSet:=dsTbl1.DataSet;

dsTbl1 в данном случае это объект TDataSource, к которому подключен некий DataSet.

Если речь идет об обработчике события в гриде, то теоритически мы можем к разным гридам цеплять разные DataSet’ы, но обрабатывать события с помощью одного обработчика:

dbgrd2.OnDrawDataCell:= dbgrd1.OnDrawDataCell;

Но тогда мы не сможем работать со значениями полей. Ведь у нас разные наборы данных с разными наборами полей. Проще использовать

DataSet:= dsTbl1.DataSet;

Но даже и это нам ничего не даст, если мы полностью скопируем имя DataSet’а вместе с именами полей (сделать это не так уж и сложно).Соответствено, в коде мы можем смело использовать конструкции типа

MyDataSetMyFirstField.asString:=’Any Value';

и особо не напрягаться. При этом используется Code Insight и прочие блага цивилизации.

А в чем же тогда премущества использования FieldByName?

Мне кажется FieldByName удобно использовать только тогда, когда изначально набор полей не известен. Т.е. его нельзя строго задать в режиме редактирования. Такая ситуация может возникнуть, если селектирующий запрос создается непосредственно в коде приложения. Например так:

var

qry : TADOQuery;

begin
qry := TADOQuery.Create(Self);

try
qry.Connection := cnDatabase;

qry.SQL.Add(‘SELECT * ‘FROM tblExpenseTaxes ‘);

qry.Open;

ShowMessage(qry.FieldByName(‘Name’).asString);

finally

FreeAndNil(Qry);

end;

end;

Если же DataSet’ы создаются в режиме проектирования, то никакого смысла использовать FieldByName и нет (по крайней мере, я его не вижу).

Собственный пост мне изрядно напомнил анекдот о сортировщике апельсинов. Тем не менее, резюме…

Если есть возможность создать список объектов – наследников TField, то лучше сделать это и использовать эти объекты для обращения к значениям полей. Во всех остальных случаях – FieldByName.

Интересно, как вы подходите к данному вопросу? А главное, какой логикой руководствуетесь при этом?

Другие статьи серии:

Редизайн интерфейса приложения. #0
Редизайн интерфейса приложения. #1
Редизайн интерфейса приложения. #2
Редизайн интерфейса приложения. #3
Редизайн интерфейса приложения. #4
Редизайн интерфейса приложения. #5
Редизайн интерфейса приложения. #6
Редизайн интерфейса приложения. #8


Поделись с другими!
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

21 комментарий: Редизайн интерфейса приложения. #7

  • 1. Designtime вообще (в прямом смысле этого слова, касаемо вообще Delphi) не юзать. Оный есть от лукавого.
    2. Написать хелпер к датасету, чтобы можно было обращаться к полям DataSetName['FieldName']

    • 1. В принципе, и от использования VCL можно отказаться. Юзать APIшные заголовки и все.
      Только в чем смысл? Если удобнее и быстрее…

      2. FieldName откуда брать? Опять подглядывать в структуру таблицы? CodeInsight его же не покажет.

  • Обновление данные через грид->датасорс->датасет — правильно. Необходимо в ситуациях, когда источники данных для грида меняются. Например, в режиме поиска будет один источник, в режиме просмотра — другой.

    Это же касается и использование FieldByName — бОльшая часть запросов из базы идет со своими полями и иногда может часто меняться (поменяется название поля или его тип, добавится новое или удалится старое). В этом случае постоянно приходится лезть в ДатаСет и вручную его править. Очень неудобно. А уж если у поля поменялся тип данных — пиши пропало.

    • 1. А в чем проблема переподключить DataSourse к другому DataSet’у?

      2. >поменяется название поля или его тип, добавится новое или удалится старое

      А как же в таком случае быть с настройками грида? Надо ведь и ширину столбцов и заголовки задавать. Что, анализировать поля подключенного DataSet’а и менять это все в коде?

      Как раз, по моему проще при изменении структуры таблицы поменять DataSet (это делается вообще мышкой)
      А если речь идет о том, что к одному гриду цепляются датасеты разной структуры, в ходе выполнения программы, то такой подход, имхо, совсем плох. Впрочем, в cxGrid для этого есть представления…

      Но я сторонник того, что бы в рантайме не создавать датасеты, которые отображаются в гриде.

  • Только FieldByName – созадавать поля это плодить лишние сущности.
    А лукапы и вычисляемые поля это вообще ужас ужас – лучше все это делать в запросе.

    • Я не совсем понял относительно “лишних сущностей”.
      Поля создадутся в датасете вне зависимости от того, хотите Вы этого или нет.

      Вопрос только в том, сможете ли Вы обращаться к ним по имени в коде.

      procedure TfMain.Button2Click(Sender: TObject);
      var
      qry: TADOQuery;
      i: integer;
      begin
      try
      qry:= TADOQuery.Create(self);
      Qry.Connection:= con1;
      Qry.SQL.Add(‘SELECT * FROM feeds’);
      Qry.Open;

      ShowMessage(IntToStr(Qry.Fields.Count));

      for I := 0 to Qry.Fields.Count – 1 do
      begin
      ShowMessage(Qry.Fields[i].Name);
      ShowMessage(Qry.Fields[i].ClassName);
      ShowMessage(Qry.Fields[i].FieldName);
      end;
      finally
      FreeAndNil(Qry);
      end;
      end;

      Замените селектирующий запрос для своей таблицы и попробуйте выполнить этот код

      • Ну в коде то они есть :) Шучу.
        У вас вполне обоснованные аргументы к созданию полей в дизайнмоде. Спорить смысла нету, можно только, что то противопоставить.
        Я например хочу, что бы в проекте все обращения к полям выглядели одинаково. А т.к. у меня много ДС создаются динамически, то использование FieldByName выглядит наилучшим образом.

  • Когда я писал заметку в DelphiNotes я особо не акцентировал внимание, но при создании полей в Design-Time есть ещё одна коварная вещь – это размер строковых полей.

    Вот пример: я, как проектировщик, вдруг решил: “для поля Name 64 символов пользователю наверняка хватит”. Затем написал приложение, объект для поле Name создался в DesignTime, и в одном из его свойств сохранилось значение 64 (длина текстового поля). После этого – приложение компилируется и выпускается, заказчик доволен. Но в один прекрасный день заказчик пишет: “А не могли бы вы увеличить размер поля, у меня тут много похожих названий, и если их обрезать до 64 символов, то они все выглядят одинаково… мне бы хотя бы 80 символов…”.
    И что мне приходится делать? А вот что:
    1. alter table modify name varchar2(80). Но этого ещё мало, приложение не узнает о том, что размер поля в БД реально может вместить 80 символов. Поэтому надо:
    2. Найти это поле в датамодуле, поменять его размер, пересобрать приложение.
    3. Попросить заказчика скачать новую версию приложения.

    И вот ведь не задача: заказчиков, использующих это приложение, много. Если другие заказчики возьмут новую версию приложения, то это новое приложение позволит ввести в поле name 80 символов, но в БД у этих заказчиков это поле до сих пор ограничено 64 символами (ну не накатили им ещё это несущественное обновление БД). В итоге будет ошибка при insert или update записи. А пользователи скажут: “Вот скачал я новую версию приложения, а оно глючит :(. Не буду больше скачивать ваши обновления”.

    Кроме длины строкового поля, может ещё поменяться тип (например с Integer на Int64 или на Float). Ну и ещё я в своей практике очень часто допускаю, что некоторых полей в БД может и не быть (приложение должно работать и на старых версиях БД, и на новых). Поэтому для себя я чётко определил: в серьёзных приложениях никаких созданий полей в DesignTime быть не должно.

    Хотя в простых случаях, когда вы используете локальные базы данных (например Accsess) и скрипт обновления БД поставляется заказчику вместе с новым приложением – это уже не принципиально.

    • Проблема обновлена БД, не обновлено приложение у меня решена просто. Есть версия БД, есть версия приложения. Они контролируются на совместимость.

      Поправить поля в DataSet в режиме проектирования – не велика проблема. Сколько у Вас DataSet’ов обращается к одной таблице?
      Это же делается мышкой удалил поле- добавил недостающее…

      А вот как без фиксированного набора полей рисовать гриды я представляю слабо. Это же работы – мрак.

      К слову. В моем приложении, как раз набор полей создан для всех датасетов, данные из которых выводятся в контролы. Но запросы к этим датасетам формируются динамически… Т.е. в грид попадет всегда один и тот же набор полей. Главное правильно селектирующий запрос написать.

      Те же датасеты, данные из которых не отображаются, создаются в коде.

      • > Сколько у Вас DataSet’ов обращается к одной таблице?
        Чаще всего: одна таблица – один датасет. Но в нашем основном проекте – примерно к 10% от общего количества таблиц (а это около 50 таблиц из примерно 500) используется по несколько select-запросов (до 3х-4х, иногда больше) вместо одного.

        > Это же делается мышкой удалил поле- добавил недостающее…
        Да, допускаю такое в относительно небольших проектах, тут спорить глупо.

        > А вот как без фиксированного набора полей рисовать гриды я представляю слабо
        Эээ, а зачем их “рисовать”? Структуру полей в гриде можно создать в RunTime. Можно написать свой компонент-наследник от стандартного грида, который будет делать какую-то специфику. Мы, к примеру, используем свою обёртку над TVirtualTreeView (очень рекомендую этот компоненет :) )
        В тех случаях, когда надо дополнительно подсветить ячейки или каким-то образом сгруппировать данные, или вывести свою прорисовку – это решается либо через обёртку, либо через обработчики событий. Причём, если поля в наборе данных нет, то и столбца в гриде для этого поля не будет, а значит и в обработчики прорисовки эти столбцы не будут передаваться.
        Немного сложнее с формами редактирования записей, но и там есть варианты: а) форма может строиться полностью автоматически, б) для полей, которых нет в наборе данных, визуальные элементы ввода данных скрываются.

        > Т.е. в грид попадет всегда один и тот же набор полей. Главное правильно селектирующий запрос написать.
        Если я правильно Вас понял… было и у меня такое когда-то… сейчас я понимаю, что это от лукавого.
        Вообще со временем у меня пришло понимание, что любая универсальность хороша в меру. Хорошо, например, создать базовую фрейму, которая содержит грид и… ну скажем property TDataSet, при назначении которого НД сразу же отображается в гриде (а в гриде создаются столбцы и их расположение/размеры/видимость загружаются из реестра/файла конфигурации).
        Хорошо, когда в этой базовой фрейме можно определить набор стандартных действий, типа “добавить запись”, “удалить запись”, “редактировать запись”, “обновить”. И хорошо, когда для каждого (ну или почти каждого) НД создавать свой наследник от базовой фреймы и в этом наследнике уже прописывать дополнительную логику, свойственную конкретному НД.
        И плохо, ну ОЧЕНЬ ПЛОХО, когда один модуль с гридом может работать с разными НД – в этом случае в коде появляются проверки, с каким именно НД мы работаем в данный момент времени, что приводит к большому кол-ву смешанного кода…
        Впрочем, я не знаю что там у вас за приложение, возможно такой подход очень даже оправдан.

        P.S.: честно говоря, мне кажется, что стандартные датасеты довольно устаревшее на сегодня явление. Как BDE. Однако заменить движок BDE на альтернативы в существующем уже приложении гораздо проще (поэтому BDE сейчас почти никто и не использует), чем уйти от DataSet’ов. Поэтому люди и пишут свои DataSet’ы (ну или скачивают/покупают компоненты, совместимые с DataSet’ами) и пишут код по старинке.

  • Я тоже стараюсь избегать создавать поля в Design-time. Sw выше привёл очень хороший пример. И размер поля может увеличится, и тип может изменится. У одного клиента в одной и той же таблице может быть 10 полей, у другого – 15. Динамическое создание полей, позволяет избежать массы проблем.

    >Мне совершенно не понятно зачем выносить в константы имена полей. … И уж переименование поля в самой базе – полная бессмыслица.
    Всё же случаются ситуации, когда без переименования поля в БД не обойтись. При обновлении используемой версии СУБД, может возникнуть ситуация, когда название поля в таблице стало совпадать с вновь появившимся зарезервированным словом. У меня возникала. Приходилось переименовывать.

    >Да и если выносить имена полей в константы, то делать это надо в отдельном модуле, а не в месте со строковыми константами, которые будут переводиться в Translation Manager’е.
    Согласен. Для строк, которые надо переводить обычно используются resourcestring. Для строк, которые не надо переводить – используются константы.

    • >У одного клиента в одной и той же таблице может быть 10 полей, у другого – 15.

      Не уловил мысль.

      >Всё же случаются ситуации, когда без переименования поля в БД не обойтись.

      Алексей, это скорее несчастный случай, чем закономерность. Достаточно придерживаться простых правил именования полей и все будет ОК. Я мало -мальски подозрительное слово в структуре БД всегда сопровождаю подчеркиванием. Во избежание, так сказать…

      Да и в чем проблема-то переименовать поле в DataSet?
      2 щелчка мышкой.

      Боитесь, что не вспомните, а какие же DataSet’ы используют это поле? Напишите тестовый код и откройте все DataSet’ы в модуле. Сразу увидите.

      • >>У одного клиента в одной и той же таблице может быть 10 полей, у другого – 15.
        >Не уловил мысль.
        Специфика такова, что БД индивидуально
        допиливается под каждого клиента.

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

        Я так полагаю, что всё во многом зависит от
        специфики приложений. Всё же, shareware-
        программы массового потребления, делаются
        по другим законам, чем корпоративные программы.
        В первом случае, все пользователи имеют
        примерно одинаковую структуру БД. И чем меньше
        пользователь будет беспокоить суппорт, тем лучше.

        Во-втором случае, каждый клиент вместе с
        программой получает индивидуальную поддержку,
        и специалиста который настроит всё под
        нужды клиента. Т.е. по сути, клиент покупает
        не просто программу, а конструктор на базе
        которого ему соберут продукт. (а ля 1С).

  • 2 sw
    >Чаще всего: одна таблица – один датасет. Но в нашем основном проекте – примерно к 10% от общего количества таблиц (а это около 50 таблиц из примерно 500) используется по несколько select-запросов (до 3х-4х, иногда больше) вместо одного.
    ———–
    И у меня примерно так. Но набор полей возвращается один и тот же.
    > Структуру полей в гриде можно создать в RunTime.
    Можно
    Но зачем?
    У меня просто все гриды настраиваемые. Я в дизайн тайме его 1 раз настроил, и выкинул приложение с этими настройками. Дальше юзер сам его строит как ему удобно (в том числе может прятать “лишние поля”), а настройка грида запоминается.
    Набор полей один и тот же…
    И про “автоматические” формы редактирования… Знаем, плавали. Не понравилось :(
    > ОЧЕНЬ ПЛОХО, когда один модуль с гридом может работать с разными НД

    100%
    В таких случаях отдельный датасет и отдельный грид.

    Приложение мое, ну скажем так, средней сложности…
    около 50 таблиц. Проблема в том, что туда давно просится нормальная СУБД, но пока там Access, по ряду причин.

  • > И у меня примерно так. Но набор полей возвращается один и тот же.
    А, кажется я наконец уловил мысль… Я понимаю так, что есть НД с полями A, B, C, D. В одном случае используются поля A, B, D, в другом A, B, C, в третем – все поля. Так? И, т.к. мы работаем с одной сущностью и большинство полей у нас совпадают, то и используется одно представление (грид).
    Это логически верный подход, примерно так оно и используется у меня, с той лишь разницей, что порядок следования полей, их видимость и ширины столбцов по умолчанию хранятся в xml-описании. А это позволяет: один-два запроса отображать в одной фрейме, другие запросы – в другой фрейме (а каждая фрейма неиспользуемые поля скрывает из списка доступных пользователю).
    Кстати, как Вы предлагаете (и предлагаете ли?) пользователям сбросить настройки грида в значения по-умолчанию? Как это реализуется (перечитывается dfm на лету, или устанавливается флаг и при следующем открытии формы игнорируется загрузка пользовательских значений)?

    • У меня просто стоит кнопка Reset Settings. Прибиваются все ini файлы и приложение перезапускается в исконном виде.

      Кроме этого, у меня в опциях есть возможность переключить с “простого режима” на “продвинутый” и наоборот. При переключении видимость столбцов принудительно меняется.

  • Думаю, что нужно использовать все варианты. Использовать всегда, везде, один вариант (ради красоты одинаковости кода) это глупость проводящая к потери гибкости. т.к. у каждого варианта есть свой плюс и минус. И даже если создать классы-обвёртки (или их создаст за Вас кодо-генератор), это не будет панацеей, по любому где то оптимальней будет FieldByName или Fields[i].Value

  • В таком случае, как вы считаете, будет-ли востребовано решение, которое позволит вставлять элементы структуры БД (названия полей, таблиц и т.д.) в код одним щелчком мышки?

  • >решение, которое позволит вставлять элементы структуры БД (названия полей, таблиц и т.д.) в код одним щелчком мышки

    Если я правильно понял – кодо-генератор который не генерит структуру СУБД а берёт её как ‘модель’ и генерит по ней паскаль..
    Не знаю, наверно да, хотя трудно сказать, смотря как это будет реализовано.

  • >2. Написать хелпер к датасету, чтобы можно было обращаться к полям DataSetName['FieldName']

    Товарищ, можешь не напрягаться, это сделал Борланл лет ~10 назад: qQuery['FieldName'] := vVariant;

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

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Продукты DevArt
Купить онлайн:



Читай русскоязычные Delphi блоги
Каталог блогов Blogdir.ru
Яндекс.Метрика