3 апреля 2012 в 12:24

Использование ORM при разработке корпоративных приложений

SQL*, .NET*
Есть много споров о плюсах и минусах ORM, попробуем сделать акцент на плюсах при его использовании в ERP приложениях.

Я 5 лет разрабатываю платформу для ERP, разработал три версии платформы. Всё начиналось с EAV, после была нормальная модель, хранимые процедуры, view-хи, и сейчас эволюционировало до использования ORM. Позвольте поделиться опытом, почему ORM — хорошо.

Для демонстрации преимуществ такого подхода я разработал небольшое приложение для риэлтерского агентства (вдохновение черпал из Циан, из него же и модель данных) и попробую описать, почему благодаря ORM я все сделал за 1 день.

image
Я являюсь сторонником CodeFirst подхода, т.к. это единственно правильно для планирования структуры бизнес-приложения.
В нашей последней платформе мы после долгого выбора решили использовать ORM DataObjects.Net, но суть статьи будет понятна для приверженца любой ORM, будь то NHibernate, Entity Framework и т.д.

Итак, спланируем простое приложение для агентства недвижимости:
Риэлтор агентства недвижимости (Агент) — вносит в систему предложения по аренде и ждет запросов от арендаторов.
Арендатор просматривает предложения, отбирает по множеству критериев интересные для него и обращается к агенту для заключения сделки.

Дизайн модели данных


Создание модели — это создание классов на C#, добавление свойств-полей, атрибутов, комментариев.
Модель классов для агентства недвижимости
в коде это выглядит примерно так:
/// <summary>
/// Предложение от арендодателя
/// </summary>
[HierarchyRoot]
[Index("CreationTime", Clustered = true)]
public abstract class RentOfferBase : DocumentBase
{
    …
    /// <summary>
    /// Наименование
    /// </summary>
    [Hidden]
    [Field(Length = 64)]
    public string Name { get; set; }

    /// <summary>
    /// Дата публикации
    /// </summary>
    [Field]
    public DateTime CreationTime { get; set; }

    /// <summary>
    /// Стоимость
    /// </summary>
    [Field(Scale = 0)]
    public decimal Price { get; set; }


    /// <summary>
    /// Комиссия
    /// </summary>
    [Field(Scale = 0)]
    public decimal Comission { get; set; }

    /// <summary>
    /// Валюта
    /// Валюта в которой указаны цены
    /// </summary>
    [Field(Nullable = false)]
    public EnCurrency Currency { get; set; }

    /// <summary>
    /// Линия
    /// Линия метро
    /// </summary>
    [Field]
    public MetroLine Line { get; set; }

    /// <summary>
    /// Метро
    /// Станция метро расположенная рядом с объектом
    /// </summary>
    [Field]
    public MetroStation Metro { get; set; }    
}

Некоторые особенности, на которые имеет смысл обратить внимание:
  • Xml-комментарии
    Они сделаны не только для понятности кода, но и для использования в интерфейсе системы. То что находится на первой строчке — будет отображаемым наименованием поля. Всё что в последующих — всплывающей подсказкой. Таким подходом мы упростили себе поддержку документации кода и управлением наименованиями объектов.
  • Атрибуты
    
    [Field(Length = 64)]
    
    Field — общее указание того что свойство будет persistent-свойством
    Length — длина строки
    Scale = 0 — количество знаков после запятой в decimal
    [Index("CreationTime", Clustered = true)] 
    
    Атрибут сущности (класса) указывающий на создание индекса, ключевые поля, тип
    
    [HierarchyRoot]
    
    Атрибут, указывающий, что класс является корнем иерархии сущностей, т.о. все экземпляры как этого класса, так и его наследников, являются хранимыми

В этом примере я применил наследование для предложений от арендодателя (RentOfferBase) — базовое предложение содержит некоторую часть полей, более детальные предложения, например предложение квартиры — содержит уточняющие поля — Площадь кухни, Количество комнат.

Наследование

При работе с ORM мы можем воспользоваться таким мощным инструментом ООП как наследование.
Для базового класса предложения об аренде создаем наследников: Предложения квартир и Предложения комнат
image
При очевидной простоте, этот подход позволяет радикально сократить количество кода и упростить разработку схожих сущностей, особенно эффективно это при разработке похожих документов, отличающимися несколькими полями.

Инкапсуляция

Кроме знакомой многим инкапсуляции из мира ООП, при использовании ORM мы инкапсулируем ещё и физическую модель хранения данных. Можем использовать любую схему наследования для одного и того-же бизнес кода. Т.е. меняем структуру бд, не изменяя код приложения, ну или почти не изменяя.
Из предыдущей структуры классов не совсем понятно, как будут выглядеть таблицы, содержащие данные предложений от арендодателя, а выглядеть они могут тремя различными способами, в зависимости от значения атрибута указывающего схему наследования:

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

SingleTable
Одна таблица для всех классов иерархии
Схема таблиц для модели наследования SingleTable

ConcreteTable
По таблице на каждый не абстрактный класс
Схема таблиц для модели наследования ConcreteTable

Зачем всё это нужно?

В некоторых случаях удобно хранить нормализованные данные, а в других, для оптимизации, удобнее денормализировать таблицы. Преимущество ORM в том что это можно сделать очень просто — всего лишь изменив одну строчку — в нашем случае
[HierarchyRoot]
будет заменено на
[HierarchyRoot(InheritanceSchema.SingleTable)]
и
[HierarchyRoot(InheritanceSchema.ConcreteTable)]
соответственно. При этом, т.к. мы пишем запросы не на SQL, то все запросы будут автоматически транслированы для использования соответствующей схемы наследования. Т.е. отчет по предложениям об аренде/квартир/комнат написанный на LINQ и работающий через ORM будет работать с каждой схемой и не потребует никаких доработок.

Добавление бизнес-логики


События форм

Большинство платформ (как и наша) умеют автоматически генерировать формы по модели. Но нам мало статических форм, давайте оживим её, добавим динамики. В нашей системе мы ввели такое понятие как обработчик событий форм — класс, реализующий интерфейс обработчика с указанием на какие поля завязаны события. По изменению данных на клиенте происходит отправка данных на сервер, десериализация, обработка .net объекта, сериализация, отправка данных на клиент.

Например, изменяем на форме Стоимость, сразу же, налету, пересчитывается Процент. И наоборот. А вот как лаконично это выглядит в коде:

/// <summary>
/// Обработчик изменения поля Price и ComissionPercent
/// </summary>
[OnFieldChange("Price", "ComissionPercent")]
public class RentalPriceFormEvent : RentOfferFormEventsBase<RentOfferBase>
{
    public override void OnFieldChange(RentOfferBase item)
    {
        if (item.ComissionPercent != decimal.Zero)
        {
            item.Comission = item.Price * 0.01m * item.ComissionPercent;
        }
    }
}

Это событие расчета комиссии по процентам и по цене, логика очень простая, но мы можем написать здесь любой код на .net. При необходимости выполнить запрос к БД или web-сервису.

Полиморфизм

В предыдущем примере мы написали событие только для одной сущности RentOfferBase, это событие будет работать и с наследниками, но что если у нас несколько сущностей с ценой/комиссией? Каждый раз писать один и тот же код?
Выделяем интерфейс
/// <summary>
/// С комиссией
/// </summary>
public interface IWithComission
{
    /// <summary>Стоимость</summary>
    decimal Price { get; set; }
    /// <summary>Комиссия</summary>
    decimal Comission { get; set; }
    /// <summary>%</summary>
    decimal ComissionPercent { get; set; }
}
и переписываем событие в виде
/// <summary>
/// Обработчик изменения поля RentalPrice и ComissionPercent
/// </summary>
/// <typeparam name="TEntity">Тип сущности</typeparam>
[OnFieldChange("Price", "ComissionPercent")]
public class RentalPriceFormEvent<TEntity> : RentOfferFormEventsBase<TEntity>
where TEntity : DocumentBase, IWithComission
{
    public override void OnFieldChange(TEntity item)
    {
        if (item.ComissionPercent != decimal.Zero)
        {
            item.Comission = item.Price * 0.01m * item.ComissionPercent;
        }
    }
}

Теперь этот код сработает для любой сущности реализующей интерфейс IWithComission. При этом, если потребуется внести изменения в логику расчета процентов, то сделать это нужно в единственном месте, во всех остальных местах всё применится автоматически. Например, создадим сущность для заявки на покупку квартиры.
Такой подход позволяет значительно уменьшить количество кода и обеспечить удобную поддерживаемость продукта.

События сущностей

События сущностей очень похожи на события форм, но срабатывают транзакционно в момент изменения сущности. Это некий аналог триггеров бд, но в отличие от триггеров и аналогично событиям форм позволяют использовать ООП подход. Например, нам нужно контролировать изменение сущностей на статусе “закрыт” так, чтобы никто кроме администратора не мог их изменять. Довольно простой код

/// <summary>
/// Событие для установки краткого наименования заявки
/// </summary>
[FireOn(EntityEventAction.Updated)]
public class CheckStatus<TEntity> : IEntityEvent<TEntity>
    where TEntity : EntityBase, IWithStatus
{
    /// <summary>
    /// Операция контроля
    /// </summary>
    /// <param name="item">Элемент сущности с измененными полями</param>
    public void Execute(TEntity item)
    {
        if (item.Status.Name == "Закрыт" && !Roles.IsUserInRole("admin"))
        {
            throw new ErrorException("Запрещено изменение сущностей на статусе 'Закрыт'!");
        }
    }

    /// <summary>
    /// Текущее действие выполняемое над элементом сущности
    /// </summary>
    public EntityEventAction CurrentAction { get; set; }
}


Который проверяет что если изменяемая сущность находится на статусе “Закрыт” и пользователь не принадлежит роли админ — то генерируется исключение. Аналогично событиям форм события сущностей будут применяться ко всем сущностям совместимым с ними, в данном случае реализующими интерфейс IWithStatus.

Разделение кода

В некоторых подходах используется RichDomainModel, у нас же она Anemic
и это значит, что в классе сущности практически отсутствует бизнес логика. (Для этого есть События Форм/Сущностей/Фильтры и т.п.)
Преимущество такого подхода в возможности модификации поведения внешних сущностей. Например, одна компания разработала модуль Адресов и поставляет его как библиотеку, мы не имеем доступа к исходному коду этой библиотеки и хотим добавить какое-нибудь поведение на форму, например при выборе некорректного адреса предупреждать.
Для этого мы можем написать событие формы, которое будет применено к внешнему компоненту.

Фильтры

Применение ORM позволяет воспользоваться для фильтрации таким мощным инструментом .net как ExpressionTrees. Мы можем заранее написать выражение фильтрации для использования как ограничения бизнес логики, можем на основе действий пользователя отфильтровать грид.

Например, для ограничения видимости неактуальных заявок, для менеджера применяется следующее выражение фильтрации из кода:
public static Expression<Func<TOffer, bool>> FilterOffers<TOffer>() 
    where TOffer : RentOfferBase
{
    return a => a.Creator.SysName == SecurityHelper.CurrentLogin || a.Status.Name == "Актуально";
}


Это простой фильтр, используемый для ограничения прав доступа только к своим заявкам, либо к заявкам на статусе “Актуально”
Этот фильтр сейчас не привязан явно ни к какой сущности, generic параметр говорит лишь о том, что использовать его можно для RentOfferBase и любого из его наследников. Для кого он будет реально применен будет определяться позже, в момент настройки приложения.

Так же мы можем задать фильтрацию одного поля формы в зависимости от другого
[FilterFor(typeof(RentOfferBase), "Metro")]
public static Expression<Func<MetroStation, bool>> MetroFilter([Source("Line")]MetroLine line)
{
    return a => line == null || a.MetroLine == line;
}


Здесь мы фильтруем станции метро в зависимости от выбранной ветки, указав в атрибутах сущности и поля, которые используются в качестве источников значений и объектов фильтрации.

Внесение изменений в систему


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

Здесь необходимо сказать, что важно не просто ORM, а идеология CodeFirst. В предыдущей версии нашей системы мы тоже использовали ORM — Linq2SQL. При этом использовался Database-first подход, база данных хранилась в виде “мастер-базы” и скриптов обновления. Типовая ошибка, встречающаяся в таком подходе — код классов .net не соответствует БД. Для решения проблемы мы написали собственные валидаторы структуры бд.

Что же мы получаем в CodeFirst:
  • Быструю разработку сущностей — пишем .net класс редко задумываясь о БД
  • Удобный способ хранения БД в системе контроля версий — храним только код класса
  • Валидацию бизнес логики в момент компиляции


Но как же быть с обновлениями?

Миграция

Представим, что мы готовим обновление, которое заказчик устанавливает на свою БД. Простые миграции выполняются полностью автоматически. Т.е. если мы внесли безопасные изменения в модель — то ORM сам смигрирует БД на новую версию.
Безопасные изменения. это изменения не удаляющие данные из БД, например:
  • Создание нового поля
  • Создание новой сущности
  • Увеличение точности/длины поля

Конечно, этих действий не достаточно при разработке серьёзных приложений, что же делать при задаче переименовать поле/сущность?
  1. Применить рефакторинг переименования (мы используем ReSharper). При этом все использования этого поля в нашем коде переименовываются.
    В том числе переименовываются использования в Фильтрах, Событиях Формы, Событиях Сущности. Сложность может доставлять только встречающиеся в виде текста имена полей в Атрибутах, но если наименование поля достаточно уникально — то проблем не будет. При этом после переименования можно запустить компиляцию и убедиться что поля в атрибутах переименованы правильно.
  2. Добавить hint переименования. Hint — подсказка для ORM что же делать с БД при наличии различий между схемой построенной по классам и реальной схемой в SQL. hint переименования поля выглядит примерно так:
    public class RenameFieldUpgrader : ModelUpgraderBase
    {
        public override Version Version { get { return new Version("3.5.0.8764"); } }
    
        public override void AddUpgradeHints(ISet<UpgradeHint> hints)
        {
            hints.Add(new RenameFieldHint(typeof(RentOfferBase), "OldName", "NewName"));
        }
    }

Похожими хинтами мы можем указать на переименование сущности, удаление поля/сущности. При наличии такого хинта при следующем запуске ORM автоматически применит рефакторинг переименования для БД и переименует поле с сохранением данных.

Итоги


В результате применения ORM мы получили:
  • Удобную и быструю разработку как модели данных так и бизнес логики
  • Удобную, простотую и надежную поддержку приложения
  • Использовали преимущества ООП на всех этапах
  • Повторное использование кода (исключили copy-paste)
  • Валидацию кода в момент компиляции
  • Лаконичный, понятный код бизнес-логики
Владимир Клековкин @pil0t
карма
15,0
рейтинг 0,0

Комментарии (110)

  • 0
    Что делать если к этой базе должно подключаться стороннее приложение?
    — например, риэлтеры отмечают сделки в этой программулине а для сайта эти же данные должен брать пхп-скрипт. Он должен лезть в базу а значит любые изменения в этой программулине (а значит и базы) ведут в дополнительной правке стороннего приложения.

    Что делать если структура данных изменилась?
    — например привязку к метро отменили и привязывают к обслуживающему дом ЖЭКу. Как конвертировать имеющиеся данные?

    Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.

    Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР? Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными?

    Вижу только быструю разработку.

    С поддержкой, разделением данных между приложениями и производительностью одна беда.
    • +8
      Что делать если к этой базе должно подключаться стороннее приложение?
      Понятно же — реализовать API для работы с БД (все сторонние клиенты работают только с API, а не с БД напрямую). Сделать это можно, например, создав Web Service (стандарт которого — кросс-платформен).
      • –12
        тогда заявление о быстрой разработке неверно. Если ещё и АПИ надо делать.
        • 0
          Такое апи делается за минуты
          • –17
            Такому апи цена — грош.
            • +12
              Такой аргумент — не дороже
        • +3
          API может быть сгенерировано автоматически
          от разработчика задача только описать класс сущности
        • +4
          Вы так сказали будто если не использовать ORM АПИ делать не нужно
          • –5
            если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками) то никакого API не нужно. Все приложения обращаются к SQL-серверу и он сам следит за соблюдением правил, оптимизацией и разделением прав доступа.

            Фактически, то что описано в статье это отбросить всё что предоставляет SQL-сервер и городить своё, родное.
            • +6
              «если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками)»
              … то это не бизнес-логика, а смех.

              В остальном же, то, о чем вы пишете — это классические аргументы за перенос бизнес-логики из прикладного слоя в БД и за использование БД в качестве точки интеграции. Been there, seen that. Так делать по первости удобно, но есть проблема: я не знаю ни одной современной РСУБД, которая была бы настолько уже удобна в разработки и последующей поддержке, что и современные же языки программирования.

              Компиляция и проверки периода компиляции? Нихьт.
              Отслеживание зависимостей? Нихьт.
              Рефакторинг? Нихьт.
              Управление версиями? Нихьт.

              PS SQL-сервер сам следит за разделением прав доступа? Были, ели. Для этого каждый пользователь лезет в базу под своим логином. Прощайте, масштабируемые фермы.
              • –3
                «если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками)»
                … то это не бизнес-логика, а смех.


                бизнес-логика в базе позволяет решить проблемы описанные в первом комментарии к статье. Да, для этого обязательно понимание что такое «база данных».
                • +2
                  минимально сложная бизнес логика требует написания кода
                  ваш вариант — писать его в процедурах
                  мой — в классах/методах

                  для валидации кода нужно будет выполнить выполнить этот код для каждой записи.

                  так чем же процедура удобнее метода?
                  • –1
                    в большинстве случаев структура базы данных уже является описанием бизнес-логики. Без всяких процедур. SQL-сервер не запускает валидацию для каждой записи, он немного иначе работает (за подробностями в соответствующую литературу).
                • +4
                  «бизнес-логика в базе позволяет решить проблемы описанные в первом комментарии к статье»
                  Любую задачу можно решить более чем одним способом. Можно — переносом всего в СУБД. Вопрос цены.

                  Все аргументы против этого уже пережеваны неоднократно.

                  Ну и пройдемся еще раз по вашим вопросам:
                  «Что делать если к этой базе должно подключаться стороннее приложение?
                  — например, риэлтеры отмечают сделки в этой программулине а для сайта эти же данные должен брать пхп-скрипт. Он должен лезть в базу а значит любые изменения в этой программулине (а значит и базы) ведут в дополнительной правке стороннего приложения.»
                  Видим недостаточную изоляцию компонентов. Для каждого приложения, работающего с данными (данными! не БД!) должен быть зафиксирован свой интерфейс, через которое это приложение и работает. Этот интерфейс выступает необходимым уровнем абстракции.

                  «Что делать если структура данных изменилась?
                  — например привязку к метро отменили и привязывают к обслуживающему дом ЖЭКу. Как конвертировать имеющиеся данные?»
                  Миграционный скрипт. Никаких отличий от работы с СУБД напрямую.

                  «Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.»
                  На правильном ORM он выполняется за столько же. А вообще, при появлении значительных требований по отчетности, нужно делить OLAP и OLTP и гонять данные ETL-скриптами.

                  «Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР?»
                  Где вводятся?

                  «Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными?»
                  Переделать те места, где это критично. Собственно, смотри выше про уровень абстракции данных.
            • +3
              Разделения прав доступа в SQL сервере практически во всех случаях не будет хватать. Дальше безопасность. Только в очень редких случаях я бы доверил выполнение sql со стороны. Те же риэлтеры могут закинуть в базу инфицированный текст.
              • –3
                текст нельзя инфицировать

                того что не разрешено в базу закинуть нельзя, SQL-сервер это гарантирует.
                • +1
                  Речь шла о SQL иньекциях.
                  • 0
                    И это кстати тоже. Ведь если работу с базой переложить на плечи сторонних разработчиков они могут допустить различные уязвимости. А расхлебывать будем мы
                    • –1
                      Наоборот.

                      Если пользователю в базе, скажем, запрещено удалять историю продаж квартир то никакие SQL-команды не помогут ему это сделать.

                      В описанном случае мне достаточно подключиться к базе и я смогу выполнить любые команды т.к. никаких проверок там нет.
                      • +2
                        Ну что бы вы могли подключится к базе вы должны знать логин и пароль. А таким же успехом если вы будете знать доступ к ftp вы можете удалить все файлы сайта.
                        • –1
                          получается что в описанном случае сторонняя программа не может подключаться к базе (т.к. может испортить данные если знает логин/пароль а проверок в базе нет).

                          Нужно делать АПИ доступа в котором проверять права.
                          Нужно в нём реализовать все запросы которые могут понадобиться сторонним программам (например кому-то понадобится общая сумма по сделкам а этого нет в описанной программулине).
                          Также этот АПИ нужно поддерживать.

                          В случае использования бизнес-логики в БД все этой работы выполнять не нужно.
                          • +1
                            А в вашем случае вы выстраиваете защиту описывая ее в процедурах. Часто фильтрация входящих данных далеко не тривиальна. Ниже я привел пример про XSS. Часто такую фильтрацию выполняют библиотеки размером до 1000 строк. Представляю размер ваших процедур…
                            • –2
                              нужно понять что процедуры и безнес-логика в структуре базы данных это разные вещи.
                              • 0
                                А что такое бизнес-логика в структуре БД?
                                • 0
                                  Бизнес-логика — в разработке информационных систем — совокупность правил, принципов, зависимостей поведения объектов предметной области (области человеческой деятельности, которую система поддерживает). Иначе можно сказать, что бизнес-логика — это реализация правил и ограничений автоматизируемых операций. Является синонимом термина «логика предметной области» (англ. domain logic).

                                  вики

                                  может быть описана как структура базы данных, для этого используется

                                  Реляционная модель данных (РМД) — логическая модель данных, прикладная теория построения баз данных, которая является приложением к задачам обработки данных таких разделов математики как теории множеств и логика первого порядка.

                                  вики
                                  • +1
                                    Т.е. модель предметной области есть часть бизнес логики? Я раньше понимал что бизнес логика, это операции, которые можно совершать в рамках каких либо процессов, над доменной моделью (моделью предметной области). Ну да ладно, видимо не верно понимал.

                                    Но вы все таки не пишите ту часть логики, которая описывается не структурой БД, в хранимках. С этим очень трудно жить. Не вижу никаких плюсов в хранимках. Возможно использование хранимок, только в искусственно созданных обстоятельствах. Во всех остальных случаях, это необоснованные затраты.
                                    • –1
                                      понимаете, я в этом обсуждении уже несколько раз повторил что безнес-логика в структуре базы данных и процедуры это совершенно разные вещи.

                                      Нужно ли мне ещё раз это повторять?
                              • 0
                                Извините, сюда писал.

                                Что такое «бизнес-логика в структуре БД»?
                              • 0
                                Хы, оказывается комментить ниже незя. :) :)
                              • 0
                                Как бы вы решили защиту от XSS? XSS я привел лишь потому что это первое что пришло в голову защита от которого не пишется в 1 строчку.
                                Также если для добавления записи нужно обязательно заполнить некоторые поля. Если поля не заполнены запись не добавляется. Скрипт что инициировал запрос должен знать об ошибке и что произошло. Не думаю что это просто сделать вашим способом.
                                • 0
                                  В новой версии хабра обещают поднять максимальный уровень вложенности до 90
                          • +1
                            Ммм… Очень часто вижу как пихают бизнес-логику в БД. Не делайте этого. Вспомните великие слова: «Представьте, что код, который вы написали, будет сопровождать маньяк, который знает где вы живете».
                      • 0
                        «В описанном случае мне достаточно подключиться к базе и я смогу выполнить любые команды т.к. никаких проверок там нет.»
                        Угу. Вот только под каким пользователем вы это будете делать, если в базе их два: админ и «пользователь-под-которым-ходит-апп-сервер», и пароль от второго хранится так же свято, как и от первого?
                  • 0
                    SQL-инъекция это когда в текст запроса без проверки добавляют переменные введённые пользователем. Все знают что так делать нельзя, все знают что это решается стандартным cmd.Prepare(); или типа того.
                    • +1
                      И тут мы подходим к тому, что время затраченное на предотвращение подобного слихвой покрывают время на реализацию API, которое, как было замечено ранее, можно написать минут за 15. Более сложное АПИ подразумевает сложную бизнес логику, которую, если переносить все в БД, без процедур вы не реализуете.
                      • –1
                        так предотвращение не стоит ничего. Ни в сложных ни в простых программах. Есть правила, их надо выполнять:

                        Нужно пользоваться PreparedStatement.

                        И всё. Типа как нельзя хранить пароль на стикере приклеенном на мониторе, нельзя давать ключ от квартиры незнакомым людям.

                        Большинство ОРМ, кстати, это правило выполняет.
                        • +2
                          Хорошо, вот вам простой пример
                          У вас есть некая система, логика которой полностью вынесена в БД. Предположим что логика там банальная — просто CRUD, например для хранения какого-то контента. В таком случае худо бедно все будет хорошо.
                          В качестве СУБД вы желаете использовать MySQL/MSSQL/Oracle и тд.

                          К вашему проекту подключаются извне, напрямую к базе, что бы внести изменения. Тобиш вместо стандартного SOAP сервиса, который, в силу простоты логики, реализуется за 5 минут, программисту стороннего приложения придется потратить кучу времени что бы разобраться как со всем этим работать. В случае если бы вы потратили эти 5 минут на создание проекта в среде разработки, описание сущности (почти все фреймворки умеют генерить их из таблиц) и создание сервиса (по сути просто объявить методы, которые уже реализуют CRUD операции, помниться мне вижла хорошо это делала сама), то жить стало бы лучше всем. Программист стороннего приложения получает WSDLку, которая полностью реализует API на его языке программирования к вашему сервису. То бишь разницы нету писать на яве, objective-c или на php, все стандартизировано. Как минимум для разработчика стороннего приложения вы сделаете хорошо. Ну а вам возможность легко вносить изменения в логику без изменения интерфейса. Может быть вам придется организовать репликацию, шардинг или еще чего.

                          Как бы я не настаиваю на таком подходе, могут быть разные требования, но как по мне пихать всю логику в базу это не есть хорошо.
                • +3
                  Я имел в виду XSS если текст с базы использоваться в вебе
    • +1
      Что делать если к этой базе должно подключаться стороннее приложение?
      Предполагается, что доступ до приложения (данных) будет через сервис (XML/JSON и т.д.)

      Что делать если структура данных изменилась?
      1. Удаляем метро
      2. Добавляем ЖЭК
      3. Заполняем ЖЭК

      Что делать при регулярно-меняющейся отчётности?
      Мы используем ReportingServices, который потребляет данные через XML источник данных.
      Конкретно для суммы по комнатам — один метод
      public static decimal GetAgentsRoom(string name)
      {
          return Query.All<RoomOffer>().Where(a => a.Creator.Name == name).Sum(a => a.Price);
      }
    • +3
      Что делать если структура данных изменилась?
      Ответ на этот вопрос характеризует гибкость написанного кода: если планируется переиспользование кода, то нужно не привязываться к конкретным классам, а работать с интерфейсами (тогда и способ хранения реализаций последних — не важен).
    • +2
      Вижу только быструю разработку.

      Так вроде, как я понял, именно изменения вносить только в C# код проще и сами изменения короче, чем вносить их сперва в код, потом в пакет обновления БД.

    • +2
      Автор показал как раз как делать нельзя если строить n-tier приложение.
      1. Сущности БД дальше слоя доступа к базе данных выходить не должны.
      2. Верхние уровни(бизнес логика) должны работать с Data Transfer Objects например.
      3. Генерировать формочки по моделям конечно легко и красиво, только очень быстро празник закачивается, как только нужно разнести приложение по разным сервисам, усложнить логику, добавить связь с внешними системами, все мы это уже проходили в Delphi, VB и даже .net с обилием code-behind обработчиков.
      Теперь по вашим вопросам:
      Что делать если к этой базе должно подключаться стороннее приложение? — SOA, REST или SOAP -based. Доступ к БД как сервис, только давать доступ к БД очень опасно, потому обычно над Data access layer строим business logic layer который делает валидацию и проверку на соотвествие бизнест требованиям запроса.
      Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.

      Так уж и любой объём данных? Так не бывает и ниодна вменяемая компания обязательства на такой SLA не подпишет. Есть физические ограничения сколько вы можете сгенерить запросов и данных. Если захотите аналитику OLAP считать в real-time так вы у вас даже на ограниченных наборах даных легко вылезете за 1 сек. А зная примерно порядок и характер данных выбираете рецепт как удовлетворить заданое требование, может случится что у вас вообще не получится этого сделать на существующей архитектуре.
      Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР? Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными? — Для этого business access layer и вводится. Если уж совсем часто меняться правила или разрабатывается движок ERP/CRM/etc. прикручивают какой-то rules-engine иногда ещё и с версионностю, чтобы проигрывать правила.
    • НЛО прилетело и опубликовало эту надпись здесь
  • –3
    Добавить hint переименования. Hint — подсказка для ORM что же делать с БД при наличии различий между схемой построенной по классам и реальной схемой в SQL


    Сегодня с утра съездил за запчастями, потом заправился, потом — в гараж договориться о покраске, потом в ГАИ… Представляешь, как бы я все это успел без машины?
    Подробнее — на сайте анекдотов «Чортова Задница», посмотри, не пожалеешь: www.hellass.com/autohumor/669-without-car.html
    • +3
      — Когда Вы говорите, Иван Васильевич, впечатление такое, что вы бредите. (с) подробнее вики
    • 0
      Как я понял, хинт — это возможность автоматически сгенерировать скрипт обновления БД.
      А можно просто руками написать такой скрипт, если я не хочу разбираться с хинтами?
  • +4
    я разочаровался в ОРМах. ИМХО удобней процедуры + мапер + тесты.
    1. Не надо думать какой сгенерится sql в итоге.
    2. Процедура — это sql без ограничений.
    3. Процедуру всегда можно пофиксить на проде без выката всего приложения.
    4. Уходит целый слой кода.
    5. Код ферст? Неверю
    • +4
      1. Так у нас тоже не надо думать какой сгенерится sql
      2. C# — это не только SQL, но и вся мощь ООП
      3. Очень спорный момент, и может подпортить много нервов и админу и разработчику и не только
      5. А вы попробуйте ;)
      • +4
        1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?
        2. С# не включает в себя sql:) Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД. ООП фишек лучше здесь избегать, нужен простой понятный код.
        3. Да я понимаю что это грязный прием и с ним нужно очень аккуратно, но когда дырка в огне и люди бьют друг другу морды в очереди из-за завышенного уровня блокировки(реальный случай). Такая возможность приходится очень кстати.
        4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать.
        • –2
          1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?

          Это уже проблема ОРМ, в нашем случае делаются джойны всегда. Но можно и не делать, а можно вообще загрузить еще все связанные сущности сразу. Главное, чтоб был АПИ. Ну, и чтение доков никто не отменял пока :)
          С# не включает в себя sql:) Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД. ООП фишек лучше здесь избегать, нужен простой понятный код.

          Смеялся, честно :)
          4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать.

          В 99% случаев, при использовании ОРМ, думать, какой код генерится — не надо. Когда надо — можно написать ручной SQL.
        • +1
          «Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД»
          Вы себе противоречите. Если бизнес-приложения — это «просто редакторы БД», то бизнес-логики там нет. А как только бизнес-логика появляется, держать ее надо там, где ее удобнее поддерживать.

          «Опять же придется думать какой же sql сгенерится. Зачем?»
          Вот именно: зачем вам об этом думать? Как только вы начинаете об этом думать, вы нарушаете данный вам уровень абстракции (и увеличиваете сложность вашей задачи). Или вы когда пишете SQL, думаете, в какие обращения к каким дорожкам жесткого диска это выльется?
          • +2
            1. Да слово «просто» я зря применил, потому что все на самом деле сложно:) Если логика крутится вокруг большего количества данных, зачем таскать их сквозь слои? Гораздо удобнее работать с данными на месте, т.е. в БД

            2.Не люблю абстракций, люблю когда все предельно детерминированно — такие приложения поддерживать проще и они надежнее. Да я стараюсь думать о дисках когда пишу на sql и еще об оперативной памяти. БД как раз то самое место где стоит подумать о производительности.
            • +3
              «Гораздо удобнее работать с данными на месте, т.е. в БД»
              Вот слово «удобнее» тут малоприменимо. Как я уже писал в одном из соседних комментариев, я еще не видел не одной БД, где было бы _удобно_ вести разработку сколь-нибудь сложной логики. Зависимости-рефакторинг-версионирование-?

              «Не люблю абстракций, люблю когда все предельно детерминированно — такие приложения поддерживать проще и они надежнее.»
              Значит, вам повезло, и ваше приложение достаточно просто, чтобы не возникало проблемы «бизнес-уровень не влезает в голову». _Только_ бизнес-уровень.
              • 0
                1. С рефакторингом помогают тесты. С версионированием ОРМ не помогает.
                2. Только бизнес-уровнем думает аналитик. Мне приходится думать обо всем… бизнес -> sql это проще чем бизнес -> генератор sql -> sql
                • +2
                  «С рефакторингом помогают тесты.»
                  Только вот для .net-кода есть еще инструменты, позволяющие это делать _до_ тестов. Получаем _три_ слоя поддержки вместо одного.
        • +1
          1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?
          — EFProf или самописная приблуда которая логирует запросы, а дальше оптимизация LINQ запросов. Практика показывает, что большинство проблем решается и такое количество join возникает или изза плохой струтуры данных или некачественного написания запроса.
          4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать. — write-once подход. Скрипты sql живут в одно месте, модели в другом, переименовали поле, изменили его тип, забыли предупредить товарища, а он потом будет мучатся 4 часа на площадке товарища выясняя почему приложение неожиданно падает.
    • +7
      >3. Процедуру всегда можно пофиксить на проде без выката всего приложения.
      Простите, но за такое надо просто руки отрывать сразу. Мы как раз делали миграцию с одного такого приложения, его поддержка была просто АДОМ
      >4. Уходит целый слой кода.
      А структура БД у вас в астрале хранится?
      При нашем подходе мы храним только классы(структуру) и данные(xml) — а дальше нам уже не важно, какая БД и что там. ОРМ сделает все за нас: приведет БД в актуальное состояние и даст к ней доступ.
      >5. Код ферст? Неверю
      У нас есть 10 готовых работающих проектов в продакшене, которые легко поддерживаются и изменяются. Вот этому я верю.
      • +1
        3. см выше
        4. Ох как у вас все розово
        5. Это круто, тут мне нечего сказать я не пробовал код ферст
        • +1
          Ну, выше я тоже ответил. А насчет «розово» — говорю, как есть, причем не в теории, а на практике.
    • +3
      «Процедура — это sql без ограничений.»
      То есть надо знать два языка вместо одного.

      «Процедуру всегда можно пофиксить на проде без выката всего приложения.»
      За такие вещи убивают на месте без права оправдания. Почитайте Continuous Delivery.

      «Уходит целый слой кода.»
      Не «уходит», а «перемещается в БД». Где его автоматизированное тестирование затрудняется на два порядка.
      • –3
        То есть надо знать два языка вместо одного.

        — именно. Поэтому даже вакансии публикуют как «программист со знанием БД» и «архитектор».
        • +1
          Архитектор — это вообще про другое.

          Ну и да, «программист со знанием БД» — это хорошо, только таких (знающих БД лучше, чем специально для нее написанный компилятор выражений в ORM) существенно меньше, чем просто хороших программистов.
      • +2
        1. sql надо блин знать полюбасу:)
        2. см выше
        3. Если приложение редактор БД почему логика должна быть не в БД? Грош цена такому тестированию которое не учитывает всех слоев.
        • +2
          «sql надо блин знать полюбасу»
          Не слишком ли много «надо знать». Голова одна.

          «Если приложение редактор БД почему логика должна быть не в БД?»
          Если приложение — редактор БД, то логики в нем нет.

          «Грош цена такому тестированию которое не учитывает всех слоев. „
          Про юнит-тесты вы не слышали? И вообще про деление тестов на компонентные, функциональные, интеграционные, приемочные и далее по тексту?

          Какбы настройка автоматического теста с использованием БД где-то на порядок-два сложнее, чем настройка такого же теста с использованием изоляции.
          • +1
            1. Жалеешь голову работай руками :)
            2. Это гипербола.
            3. Слышали:) интеграционные эффективнее всего хоть и действительно сложнее. Юнит-тесты полезны пожалуй только в тестировании сложных алгоритмов.
            • 0
              «Жалеешь голову работай руками»
              Не думая?

              «Слышали:) интеграционные эффективнее всего хоть и действительно сложнее. Юнит-тесты полезны пожалуй только в тестировании сложных алгоритмов. „
              Уууу… вас не смущает тот факт, что интеграционный тест банально дольше работает? На те же два-три-четыре порядка? А значит, полное покрывающее тестирование системы будет идти нецивилизованно долго?
              • +1
                Тест — это не приложение, он не обязан супер быстро работать, не в этом цель. Цель теста показать ошибки.
                • +2
                  «Тест — это не приложение, он не обязан супер быстро работать, не в этом цель. Цель теста показать ошибки. „
                  Вы, видимо, не читали ни Continuous Delivery, ни The Art of Unit-Testing.

                  Тест должен работать быстро, потому что иначе никто не будет его выполнять. Потому что тесты, в идеале, должны гоняться после каждого изменения. Если тесты занимают день, никто не будет их прогонять чаще раза в месяц, значит, и информации об ошибках не будет.
                  • 0
                    Читал, так же еще в таких книгах обычно написано лучше медленный тест чем никакого:) Действительно все тесты не гоняю каждый раз, только связанные с фичей, однако билдстанция прогоняет при каждом коммите и шлет письма если че не так.
                    • +1
                      «однако билдстанция прогоняет при каждом коммите и шлет письма если че не так. „
                      Интеграционные? На near-production БД? На все сценарии использования?

                      У нас одно развертывание образцовой БД занимает дольше, чем имеет право идти коммит-тест.
                      • 0
                        Ресторится девовская бд, каждый тест подготавливает себе данных и подчищает за собой. 100 тестов 5 минут. 80% тестов интеграционные.
                        • +1
                          100 тестов? Выглядит, гм, занятно. Интересно, какое у вас покрытие.

                          (ну и да, у меня тут некий проектик на 119 тестов, проходит за 9 секунд)

                          (впрочем, у нас БД для интеграционных тестов ресторится дольше)
  • +1
    А зачем у вас данные о метро в RentOfferBase денормализованы? Ведь вместо станции и линии можно было бы хранить только станцию
    • 0
      В основном для удобства использования поиска-фильтраци, а так же удобно показать пример фильтра
  • 0
    Хорошая ли вещь ORM? Задумка хорошая. А вот реализация отвратительная.

    Но у нас, например, в PHP, все ORM реализованы из рук вон плохо, неэффективно, тащат в рантайме десятки классов, делают кучу лишних действий и т.д. А как с этим дела в .NET? Подозреваю, что так же.

    Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать. А так выгоднее руками запросы писать, хотя бы тормозить не будет.

    Например, почему вы на каждое поле создаете приватное поле, сеттер и геттер? Памяти лишней много? Зачем этот мусор в коде? Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))? А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров? Потом вызывать всю эту тьму геттеров и сеттеров, чтобы их обновить? Вот еще одно доказательство, что ORM пишут непродуманно и как попало.

    Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

    Также, комментарии реализованы неудачно. Что за уродливый синтаксис? В нем XML-мусора больше, чем полезной информации. Есть же JavaDoc.

    Также, неудачно названо поле onFieldChange. Ну кто, кроме индусов из майкрософт, мог придумать такое тупое название? Какой еще change? В нормальном мире это называется зависимостью между полями, или вычисляемым полем (calculated field).

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

    > Например, одна компания разработала модуль Адресов и поставляет его как библиотеку, мы не имеем доступа к исходному коду этой библиотеки и хотим добавить какое-нибудь поведение на форму, например при выборе некорректного адреса предупреждать.
    > Для этого мы можем написать событие формы, которое будет применено к внешнему компоненту.

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

    Также, у ORM есть фундаментальный недостаток. Так как ORM создает «видимость» (абстракцию), что persisted объект является обычным объектом, программист работает с ним как с обычным объектом (а не с таблицей SQL), что порождает неэффективные SQL-запросы, которые могут делать адские джойны, выборку не по индексу, которые пытаются неуклюже лечить огромным кешем, держа все активные объекты в оперативной памяти, или держа списки изменившихся свойств (чтобы сохранить их в конце транзакции), что тоже расходно.

    Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое, что даже MS SQL сервер с многотыясчедолларовой лицензией будет скрипеть жестким диском (хотя, если подумать, производителям железа выгодны тяжелые ORM — спрос повышается на SSD).

    Подводя итоги, мы видим множество недостатков и просчетов, допущенных при проектировании ORM компоненты. За быструю разработку приходится расплачиваться космической неэффективностью кода. Не представляю, как на такой непродуманной и ненадежной платформе можно вообще что-то писать. Неудивительно, что ентерпрайзный софт ругают все, кто с ним работал.
    • +3


      • +3
        Был так возмущен, что слов не осталось? :)
      • +4
        Предыдущий комент сам отправился :(
        Но у нас, например, в PHP, все ORM реализованы из рук вон плохо, неэффективно, тащат в рантайме десятки классов, делают кучу лишних действий и т.д. А как с этим дела в .NET? Подозреваю, что так же.
        Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать. А так выгоднее руками запросы писать, хотя бы тормозить не будет.

        У нас 1 класс на 1 сущность. Реализованы эффективнее, чем большинство написало бы сами.

        Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

        Прощай легкая поддержка, привет АД

        Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

        Хоспади… упаси меня

        Дальше просто не смог читать, извините.
    • +7
      > Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))?

      Дальше не читал. Это такой адский отжиг, что я даже не знаю что вам на это сказать.
    • +3
      «Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать.»
      EF (POCO-генератор или Code First"

      «Например, почему вы на каждое поле создаете приватное поле, сеттер и геттер? Памяти лишней много? Зачем этот мусор в коде?»
      А что, в .net это как-то влияет на память? Нет, не влияет. Более того, может инлайниться до обращения к полю.

      «Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))?»
      Потому что это не соответствует ООП.

      (не говоря уже о накладных расходах на поиск в ассоциативном массиве)

      «А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров?»
      Да. Это плата за уровень абстракции, удешевляющий разработку.

      «Более того, почему вообще 1 модели должен соотвествовать один класс?»
      Мммм… ООП?

      «Также, комментарии реализованы неудачно. Что за уродливый синтаксис? В нем XML-мусора больше, чем полезной информации. Есть же JavaDoc.»
      ORM тут не при чем совершенно, это стандартный синтаксис .net.

      «События, опять же, приделаны как какой-то костыль. В правильном описании модели между полями могут быть взаимосвязи/зависимости, и события не нужны (так как если поля связаны функциональной зависимостью, они обновляются сами, без всяких событий).»
      Это вопрос используемой абстракции. Событийная менее наглядна (по сравнению с вычисляемыми полями), но дает больше возможностей (например, можно делать валидацию по изменению поля).

      «Также, у ORM есть фундаментальный недостаток. Так как ORM создает «видимость» (абстракцию), что persisted объект является обычным объектом, программист работает с ним как с обычным объектом (а не с таблицей SQL), что порождает неэффективные SQL-запросы, которые могут делать адские джойны, выборку не по индексу, которые пытаются неуклюже лечить огромным кешем, держа все активные объекты в оперативной памяти, или держа списки изменившихся свойств (чтобы сохранить их в конце транзакции), что тоже расходно.»
      Никто не говорит, что для работы с ORM не нужно представлять себе, как работает БД. Но не забывайте, premature optimization is the root of all evil. И именно этот предоставляемый уровень абстракции позволяет нам проще работать с кодом, уменьшая (воспринимаемый" уровень сложности системы.

      «Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое, что даже MS SQL сервер с многотыясчедолларовой лицензией будет скрипеть жестким диском (хотя, если подумать, производителям железа выгодны тяжелые ORM — спрос повышается на SSD).»
      Понимаете ли, любой SSD дешевле, чем человеческое время.
      • –3
        «Потому что это не соответствует ООП.»

        Геттеры и сеттеры сами по себе не соответствуют ООП, они нарушают инкапсуляцию. О каком ООП вообще речь?
        • +4
          «Геттеры и сеттеры сами по себе не соответствуют ООП, они нарушают инкапсуляцию. О каком ООП вообще речь? „
          Вы МакКоннела читали? Вот использование поля как части публичного интерфейса нарушает инкапсуляцию. А геттеры и сеттеры как раз прячут конкретную реализацию, выставляя консистентный интерфейс.

          Более того, в конкретно взятом .net геттеры и сеттеры являются синтаксическим сахаром вокруг абстракции свойства, и уж там-то точно не нарушают ничего.
          • –2
            Подсунуть вместо поля пару функций/свойство это ещё не значит инкапсулировать. Вот, ознакомьтесь
            www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html

            Кроме того, я тут объектов как таковых не вижу. Это просто записи/структуры.
            • +2
              То, что тут anemic/dto — это другой разговор. А про инкапсуляцию и геттеры-сеттеры все сказано у МакКоннела, раздел 6.2 Good Class Interfaces, секция Good Encapsulation.
      • 0
        lair, спасибо за ответ, хочу дополнить:
        А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров?
        Если эти объекты невозможно обработать запросом, то в любом случае они будут загружены в память.
        При этом мы можем сделать выборку только нужных (например 3-4 полей вместо 20).
        События
        Они на то и события, чтобы указывать возможность выполнить код.
        Для зависимых полей у нас тоже есть решение, виртуальные поля
        может выглядеть например так:
        /// <summary>
        /// Выражение для поля суммы
        /// </summary>
        private Expression<Func<RentOfferBase, decimal>> SummExpression = a => a.Price * a.Count;
                
        /// <summary>
        /// Сумма
        /// Вычисляемое поле
        /// </summary>
        [VirtualField("SummExpression")]
        public decimal Summ { get; set; }


        Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое

        К сожалению, классические разработчики не всегда узнают о новых возможностях, например появился в SQL server новый способ выполнить эффективнее запрос, мы «обучаем» делать это ORM и он везде где может применяет это, не забывая, не требуя времени на исправления во всех местах.
    • 0
      Еще раз простите, я тут вытащил код из одного ПХП проекта… это не вы писали случаем?
      pastebin.com/hwFWiayM
      • 0
        Точно не я. Я не использую PHP 4, даю прекрасные английские названия переменным, не использую подчеркивание в начале имени, не люблю imagick и запуск сторонних процессов, и уж точно не написал бы этот нечитаемый ужас.
    • +1
      Не знаю как у Вас, но у нас в php + DoctineORM всё прекрасно. А благодаря использованию мепперов sql генерируется бывает и лучше чем кривыми ручками.

      НО… суть не в этом. ORM это не просто абстракция над хранилищем, но и чёрный ящик который помогает скрыть часть бизнес логики. Например, есть таблица с пользователями и softdelete + versionable, ты просто указал использовать этот шаблон и не паришься что кто-то кривым запросом может убить целостность данных.
  • +5
    эх, уютный enterprise мир, где основная проблема какой бы еще интерфейс к SQL навернуть.
    У нас все бегают с другими словами: денормализация, мемекэши, nosql, шардинг, мастер нода, слейв нода, разогрев мемкэшей, каскадное падение )
    • +2
      А ещё у нас есть пиченьки!
      • +2
        еще спорт зал и страховка… Вот тебе и choose life )
        • +2
          хочу печеньки, спортзал, старховку, шардинг, каскадное падение, отсутствие SPoF, map/reduce. Куда идти?
  • +1
    Не знаю за что можно так не любить ОРМ. Все в одном месте. Валидация — через аннотации. Структура — через метаданные. События — через анноациии и методы внутри моделей. Что ещё нужно для счастья не понимаю.
  • –4
    Автор, а ответьте-ка, как в вашем хваленом ORM реализуются следующие банальные вещи?

    1) Допустим, мы хотим сделать в нашем приложении комментарии. Очевидно, что у комментария есть автор, но если хранить только id автора, и брать его имя из дополнительной таблицы JOIN'ом, то любая база данных затрещит, потому мы хотим хранить в таблице комментариев дополнительно имя и ник автора, сохраняя их в момент создания комментария. Вопрос: можно ли в указанном ORM с помощью аннотаций добавить псевдо-поля «authorName» и «authorNickname», чтобы они формировались при создании/обновлении комментария на основе поля authorId и таблицы пользователей. Без событий и костылей. Это поля, которые просто кешируют данные из связанной таблицы (authors).

    2) мы хотим иерархические комментарии, чтобы список комментариев выбирался из БД легким индексируемым запросом без джойнов. Для этого как нельзя лучше подошли бы Materialized Paths. Можно ли аннотацией без костылей и событий как-то сказать классу, что он хранит иерархические записи с использованием MP?

    3) Теперь мы хотим на главной, под анонсом поста, выводить количество комментариев к нему. Очевидно, что если делать для каждого анонса SELECT COUNT(*), база прикажет долго жить. Потому мы хотим добавить аггрегатное псевдо-поле commentCount, которое будет автоматически инкрементиться/декрементиться/обнуляться при создании/удалении/перемещении комментария? С помощью аннотации типа [field virtual method=aggregate function=count relatedEntity=comments]? С помощью какого-нибудь свойства связи posts to comments?

    Все задачи, которые я привел, банальны и всречаются на каждом втором сайте. ORM, в котором нет встроенных средств для их решения, никак нельзя назвать современным, актуальным и прочее. Такой ORM провоцирует разработчика на написание неэффективного кода (вместо того чтобы предложить встроенный протестированный инструмент для решения задачи). Он годится разве что для сайтика в локалке на 3 пользователей, а не серьезных проектов. А его разработчики видимо перечитали Макконнела и окончательно потеряли связь с реальностью.
    • +3
      2) мы хотим иерархические комментарии, чтобы список комментариев выбирался из БД легким индексируемым запросом без джойнов. Для этого как нельзя лучше подошли бы Materialized Paths. Можно ли аннотацией без костылей и событий как-то сказать классу, что он хранит иерархические записи с использованием MP?

      А я считаю, например, что лучше подходят Nested Sets. Для этих целей у нас есть специальный модуль, который обрабатывает все сущности, реализующие интерфейс IHierarchical where T: Entity

      А теперь вы ответьте-ка, как вы сделаете то же самое без ОРМ, без триггеров(которые на самом деле события).

      3) Теперь мы хотим на главной, под анонсом поста, выводить количество комментариев к нему. Очевидно, что если делать для каждого анонса SELECT COUNT(*), база прикажет долго жить.

      Селект всех постов разом с подсчетом коментов? Про Group By слышали?:)
      Ну, а если сделать, как вы предлагаете, то опять же, есть события сущностей.
      • –4
        > А я считаю, например, что лучше подходят Nested Sets.

        Не пишите такие вещи, если не разбираетесь. С Nested Sets вставка комментария в начало приводит к пересчету left/right key у всех сотен следующих за ним (а ведь это индексированные поля).

        > А теперь вы ответьте-ка, как вы сделаете то же самое без ОРМ, без триггеров(которые на самом деле события).

        Очевидно, я возьму правильный (воображаемый) Data Access Layer, в котором это есть, или напишу плагин, если такового нет.

        > Селект всех постов разом с подсчетом коментов? Про Group By слышали?:)

        Зачем делать Group BY, заставляя сервер пересчитывать тысячи комментариев, когда можно хранить циферку в табличке. И делать селект из ОДНОЙ таблицы без всяких джойнов и груп бай. И вместо шкафа из мощных серверов поставить один. Сдается мне, вы как раз разрабатываете сервисы для локалки на 3 человек. Ну что вы пишете такое.

        Слушайте, а если вам надо будет вывести рейтинг постов с наибольшим числом комментариев, вы тоже Group BY и сортировку (или вложенные запросы? как там принято?) будете делать на таблицах с десятками тысяч постов и миллионами комментариев?
        • +4
          >Не пишите такие вещи, если не разбираетесь. С Nested Sets вставка комментария в начало приводит к пересчету left/right key у всех сотен следующих за ним (а ведь это индексированные поля).

          Могу сказать вам то же самое. На одно добавление коментария будет 1000 его просмотров, а строить дерево по нестед сетам гораздо приятнее.

          >Очевидно, я возьму правильный (воображаемый) Data Access Layer, в котором это есть, или напишу плагин, если такового нет.

          Что вы понимаете под DAL? ORM — это тоже DAL.

          >Сдается мне, вы как раз разрабатываете сервисы для локалки на 3 человек. Ну что вы пишете такое.
          Я разрабатываю сервисы для тысяч. Мне гораздо проще закешировать тот самый запрос(а если быть точнее, весь метод, возвращающий данные для главной страницы), чем денормализовывать базу. Видимо, вы кроме sql ничего слаще не пробовали?:)

          >будете делать на таблицах с десятками тысяч постов и миллионами комментариев?
          десятки тысяч постов — это просто смешно) такие объемы данных — это тестовые данные, не более :)
    • +2
      А почему вы считаете события костылями?
      действительно, задача очень даже типовая и встречается часто, и вот как бы я её решил:
      1. Выделил бы интерфейс для сущностей в которых необходимо кэшировать ник/имя
      public interface  IWithCachedUser
      {
          /// <summary>Ссылка на пользователя</summary>
          User User { get; set; }
              
          /// <summary>Имя пользователя (для кэширования)</summary>
          string UserName { get; set; }
              
          /// <summary>Ник</summary>
          string UserNick { get; set; }
      }
      2.Написал бы простой код проставляющий значения этих полей:
      public class CacheUserEvent<TEntity> : IEntityEvent<TEntity>
          where TEntity : EntityBase, IWithCachedUser
      {
          public void AfterSave(TEntity item)
          {
              item.UserName = item.User.Name;
              item.UserNick = item.User.Nick;
          }
      }<source>
      Теперь для всех классов, реализующих этот интерфейс есть кэширование и не обязательно делать джойны.
    • +2
      Все, о чем вы пишете — это оптимизация по производительности. Такая оптимизация практически всегда нарушает и нормализацию, и «хорошее проектирование». Соответственно, такие вещи можно и нужно делать на уровне БД, потому что оптимизируем мы БД.
  • +3
    Понятно же что ORM не для хайлода. Есть задачи где ORM необходим, и есть где просто не нужен.
    • +2
      Ну не всегда так, после того как на одном высоконагруженном проекте внедрили BLToolkit провел нагрузочное тестирование и остался очень доволен.
      • 0
        BLToolkit очень легковесный ORM я бы сказал. Особенно по сравнению со всем остальным в дотнете.
      • +2
        ormbattle.net/ вот тут есть неплохое сравнение, правда довольно старое
    • +2
      code.google.com/p/dapper-dot-net/ один хороший легковесный ORM (конечно он больше маппер, чем полноценная ORM) — с очень хорошим «портфолио» в виде сайта хайлоад сайта stackoverflow.com.
  • +2
    Почитал все комментарии — выводы:
    — отношение к ORM-решениям у разных людей может быть совершенно разное — все очень сильно зависит от опыта работы с конкретными решениями и специфики проектов, над которыми они работали.
    — очень многие из этих людей готовы отстаивать свою точку зрения, несмотря на понимание того, что единой точки зрения тут явно быть не может :) (все очень сильно зависит от массы факторов).

    На мой взгляд, что ORM обычно эффективны в сценариях, когда скорость разработки очень важна. На мой взгляд, связь примерно такая:
    — Если производительнось исключительно важна (вы делаете облачный сервис, который будет обслуживать миллионы клиентов), вероятно, вам лучше отказаться от сложных ORM, а возможно, и от SQL DB вообще. Ваши лучшие друзья — memcached, Redis, C++ и т.п., т.к. расходы на разработку в данном случае могут быть нивелированы расходами на эксплуатацию.
    — Если для вас крайне важна скорость разработки — берите любое более-менее известное ORM-решение, использование которого в вашем конкретном случае даст максимальный эффект. В производительности вы потеряете не так уж и много, но заметно ускорите сам процесс разработки.

    В промежуточном сенарии я бы все же выбрал ORM, но пользовался бы SOA/DTO для реализации уровня абстракции, т.е. заранее предусмотрел бы возможность отказа как от ORM, так и от текущей БД.

    Возможно, ответ слегка biased, т.к. я — один из разработчиков DataObjects.Net.
  • +6
    И, раз уж здесь была затронута тема о том, что стоит выносить на уровень БД, озвучу свое мнение:

    IMO, лучше всеми возможными способами избегать использование хранимых процедур, триггеров и прочего кода на SQL, сильно связанного с логикой работы приложения. Причин тому несколько, вот главные из них:
    — Сложный код на SQL (все, что не является запросом) — ужасен. Его отладка, профайлинг и поддержка (ревизии в репозитории и т.п.) в разы дороже, чем в случае с кодом на любом современном языке.
    — То, что код, работающий в процессе БД спасет вас в случае роста нагрузки — миф. Скорее спасет partitioning / sharding, и денормалиация (пример: goo.gl/dN7tl ). И конечно же, разработчики, хорошо понимающие, чем отличается full scan от index seek.
  • 0
    Одинэсники смотрят на этот джихад с умилением. =)

    А если серьезно — ORM рулит и педалит на сложных и динамичных проектах, где скорость и легкость разработки важнее некоторого выигрыша в рантайме. Просто не передать словами тот кайф, когда не нужно забивать голову деталями хранения и получения данных.

    А еще ORM прекрасно ложится на NoSQL, что дает огромный прирост производительности без малейших усилий со стороны разработчиков. =)
    • 0
      > ORM прекрасно ложится на NoSQL

      Вот это как раз неверно :) В см. как правило, там не нужен ORM, т.к. в БД можно сохранять объекты «как есть». Соответственно, не нужен маппинг, сложные преобразования запросов и т.п.

      Т.е. те плюсы, которые дает ORM РСУБД-разработчикам, NoSQL-разработчики получают от client-side API.
      • 0
        ORM почти полностью скрывает СУБД от разработчика, так что пофиг, что там — SQL, не-SQL, реляционная или еще какая. Для не-SQL процесс преобразования объектов в сущности хранилища намного проще и быстрее, но полностью не исключен. Все-таки нужно как-то сериализовать объект в нужном виде, выделять ключевые и индексируемые поля, и все такое…

        Возможно, при этом теряется смысл буквы «R» в слове ORM. Но я не знаю другой, более подходящей по смыслу аббревиатуры.
        • 0
          Да, это скорее терминологический вопрос — в см. это уже не ORM, т.к. сложных преобразований (в том числе и для запросов) NoSQL-клиенты не делают. Как правило, все сводится к change tracking и сериализации.

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