Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 21.04.10 15:35
Оценка:
Пишу дсл для миграций.
Создание таблицы выглядит так:
  create TestTable
  {
    Id : Guid(pk);
    Zip : string?(len = 10) = "zzzz";
    Price : int = 10;
  }

Какой синтакисис указания foreign key будет интуитивно понятнее для немерлиста? Сам склоняюсь ко второму
   PersonId => Person;
   (f1, f2) => Person?; // несколько нулабельных колонок ссылаются на таблицу с композитным ключем
   
   PersonId :> Person;
   (f1, f2) :> Person?;
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re: Какой синтакис интуитивнее?
От: Аноним  
Дата: 21.04.10 16:34
Оценка: 13 (2)
Здравствуйте, Ziaw, Вы писали:

Z>Какой синтакисис указания foreign key будет интуитивно понятнее для немерлиста? Сам склоняюсь ко второму

Z>
Z>   PersonId => Person;
Z>   (f1, f2) => Person?; // несколько нулабельных колонок ссылаются на таблицу с композитным ключем
   
Z>   PersonId :> Person;
Z>   (f1, f2) :> Person?; 
Z>


Второй, ибо оператор :> имеет отношение к типам данных и в каком-то смысле связан с понятием FK. А оператор => менее релевантен.
Re: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.04.10 16:41
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Какой синтакисис указания foreign key будет интуитивно понятнее для немерлиста? Сам склоняюсь ко второму

Z>
Z>   PersonId => Person;
Z>   (f1, f2) => Person?; // несколько нулабельных колонок ссылаются на таблицу с композитным ключем
   
Z>   PersonId :> Person;
Z>   (f1, f2) :> Person?; 
Z>


А можно так:
PersonId FK(Person);


Хотя сама идея оперировать в терминах БД мне не нравится. Лучше оперировать термином ассоциация (из Линка).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 21.04.10 16:58
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Хотя сама идея оперировать в терминах БД мне не нравится. Лучше оперировать термином ассоциация (из Линка).


Ага, а ассоциация с типом (Person как таблица так и тип модели) все же лучше :>.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[3]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.04.10 17:25
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Ага, а ассоциация с типом (Person как таблица так и тип модели) все же лучше :>.


:> — это оператор используемый для приведения типов. Может быть путаница.

А как это указывается в рельстах?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Какой синтакис интуитивнее?
От: hardcase Пират http://nemerle.org
Дата: 21.04.10 18:50
Оценка: +1
Здравствуйте, Ziaw, Вы писали:

Z>Пишу дсл для миграций.

Z>Создание таблицы выглядит так:
Z>
Z>  create TestTable
Z>  {
Z>    Id : Guid(pk);
Z>    Zip : string?(len = 10) = "zzzz";
Z>    Price : int = 10;
Z>  }
Z>

Z>Какой синтакисис указания foreign key будет интуитивно понятнее для немерлиста? Сам склоняюсь ко второму
Z>
Z>   PersonId => Person;
Z>   (f1, f2) => Person?; // несколько нулабельных колонок ссылаются на таблицу с композитным ключем
   
Z>   PersonId :> Person;
Z>   (f1, f2) :> Person?; 
Z>



А обязательно придумывать или перегружать крякозябры? Я стандартные-то смайлики ( %| ) для работы с флагами с трудом вспоминаю...
Может лучше словами — FK, Assoc или что-то на тему.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 01:18
Оценка:
Здравствуйте, hardcase, Вы писали:

H>А обязательно придумывать или перегружать крякозябры? Я стандартные-то смайлики ( %| ) для работы с флагами с трудом вспоминаю...

H>Может лучше словами — FK, Assoc или что-то на тему.

Это проблема всех DSL, их надо знать .

Мне кажется нет двух путей, либо пытаться остаться в синтаксисе:
Price : int = 10;

Либо отказаться от DSL и просто сделать удобный API, как в рельсах. Если nemerle позволяет писать fk перед типом, т.е. можно создать валидные выражения вида:
PersionId : fk Person;
(PersionId, OrderId) : fk PersonToOrders;
(PersionId, OrderId) : fk PersonToOrders?;
(PersionId, OrderId) : fk PersonToOrders(pk);

то я могу включить альтернативный синтаксис, не проблема, но мне все равно больше нравится:
PersionId :> Person;
(PersionId, OrderId) :> PersonToOrders;
(PersionId, OrderId) :> PersonToOrders?;
(PersionId, OrderId) :> PersonToOrders(pk);
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[4]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 05:55
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, Ziaw, Вы писали:


Z>>Ага, а ассоциация с типом (Person как таблица так и тип модели) все же лучше :>.


VD>:> — это оператор используемый для приведения типов. Может быть путаница.


А Person это тип модели, мы объявляем фактически ссылку на него.

VD>А как это указывается в рельстах?


Когда я их юзал никак, просто делались поля нужного типа. На констрейнты они забивали.
Но сейчас вроде что-то появилось, хотя доки скудные:
  create_table :taggings do |t|
    t.references :tag
    t.references :tagger, :polymorphic => true
    t.references :taggable, :polymorphic => { :default => 'Photo' }
  end

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#M001985

Я так понимаю, первой строчкой создается колонка tag_id ссылающаяся на таблицу tags.
DSL там построен через API и такой синтаксис именно поэтому, я могу такой же сделать на экстеншенах, правда вместо символов будут строки. Но мой DSL мне пока нравится больше.
Re[4]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 05:56
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>:> — это оператор используемый для приведения типов. Может быть путаница.


Кстати я не перегружаю операторы, просто разбираю экспрешены внутри макросов. Если ты насчет этого беспокоишься.
Re[5]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 13:56
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Здравствуйте, VladD2, Вы писали:


VD>>:> — это оператор используемый для приведения типов. Может быть путаница.


Z>Кстати я не перегружаю операторы, просто разбираю экспрешены внутри макросов. Если ты насчет этого беспокоишься.


Я говорю о смысловой перегрузке, т.е. о том как это дело будет воспринимать человек.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 14:12
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Когда я их юзал никак, просто делались поля нужного типа. На констрейнты они забивали.


Ниже приведена выдержка из статьи по Грэилс (Грувийные Рельсы). В Ргуви можно задавать (опционально) типы. Они этим пользуются. Думаю не помешает знать как у них это дело сделано:

2. GORM: забавное название, серьезная технология

Эта часть посвящена другой области применения Grails: персистентности с использованием Grails Object Relational Mapping (GORM) API. Я начну с описания того, что такое объектно-реляционное отображение в Grails и с создания отношения один-ко-многим. Затем вы узнаете о проверках данных, гарантирующих, что ваше приложение не будет страдать синдромом «мусор на входе/мусор на выходе». Вы увидите язык предметной области GORM в действии, и он позволит вам настроить сохранение ваших POGO (plain old Groovy objects). Наконец, вы увидите, как просто переключиться с одной реляционной СУБД на другую. Подойдет любая СУБД, для которой имеется JDBC-драйвер и диалект Hibernate.

Определение ORM

Реляционные СУБД возникли в конце 70-х, но разработчики ПО по сей день бьются над эффективным извлечением и записью данных. Сегодняшнее ПО основано не на реляционных принципах, используемых в популярных СУБД, а на объектно-ориентированных.
Целый класс программ – ORM – служит для упрощения перемещения данных между БД и кодом. Hibernate, TopLink и Java Persistence API (JPA) – три популярные Java API, служащие этой цели (см. Ресурсы), хотя совершенным не является ни один из них. Проблема настолько персистентна (каламбур случайный), что имеет собственное крылатое выражение «объектно-реляционная несовместимость» (object-relational impedance mismatch, см. Ресурсы).
GORM – это тонкий Groovy-фасад над Hibernate. Это значит, что все ваши Hibernate-приемы будут работать – например, поддерживаются HBM файлы отображения и аннотации – но в этой статье речь пойдет об интересных возможностях самого GORM.

Создание отношения один-ко-многим

Легко недооценить сложности сохранения POGO в поля БД. На самом деле, если POGO отображается на одну таблицу, все довольно просто – свойства POGO точно соответствуют колонкам таблицы. Но если чуть-чуть усложнить объектную модель, например, ввести два POGO, ссылающихся друг на друга, все сразу станет намного сложнее.
Посмотрите, например, на Web-сайт Trip Planner, который мы начали в прошлой статье. Неудивительно, что POGO Trip играет в приложении важную роль. Откройте в текстовом редакторе grails-app/domain/Trip.groovy (листинг 1):
Листинг 1. Класс Trip
class Trip 
{ 
  String name
  String city
  Date startDate
  Date endDate
  String purpose
  String notes
}

Каждый из атрибутов в Листинге 1 легко и просто отображается на соответствующее поле таблицы Trip. В прошлой статье говорилось, что все POGO, которые хранятся в каталоге grails-app/domain, получают дополнительную таблицу, которая автоматически создается при запуске Grails. По умолчанию Grails использует встроенную БД HSQLDB, но к концу этой статьи вы сможете использовать любую реляционную СУБД по вашему выбору.
Поездка часто включает перелет, поэтому есть смысл создать класс Airline (показанный в листинге 2):
Листинг 2. Класс Airline.
class Airline
{ 
  String name
  String url
  String frequentFlyer
  String notes
}

Теперь вы захотите связать эти два класса. Чтобы запланировать перелет в Чикаго рейсом Xyz Airlines, вы представляете его в Groovy так же, как в Java-коде – добавляя в класс Trip свойство Airline (см. листинг 3). Эта техника называется композицией объектов (см. Ресурсы).
Листинг 3. Добавление свойства Airline в класс Trip.

class Trip 
{ 
  String name
  String city
  ...
  Airline airline
}


Все это здорово в программной модели, но в реляционных БД используется несколько иной подход. Каждая запись в таблице обладает уникальным ID, или первичным ключом. Добавление поля airline_id в таблицу Trip позволит связать одну запись с другой (в данном случае запись "Xyz Airlines" с записью "Chicago trip"). Это называется отношением один-ко-многим: с одной авиалинией может быть ассоциировано множество поездок (примеры отношений один-ко-многим и многие-ко-многим можено найти в онлайн-документации Grails, см. Ресурсы).
У предложенной схемы БД есть всего одна проблема. Вы можете иметь успешно нормализованную БД (см. Ресурсы), но теперь колонки таблицы рассинхронизированы с программной моделью. Если заменить поле Airline полем AirlineId, то вы допустите «просачивание» деталей реализации (факта сохранения POGO в БД) в объектную модель. Джоэль Спольски называет это Законом дырявых абстракций (см. http://russian.joelonsoftware.com/Articles/LeakyAbstractions.html).
GORM помогает смягчить проблему дырявых абстракций, позволяя представить объектную модель способом, имеющим смысл для Groovy. Он скрыто занимается вопросами реляционной БД за вас, но, как вы сейчас увидите, можно легко переопределить настройки по умолчанию. GORM – это не непрозрачный слой, скрывающий от вас детали БД, а полупрозрачный – он пытается делать все правильно сам, но не встает у вас на пути, если вы захотите изменить его поведение. Это позволяет вам получить лучшее из двух миров.
Вы уже добавили свойство Airline в POGO Trip. Чтобы завершить отношение один-ко-многим, добавьте в POGO Airline настройку hasMany, как показано в листинге 4:
Листинг 4. Создание отношения один-ко-многим в Airline

class Airline 
{ 
  static hasMany = [trip:Trip]

  String name
  String url
  String frequentFlyer
  String notes
}


Каскадное удаление

В имеющейся модели вы можете получить «висячие» записи в БД: при удалении Airline останутся записи Trip, ссылающиеся на несуществующих родителей. Чтобы исключить это, можно добавить коллекцию типа hashmap static belongsTo в класс, описывающий сторону «многих».
Статическая настройка hasMany – это Groovy коллекция типа hashmap: ключ – это trip; значение – класс Trip. Если вы хотите создать еще одно отношение один-ко-многим для класса Airline, можно написать разделенный запятыми список пар ключ/значение в квадратных скобках.
Теперь создадим маленький класс AirlineController (см. Листинг 5) в grails-app/controllers, чтобы увидеть новое отношение один-ко-многим в действии:
Листинг 5. Класс AirlineController

class AirlineController 
{ 
  def scaffold = Airline
}


Вспомним из прошлой статьи, что def scaffold говорит Grails динамически во время исполнения создать базовые методы list(), save() и edit(). Он также говорит Grails динамически создать представления GroovyServer Page (GSP). И TripController, и AirlineController содержат def scaffold. Если после ввода grails generate-all у вас сохранились каталоги trip или airline в grails-app/views, их нужно удалить. В этом примере мы проверяем, что Grails создает контроллеры и представления динамически.
Теперь, когда доменные классы и контроллеры на месте, запустите Grails. Введите grails prod run-app, чтобы запустить приложение в рабочем режиме. Если все в порядке, вы увидите следующее сообщение:
Server running. Browse to http://localhost:8080/trip-planner

Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 14:47
Оценка:
Здравствуйте, Ziaw, Вы писали:

H>>Может лучше словами — FK, Assoc или что-то на тему.


Z>Это проблема всех DSL, их надо знать .


Это проблема плохих ДСЛ-ей. Хорошие ДСЛ-и понятны интуитивно.

Лично я предпочел бы что-то вроде:
Association(тип)
где тип это:
* 1-many
* 1-1
* many-many
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 14:49
Оценка:
Здравствуйте, VladD2, Вы писали:

Какое отношение описание моделей имеет к синтаксису миграций?

VD>У предложенной схемы БД есть всего одна проблема. Вы можете иметь успешно нормализованную БД (см. Ресурсы), но теперь колонки таблицы рассинхронизированы с программной моделью.

VD>Если заменить поле Airline полем AirlineId, то вы допустите «просачивание» деталей реализации (факта сохранения POGO в БД) в объектную модель. Джоэль Спольски называет это Законом дырявых абстракций (см. http://russian.joelonsoftware.com/Articles/LeakyAbstractions.html).
VD>GORM помогает смягчить проблему дырявых абстракций, позволяя представить объектную модель способом, имеющим смысл для Groovy. Он скрыто занимается вопросами реляционной БД за вас, но, как вы сейчас увидите, можно легко переопределить настройки по умолчанию. GORM – это не непрозрачный слой, скрывающий от вас детали БД, а полупрозрачный – он пытается делать все правильно сам, но не встает у вас на пути, если вы захотите изменить его поведение. Это позволяет вам получить лучшее из двух миров.

Вобщем-то единственно полезный абзац. Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится. В рельсах другой подход, с ним я тоже знаком. Вдобавок там ActiveRecord, который я точно не собираюсь тащить в проект.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[4]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 14:52
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Лично я предпочел бы что-то вроде:

VD>Association(тип)
VD>где тип это:
VD>* 1-many
VD>* 1-1
VD>* many-many

Я так не понял как будет выглядеть интуитивное создание колонки, я привел свой вариант, приведи аналогичный.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[7]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 16:01
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится.


Где это он заставляет о таком помнить?

Он заставляет помнить, что данные имеют реляционную природу и не являются полноценными ОО-данными. Но это не тоже самое что возиться с деталями.

Те же ассоциации сделаны именно для того, чтобы абстрагироваться от возиться с ключами и прочей СУБД-шной фигней.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 16:18
Оценка:
Здравствуйте, VladD2, Вы писали:

Z>>Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится.


VD>Где это он заставляет о таком помнить?


У него нет лейзи. Он не умеет трекать изменения. Поэтому идеология "давайте спрячем AirplaneId и оставим только Airplane" в нем не прокатит. В гибернейте прокатила бы. Там можно создать lazy свойство Airplane и доступ к его айдишнику не заставит гибер лезть в базу, а к полям заставит. Тру абстракция. Когда дойдут руки до поддержки гибернейта ты сможешь это использовать. Если мы так сделаем в тулките — он просто не загрузит Airplane, это сделает работу с ассоциациями чертовски неудобной. И в любом случае никакого отношения к миграциям эти принципы не имеют.

VD>Он заставляет помнить, что данные имеют реляционную природу и не являются полноценными ОО-данными. Но это не тоже самое что возиться с деталями.


VD>Те же ассоциации сделаны именно для того, чтобы абстрагироваться от возиться с ключами и прочей СУБД-шной фигней.


Только базу все равно надо создавать, делать индексы и форинкеи. Для этого я и пишу DSL миграций. Чтобы абстрагироваться от конкретного диалекта DDL и специфических типов данных. Чтобы кратко и декларативно менять схему базы. Есть конкретные идеи — я всегда рад выслушать.

P.S. Влад, извини, но мне кажется ты уже сложил негативное впечатление о проекте и пытаешься что-то доказать по инерции. Не задумываясь даже о чем идет речь. Если это так, давай сэкономим время друг друга, это здорово поможет нашим проектам.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[6]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 16:21
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Я говорю о смысловой перегрузке, т.е. о том как это дело будет воспринимать человек.


Я воспринимаю это так: тип данных PersonId имеет способы преобразования в Person.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[5]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 16:57
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Здравствуйте, VladD2, Вы писали:


VD>>Лично я предпочел бы что-то вроде:

VD>>Association(тип)
VD>>где тип это:
VD>>* 1-many
VD>>* 1-1
VD>>* many-many

Z>Я так не понял как будет выглядеть интуитивное создание колонки, я привел свой вариант, приведи аналогичный.


Как-то так:
  create Person
  {
    Name      : string;
    Addresses : Association[Addresse]
//  описание можно задать и явно:
//  Addresses : Association[Addresse](1-many)
  }

  create Address
  {
    Country : string;
    City    : string;
    Street  : string;
    Hous    : string;
  }

  create Product
  {
    Name   : string;
    Price  : double;
    Orders : Association[Order]; // many-many, так как аналогичная ассоциация есть в Order
  }

  create Order
  {
    Date     : DateTime;
    Num      : string;
    Products : Association[Product]; // many-many, так как аналогичная ассоциация есть в Order
//  описание можно задать и явно:
//  Products : Association[Product](many-many)

    defkey[DateNum](Date, Num) : Unique; // описание уникального ключа (для него создается индекс)
  }

При этом для таблиц где явно не заданы поля (все в данном примере) автоматически создаются ключевые поля, например, OrderId для таблицы Order.

Для связей так же заводятся дополнительные поля описывающие внешние ключи.

Для связей many-many (многие ко многим) так же заводится промежуточная таблица с именем ИмяПервойТаблицыToИмяВторойТаблицы. Так для связи Product-Order заводится таблица ProductToOrder с полями OrderId и ProductId входящими в первичный ключ.

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

Тоже самое пир удалении. Мы должны иметь возможность сказать, что удаляем ассоциацию, а библиотека сгенерирует код удаленя всех сопутствующих деталей реализации.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Какой синтакис интуитивнее?
От: Ziaw Россия  
Дата: 22.04.10 17:39
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>При этом для таблиц где явно не заданы поля (все в данном примере) автоматически создаются ключевые поля, например, OrderId для таблицы Order.


Интересная идея. Я подумаю. Возможно я сделаю просто сокращенный синтаксис. Например так
create Test
{
    id; // краткая запись для Id : Guid() = Sql.Newid;
    Text : string;
}

или так
create Test
{
    noid; // если не указать это, то ключ будет сгенерирован
    Text : string;
}


VD>Для связей many-many (многие ко многим) так же заводится промежуточная таблица с именем ИмяПервойТаблицыToИмяВторойТаблицы. Так для связи Product-Order заводится таблица ProductToOrder с полями OrderId и ProductId входящими в первичный ключ.


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


Ты путаешь описание модели и код миграций. В коде миграций создают только внешние ключи. В описании модели уже можно создавать ассоциации, это будут отдельные макры. К миграциям отношения не имеющие. А в миграциях DSL должен прозрачно ложиться в DDL.

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


дроп колонки, что может быть проще?

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

Ты настойчиво пытаешься увидеть модель в миграциях, миграции это не модель. Это инструмент эволюционной разработки, который дает возможность постепенно вносить изменения в схему базы не теряя при этом данных. Можно плясать от модели, да миграции будут не нужны, но модификации модели будут приводить к неинтуитивным автоматическим действиям с БД. Мне эта особенность очень не нравится. Мне нравится указывать явно, все что я хочу сделать со своей базой для приведения ее в новое состояние.
... << RSDN@Home 1.2.0 alpha 4 rev. 1468>>
Re[9]: Какой синтакис интуитивнее?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 17:51
Оценка:
Здравствуйте, Ziaw, Вы писали:

VD>>Где это он заставляет о таком помнить?


Z>У него нет лейзи.


Это тут не причем. Его провайдер ориентирован на запросы. Нужна ленивая загрузка — пиши запрос.

Z> Он не умеет трекать изменения.


Умеет, но отдельным объектом.

Опять же — это не имеет отношения к оголению абстракций конкретной СУБД или БД в общем.

Z>Поэтому идеология "давайте спрячем AirplaneId и оставим только Airplane" в нем не прокатит.


Это в корне не верный взгляд!

БЛТулкит не принуждает отказываться от абстракций! Он призывает не рассматривать данные как объекты в полном понимании этого слова. Вместо этого он предлагает рассматривать данные как набор простых типов и отношений между ними, т.е. как реляционную (Entity-Relation) схему.

Z>В гибернейте прокатила бы. Там можно создать lazy свойство Airplane и доступ к его айдишнику не заставит гибер лезть в базу, а к полям заставит. Тру абстракция. Когда дойдут руки до поддержки гибернейта ты сможешь это использовать. Если мы так сделаем в тулките — он просто не загрузит Airplane, это сделает работу с ассоциациями чертовски неудобной. И в любом случае никакого отношения к миграциям эти принципы не имеют.


У тебя каша в рассуждениях.

Причем тут lazy?

То что ты описываешь Ассоциацию или даже свойство не значит, что через это свойство можно будет foreach-ем перебрать эти зависимые объекты, и что они автоматом закэшируются или будут подниматься лениво.

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

Вот что об ассоциациях пишет сам IT:

Ассоциации

Ассоциациями называются связи между сущностями. Ассоциации задаются с помощью атрибутов и позволяют существенно упростить запросы за счёт того, что устраняют необходимость каждый раз указывать соединения между связываемыми таблицами.
Ниже приведена таблица Product и её ассоциации.
[TableName("Products")]
public abstract class Product
{
    [PrimaryKey, Identity] public int      ProductID;
    [NotNull]              public string   ProductName;
                           public int?     SupplierID;
                           public int?     CategoryID;
                           public string   QuantityPerUnit;
                           public decimal? UnitPrice;
                           public short?   UnitsInStock;
                           public short?   UnitsOnOrder;
                           public short?   ReorderLevel;
                           public bool     Discontinued;

    [Association(ThisKey="ProductID",  OtherKey="ProductID")]
    public List<OrderDetail> OrderDetails;

    [Association(ThisKey="CategoryID", OtherKey="CategoryID", CanBeNull=false)]
    public Category Category;

    [Association(ThisKey="SupplierID", OtherKey="SupplierID", CanBeNull=false)]
    public Supplier Supplier;
}


Что нужно знать об ассоциациях?
* Член класса, к которому применён атрибут ассоциации, может иметь либо списочный тип, либо являться ссылкой на объект. Списочный тип означает связь один-ко-многим. То есть в нашем случае имеется один Product и много Order Details. Просто ссылка на объект озна-чает связь один-к–одному, либо много-к-одному, что для нас уже не важно, т.к. на генера-цию SQL это никак не влияет.
* Сама связь задаётся парой свойств атрибута ThisKey/OtherKey. Свойство ThisKey пере-числяет через запятую поля самой сущности, в которой объявлена ассоциация. Свойство OtherKey – соответствующие поля связанной сущности. Если имена полей сущности и ис-ходной таблицы базы данных отличаются, то в этих свойствах указываются имена полей сущности.
* Свойство CanBeNull указывает BLToolkit, какую связь генерировать: Inner Join или Left Join. Будьте внимательны с этим свойством. Left Join, примененный не по делу, может весьма существенно увеличить время выполнения запроса. К тому же BLToolkit может произво-дить дополнительные оптимизации для связей один-к-одному и убирать лишние связи из запроса, если это Inner Join. В общем случае правило использования этого атрибута такое: для связей один-ко-многим CanBeNull может быть равно false только в случае, если мас-тер-запись не может появиться в базе данных без дочерних (например, Order -> OrderDetails); для связей один-к-одному CanBeNull чаще всего должно быть равно false, но это нужно указать явно.
* Члены класса, к которым применена ассоциация, не заполняются автоматически при чте-нии объекта из базы данных.
Теперь посмотрим, что у нас получилось:
from p in db.Product
select new
{
    p.Category.CategoryName,
    p.ProductName
};

Явное указание соединения (Join) с таблицей Category исчезло, но генерируемый SQL остался прежним:
SELECT
    [t1].[CategoryName],
    [p].[ProductName]
FROM
    [Products] [p]
        INNER JOIN [Categories] [t1] ON [p].[CategoryID] = [t1].[CategoryID]

Давайте попробуем поменять у ассоциации Category значение свойства CanBeNull на true и по-смотрим на результат:
SELECT
    [t1].[CategoryName],
    [p].[ProductName]
FROM
    [Products] [p]
        LEFT JOIN [Categories] [t1] ON [p].[CategoryID] = [t1].[CategoryID]

Теперь у нас получился Left Join.
Использовать списочные ассоциации можно, например, следующим образом:
from p in db.Product
select new
{
    p.OrderDetails.Count,
    p.ProductName
};

SQL:
SELECT
    (
        SELECT
            Count(*)
        FROM
            [Order Details] [t1]
        WHERE
            [p].[ProductID] = [t1].[ProductID]
    ) as [c1],
    [p].[ProductName]
FROM
    [Products] [p]

Как было сказано выше, связанные сущности не заполняются автоматически при создании объек-та, но при желании это можно сделать вручную:
from o in db.Order
select new Northwind.Order
{
    OrderID  = o.OrderID,
    Customer = o.Customer
};

SQL:
SELECT
    [o].[OrderID],
    [t1].[CustomerID],
    [t1].[CompanyName],
    [t1].[ContactName],
    [t1].[ContactTitle],
    [t1].[Address],
    [t1].[City],
    [t1].[Region],
    [t1].[PostalCode],
    [t1].[Country],
    [t1].[Phone],
    [t1].[Fax]
FROM
    [Orders] [o]
        INNER JOIN [Customers] [t1] ON [o].[CustomerID] = [t1].[CustomerID]

Ассоциации являются очень мощным средством, позволяющим с лёгкостью писать самые слож-ные запросы. Давайте попробуем написать запрос с многоуровневыми ассоциациями:
from o in db.OrderDetail
select new
{
    o.Product.ProductName,
    o.Order.OrderID,
    o.Order.Employee.ReportsToEmployee.Region
};

SQL:
SELECT
    [t1].[ProductName],
    [o].[OrderID],
    [t2].[Region]
FROM
    [Order Details] [o]
        INNER JOIN [Products] [t1] ON [o].[ProductID] = [t1].[ProductID]
        INNER JOIN [Orders] [t4]
            LEFT JOIN [Employees] [t3]
                LEFT JOIN [Employees] [t2] ON [t3].[ReportsTo] = [t2].[EmployeeID]
            ON [t4].[EmployeeID] = [t3].[EmployeeID]
        ON [o].[OrderID] = [t4].[OrderID]

Обратите внимание на то, что для разных таблиц используются разные типы Join. Это результат применения параметра CanBeNull.
Ещё одним интересным моментом является использования в Select поля OrderID из таблицы Or-derDetails, хотя в самом Linq-запросе используется поле таблицы Orders. Это как раз та самая оп-тимизация, которую выполняет BLToolkit, чтобы убрать лишние соединения. Давайте немного из-меним пример:
from o in db.OrderDetail
select new
{
    o.Product.ProductName,
    o.Order.OrderID,
    //o.Order.Employee.ReportsToEmployee.Region
};

SQL:
SELECT
    [t1].[ProductName],
    [o].[OrderID]
FROM
    [Order Details] [o]
        INNER JOIN [Products] [t1] ON [o].[ProductID] = [t1].[ProductID]

Так как таблица Orders для доступа к Employee нам теперь не нужна, а использование OrderID из OrderDetails не меняет логики запроса, то соединение с Orders тоже исчезло.
Кроме всего прочего, однотипные ассоциативные объекты разных сущностей можно сравнивать:
from o in db.Order
from t in db.EmployeeTerritory
where o.Employee == t.Employee
select new
{
    o.OrderID,
    o.EmployeeID,
    t.TerritoryID
};

SQL:
SELECT
    [o].[OrderID],
    [o].[EmployeeID],
    [t1].[TerritoryID]
FROM
    [Orders] [o], [EmployeeTerritories] [t1]
WHERE
    [o].[EmployeeID] = [t1].[EmployeeID]

И использовать для группировки:
from p in db.Product
group p by p.Category into g
where g.Count() == 12
select g.Key.CategoryName;

SQL:
SELECT
    [t1].[CategoryName]
FROM
    [Products] [p]
        INNER JOIN [Categories] [t1] ON [p].[CategoryID] = [t1].[CategoryID]
GROUP BY
    [t1].[CategoryID],
    [t1].[CategoryName]
HAVING
    Count(*) = 12

Для сравнения и группировки по объектам BLToolkit использует информацию о первичных ключах объекта. Если первичный ключ не задан, то BLToolkit генерирует код, сравнивающий все поля объекта друг с другом. Это, во-первых, неэффективно, а, во-вторых, не всегда возможно, т.к. не все типы в SQL можно сравнивать.
В качестве эксперимента давайте попробуем убрать у сущности Employee первичный ключ и вы-полнить предыдущий запрос ещё раз. В результате получится следующий SQL:
SELECT
  [o].[OrderID],
  [o].[EmployeeID],
  [t3].[TerritoryID]
FROM
  [Orders] [o]
    LEFT JOIN [Employees] [t1] ON [o].[EmployeeID] = [t1].[EmployeeID],
  [EmployeeTerritories] [t3]
    LEFT JOIN [Employees] [t2] ON [t3].[EmployeeID] = [t2].[EmployeeID]
WHERE
  [o].[EmployeeID] = [t3].[EmployeeID] 
    AND [t1].[LastName] = [t2].[LastName] 
    AND [t1].[FirstName] = [t2].[FirstName] 
    AND [t1].[Title] = [t2].[Title] 
    AND [t1].[TitleOfCourtesy] = [t2].[TitleOfCourtesy] 
    AND ([t1].[BirthDate] IS NULL
    AND [t2].[BirthDate] IS NULL 
  OR [t1].[BirthDate] IS NOT NULL 
    AND [t2].[BirthDate] IS NOT NULL
    AND [t1].[BirthDate] = [t2].[BirthDate])
    AND ([t1].[HireDate] IS NULL
    AND [t2].[HireDate] IS NULL
  OR [t1].[HireDate] IS NOT NULL
    AND [t2].[HireDate] IS NOT NULL
    AND [t1].[HireDate] = [t2].[HireDate])
    AND [t1].[Address] = [t2].[Address]
    AND [t1].[City] = [t2].[City]
    AND [t1].[Region] = [t2].[Region]
    AND [t1].[PostalCode] = [t2].[PostalCode]
    AND [t1].[Country] = [t2].[Country]
    AND [t1].[HomePhone] = [t2].[HomePhone]
    AND [t1].[Extension] = [t2].[Extension]
    AND [t1].[Notes] = [t2].[Notes]
    AND ([t1].[ReportsTo] IS NULL
    AND [t2].[ReportsTo] IS NULL
  OR [t1].[ReportsTo] IS NOT NULL
    AND [t2].[ReportsTo] IS NOT NULL
    AND [t1].[ReportsTo] = [t2].[ReportsTo])
    ANDы [t1].[PhotoPath] = [t2].[PhotoPath]

Мало того, что на этот запрос без слёз смотреть нельзя, так он ещё и не работает, так как среди полей таблицы Employees есть поля с типом ntext, которые нельзя сравнивать.
Будьте внимательны при описании модели данных. BLToolkit использует метаинформацию о сущностях для оптимизации запросов, и её отсутствие может привести к построению неоптимальных запросов.


Так что БЛТулкит поддерижвает все что нужно. Просто, кончено же, он поддерживает все это дело на более низком уровне.

Делая аналог Рельс для Немерла было бы логично предоставить более высокуровневый вариант описания (на ряду с низкоуровневым) который позволил уменшить объем не нужной писанины и получить дополнительный уровень абстракции.

Z>Только базу все равно надо создавать, делать индексы и форинкеи.


И из этого следут, что нужно оябзательно описывать ее на самом низком уровне выставляя на показ все (даже не нужные) детали?

Z>Для этого я и пишу DSL миграций. Чтобы абстрагироваться от конкретного диалекта DDL и специфических типов данных.


+1

Z>Чтобы кратко и декларативно менять схему базы.


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

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

Так и надо поступать.

Проблемы Рельсов возникают отнюдь не во время описания миграций или эктив-рекордов. Они возникают когда с объектами начинают работать как с ОО-сущностями, а не как с кортежами и отношениями.

Z>Есть конкретные идеи — я всегда рад выслушать.


Ты их пока что успешно игнорируешь.

Z>P.S. Влад, извини, но мне кажется ты уже сложил негативное впечатление о проекте и пытаешься что-то доказать по инерции. Не задумываясь даже о чем идет речь. Если это так, давай сэкономим время друг друга, это здорово поможет нашим проектам.


Я тебе выдаю свои мысли. Делюсь совими знаниями и опытом. Ты же волен прислушаться или игнорировать.

Где-то ты споришь потому что просто не понимаешь меня (видимо мешает терминалогия). Но где-то ты откровенно не прав. Я тут поделать ничего не могу. Проект твой и все в твоих руках. Держать тебя за руку я не мугу (не хочу). Просто бобидно когда делаютс откровенно не верные дизайнерские решения. Вот я и высказываю свое мнение, чтобы попытаться предупредить такие просчеты. Услышишь — хорош. Нет — ничего не поделаешь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.