Здравствуйте, 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 или что-то на тему.
Здравствуйте, 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);
то я могу включить альтернативный синтаксис, не проблема, но мне все равно больше нравится:
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Ziaw, Вы писали:
Z>>Ага, а ассоциация с типом (Person как таблица так и тип модели) все же лучше :>.
VD>:> — это оператор используемый для приведения типов. Может быть путаница.
А Person это тип модели, мы объявляем фактически ссылку на него.
VD>А как это указывается в рельстах?
Когда я их юзал никак, просто делались поля нужного типа. На констрейнты они забивали.
Но сейчас вроде что-то появилось, хотя доки скудные:
Я так понимаю, первой строчкой создается колонка tag_id ссылающаяся на таблицу tags.
DSL там построен через API и такой синтаксис именно поэтому, я могу такой же сделать на экстеншенах, правда вместо символов будут строки. Но мой DSL мне пока нравится больше.
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, VladD2, Вы писали:
VD>>:> — это оператор используемый для приведения типов. Может быть путаница.
Z>Кстати я не перегружаю операторы, просто разбираю экспрешены внутри макросов. Если ты насчет этого беспокоишься.
Я говорю о смысловой перегрузке, т.е. о том как это дело будет воспринимать человек.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Какое отношение описание моделей имеет к синтаксису миграций?
VD>У предложенной схемы БД есть всего одна проблема. Вы можете иметь успешно нормализованную БД (см. Ресурсы), но теперь колонки таблицы рассинхронизированы с программной моделью. VD>Если заменить поле Airline полем AirlineId, то вы допустите «просачивание» деталей реализации (факта сохранения POGO в БД) в объектную модель. Джоэль Спольски называет это Законом дырявых абстракций (см. http://russian.joelonsoftware.com/Articles/LeakyAbstractions.html). VD>GORM помогает смягчить проблему дырявых абстракций, позволяя представить объектную модель способом, имеющим смысл для Groovy. Он скрыто занимается вопросами реляционной БД за вас, но, как вы сейчас увидите, можно легко переопределить настройки по умолчанию. GORM – это не непрозрачный слой, скрывающий от вас детали БД, а полупрозрачный – он пытается делать все правильно сам, но не встает у вас на пути, если вы захотите изменить его поведение. Это позволяет вам получить лучшее из двух миров.
Вобщем-то единственно полезный абзац. Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится. В рельсах другой подход, с ним я тоже знаком. Вдобавок там ActiveRecord, который я точно не собираюсь тащить в проект.
Здравствуйте, Ziaw, Вы писали:
Z>Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится.
Где это он заставляет о таком помнить?
Он заставляет помнить, что данные имеют реляционную природу и не являются полноценными ОО-данными. Но это не тоже самое что возиться с деталями.
Те же ассоциации сделаны именно для того, чтобы абстрагироваться от возиться с ключами и прочей СУБД-шной фигней.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
Z>>Этот подход мне не интересен, т.к. тулкит имеет обратную идеологию. Он заставляет помнить, что данные в БД и это мне нравится.
VD>Где это он заставляет о таком помнить?
У него нет лейзи. Он не умеет трекать изменения. Поэтому идеология "давайте спрячем AirplaneId и оставим только Airplane" в нем не прокатит. В гибернейте прокатила бы. Там можно создать lazy свойство Airplane и доступ к его айдишнику не заставит гибер лезть в базу, а к полям заставит. Тру абстракция. Когда дойдут руки до поддержки гибернейта ты сможешь это использовать. Если мы так сделаем в тулките — он просто не загрузит Airplane, это сделает работу с ассоциациями чертовски неудобной. И в любом случае никакого отношения к миграциям эти принципы не имеют.
VD>Он заставляет помнить, что данные имеют реляционную природу и не являются полноценными ОО-данными. Но это не тоже самое что возиться с деталями.
VD>Те же ассоциации сделаны именно для того, чтобы абстрагироваться от возиться с ключами и прочей СУБД-шной фигней.
Только базу все равно надо создавать, делать индексы и форинкеи. Для этого я и пишу DSL миграций. Чтобы абстрагироваться от конкретного диалекта DDL и специфических типов данных. Чтобы кратко и декларативно менять схему базы. Есть конкретные идеи — я всегда рад выслушать.
P.S. Влад, извини, но мне кажется ты уже сложил негативное впечатление о проекте и пытаешься что-то доказать по инерции. Не задумываясь даже о чем идет речь. Если это так, давай сэкономим время друг друга, это здорово поможет нашим проектам.
Здравствуйте, 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 входящими в первичный ключ.
Если прикладному программисту хочется, то он должен иметь возможность задать явные связи и явные ключи, но по-умолчанию его эти вопросы не должны колыхать. Он должен иметь просто описать ассоциации, а твой код должен создать все необходимые поля, ключи, и констрэйны автоматом.
Тоже самое пир удалении. Мы должны иметь возможность сказать, что удаляем ассоциацию, а библиотека сгенерирует код удаленя всех сопутствующих деталей реализации.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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>Тоже самое пир удалении. Мы должны иметь возможность сказать, что удаляем ассоциацию, а библиотека сгенерирует код удаленя всех сопутствующих деталей реализации.
дроп колонки, что может быть проще?
Я понимаю твою обеспокоенность тем, что ассоциации описывающиеся в модели слегка дублируют код миграций, но это только на первый взгляд. В рельсах сделано именно так.
Ты настойчиво пытаешься увидеть модель в миграциях, миграции это не модель. Это инструмент эволюционной разработки, который дает возможность постепенно вносить изменения в схему базы не теряя при этом данных. Можно плясать от модели, да миграции будут не нужны, но модификации модели будут приводить к неинтуитивным автоматическим действиям с БД. Мне эта особенность очень не нравится. Мне нравится указывать явно, все что я хочу сделать со своей базой для приведения ее в новое состояние.
Здравствуйте, 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
};
Ассоциации являются очень мощным средством, позволяющим с лёгкостью писать самые слож-ные запросы. Давайте попробуем написать запрос с многоуровневыми ассоциациями:
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. Влад, извини, но мне кажется ты уже сложил негативное впечатление о проекте и пытаешься что-то доказать по инерции. Не задумываясь даже о чем идет речь. Если это так, давай сэкономим время друг друга, это здорово поможет нашим проектам.
Я тебе выдаю свои мысли. Делюсь совими знаниями и опытом. Ты же волен прислушаться или игнорировать.
Где-то ты споришь потому что просто не понимаешь меня (видимо мешает терминалогия). Но где-то ты откровенно не прав. Я тут поделать ничего не могу. Проект твой и все в твоих руках. Держать тебя за руку я не мугу (не хочу). Просто бобидно когда делаютс откровенно не верные дизайнерские решения. Вот я и высказываю свое мнение, чтобы попытаться предупредить такие просчеты. Услышишь — хорош. Нет — ничего не поделаешь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Ziaw, Вы писали:
VD>>Я говорю о смысловой перегрузке, т.е. о том как это дело будет воспринимать человек.
Z>Я воспринимаю это так: тип данных PersonId имеет способы преобразования в Person.
Весьма странное восприятие. Тем более что PersonId не является типом данных, да и преобразования тут не происходит.
В общем, на мой взгляд, если ты уж упрашеся в детали построения БД, то нужно быть последовательным и описывать те самые PK/FK.
Ну, а лучше всего было бы (кроме этого детального описания) иметь более высокоуровневое и более абстрактное описание на уровне ассоциаций (реляционных) между сущностями. При этом все детали (колонки, ключи и т.п.) можно генирировать автоматом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Ziaw, Вы писали:
Z>Интересная идея. Я подумаю. Возможно я сделаю просто сокращенный синтаксис. Например так Z>
Z>create Test
Z>{
Z> id; // краткая запись для Id : Guid() = Sql.Newid;
Z> Text : string;
Z>}
Z>
Z>или так Z>
Z>create Test
Z>{
Z> noid; // если не указать это, то ключ будет сгенерирован
Z> Text : string;
Z>}
Z>
Для меня был было лучше если бы вообще не пришлось возиться с этим ID.
В 90% случаев (если не в 99%) ключевые поля, PK, FK и даже индексы строятся по одному прототипу — с введением суррогатного первичного ключа.
Только 1-10% таблиц получают составные первичные ключи или первичные ключи основанные на вводимых пользователями значениях. Вот для них я готов вводить информацию о PK.
А для FK в 100% случаев можно сгенерировать все автоматом. Ведь связи иделаются именно между таблицами. Если мы знаем все PK и тип связи, то без труда можем сгенерировать как и FK-пооля, и сами FK, и всю остальную обвязку. За одно можно (нужно!) генерировать и все аннотации для БЛТулкита.
Z>Ты путаешь описание модели и код миграций.
Скажем та. Мне вообще не очень нравится модель миграций и временами я сваливаюсь на мышление в терминах модельного проектирования.
Z>В коде миграций создают только внешние ключи.
Ну, как же это? Ты же вручную создаешь описание ключевых полей. Потом вручную добавляешь к ним индексы, связи и т.п.
Так почему не задавать те же ассоциации? Что этому препятствует?
Понятно, что в отличии от описания в модели у тебя будет императивный код создающий или удаляющий связи. Но все остальное можно генерировать автоматом.
Z>В описании модели уже можно создавать ассоциации, это будут отдельные макры. К миграциям отношения не имеющие. А в миграциях DSL должен прозрачно ложиться в DDL.
Ты говорил о DRY. Но то что ты сейчас говоришь — это то самое повторение себя! Мы один раз описываем связи (их создание, удаление или модификацию) в сктипте, а потом тоже самое делаем в модели.
Точнее даже еще хуже! Мы сначала описываем миграции которые в итоге порождают новые версии моделей, а потом мы еще раз описываем модели! Это явное нарушение принципа DRY.
Z>дроп колонки, что может быть проще?
А так оно и будет. Просто колонка ассоциативная. В случае связи многое-ко-мноким придется сделать удаление двух колонок.
Смотри:
drop Person.Addresses
и мне не надо думать ни о каких деталях (названиях полей отвечающих за эту связь).
В твоем же случае ты просто предоставляешь более низкоуровневый режим работы. Скажем если таблица на которую идет связь имеет составное ПК, нам придется делать "дроп" всех колонок входящих во внешний ключ.
Самое противное при этом то, что прикладной программист будет вынужден держать в голове множество мелких деталей и мыслить на менее абстрактном уровне.
Z>Я понимаю твою обеспокоенность тем, что ассоциации описывающиеся в модели слегка дублируют код миграций, но это только на первый взгляд.
Почему же это "только на первый взгляд"? Это так на любой взгляд.
Z>В рельсах сделано именно так.
Это не разу не аргумент.
Я тебе сразу сказал, что миграции в рельсах сделаны плохо. Задачу решают, но совершенно не декларативно и не защищают от дублирования и ошибок.
Z>Ты настойчиво пытаешься увидеть модель в миграциях, миграции это не модель.
Это потому что миграции меняют модель.
Z>Это инструмент эволюционной разработки, который дает возможность постепенно вносить изменения в схему базы не теряя при этом данных.
Это результат компромисса между декларативностью и надежностью с одной стороны, и гибкостью с другой. Причем компромисса далеко не сторону декларативности и надежности.
Z>Можно плясать от модели, да миграции будут не нужны, но модификации модели будут приводить к неинтуитивным автоматическим действиям с БД.
Про не интуитивный — это домыслы.
Но я сейчас о другом говорю. Раз уж ты выбрал идею императивно но высокоуровнево и абстрактно описывать модификацию БД (она же модель), то нужно постараться как можно лучше выдержать те самые принципы DRY которые ты так хорошо декларировал в своем роадмапе.
Если у нас есть понятие "ассоциация", то твоя миграция должна уметь создавать их и удалять. При этом если я добавил или изменил ассоциацию, то в модели это так же должно автоматически отразиться.
Ну, и конечно по меньше лишних деталей! Если я могу описать связь одной простой строкой, то не надо ее описывать двумя сложными (да и одной сложной тоже не надо).
Z> Мне эта особенность очень не нравится. Мне нравится указывать явно, все что я хочу сделать со своей базой для приведения ее в новое состояние.
Идея большей настраиваемости хороша сама по себе. Но только если эта идея не конфликтует с идеей большей простоты и более качественны абстракций.
Я не вижу проблем совместить эти идеи в одном флаконе. Ведь нет проблем позволить людям работать как на уровне ассоциаций, так и на уровне полей и ключей. В конце концов ассоциация просто будет приводить к автоматическому формированию этих самых полей и ключей, плюс еще что-то (атрибуты для БДТулкита и т.п.).
Так почему бы не сделать так чтобы было удобно людям и в том же время, чтобы в некоторых случаях можно было выключить автоматику и взять управление на себя?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>А для FK в 100% случаев можно сгенерировать все автоматом. Ведь связи иделаются именно между таблицами. Если мы знаем все PK и тип связи, то без труда можем сгенерировать как и FK-пооля, и сами FK, и всю остальную обвязку.
А что по твоему делает запись:
PersonId :> Person;
Она как раз генерит поле PersonId такого же типа как и PK в Person, заодно генерирует FK.
VD>За одно можно (нужно!) генерировать и все аннотации для БЛТулкита.
Брр... Аннтоации тулкиту генерируются во время компиляции модели. Миграции выполняются совершенно в другое время. Могут выполняться тыщу раз на разных базах, откатываться назад и т.п. Не должны они лезть в код модели.
Z>>В описании модели уже можно создавать ассоциации, это будут отдельные макры. К миграциям отношения не имеющие. А в миграциях DSL должен прозрачно ложиться в DDL.
VD>Ты говорил о DRY. Но то что ты сейчас говоришь — это то самое повторение себя! Мы один раз описываем связи (их создание, удаление или модификацию) в сктипте, а потом тоже самое делаем в модели.
VD>Точнее даже еще хуже! Мы сначала описываем миграции которые в итоге порождают новые версии моделей, а потом мы еще раз описываем модели! Это явное нарушение принципа DRY.
Нет, в миграциях мы указываем связи между таблицами, а в модели необходимые ассоциации между объектами. Они могут быть сильно разными.
VD>Самое противное при этом то, что прикладной программист будет вынужден держать в голове множество мелких деталей и мыслить на менее абстрактном уровне.
Да. Будет. Я не собираюсь скрывать эти детали. В первую очередь я делаю фреймворк для себя.
Z>>Я понимаю твою обеспокоенность тем, что ассоциации описывающиеся в модели слегка дублируют код миграций, но это только на первый взгляд.
VD>Почему же это "только на первый взгляд"? Это так на любой взгляд.
Потому, что FK бывает отражен в совершенно разные ассоциации. По ассоциации еще можно понять какой FK нужен, но писать ассоциации в миграциях совершенно не вариант, не должны они менять код модели, только базу.
VD>Я тебе сразу сказал, что миграции в рельсах сделаны плохо. Задачу решают, но совершенно не декларативно и не защищают от дублирования и ошибок.
Я понял только то, что тебе они не нравятся. Я считаю наоборот, для решения своей задачи они очень декларативны и защищают от множества ошибок деплоя.
VD>Это потому что миграции меняют модель.
Именно. Меняют модель в базе. Но не в коде.
Z>>Можно плясать от модели, да миграции будут не нужны, но модификации модели будут приводить к неинтуитивным автоматическим действиям с БД.
VD>Про не интуитивный — это домыслы.
Так развей их. Расскажи принципы работы и докажи интуитивность. Вот был у меня класс
class Test
{
mutable public F : string;
}
Я его исправил:
class Test
{
mutable public F1 : string;
mutable public F2 : string;
}
Что с будет делать твой инструмент с таблицой Test в которой есть записи?
VD>Ну, и конечно по меньше лишних деталей! Если я могу описать связь одной простой строкой, то не надо ее описывать двумя сложными (да и одной сложной тоже не надо).
Именно, только знания об ассоциациях все равно придется дублировать в модели, либо заводить еще одно хранилище для них.
VD>Я не вижу проблем совместить эти идеи в одном флаконе. Ведь нет проблем позволить людям работать как на уровне ассоциаций, так и на уровне полей и ключей. В конце концов ассоциация просто будет приводить к автоматическому формированию этих самых полей и ключей, плюс еще что-то (атрибуты для БДТулкита и т.п.).
Здравствуйте, VladD2, Вы писали:
Z>>В гибернейте прокатила бы. Там можно создать lazy свойство Airplane и доступ к его айдишнику не заставит гибер лезть в базу, а к полям заставит. Тру абстракция. Когда дойдут руки до поддержки гибернейта ты сможешь это использовать. Если мы так сделаем в тулките — он просто не загрузит Airplane, это сделает работу с ассоциациями чертовски неудобной. И в любом случае никакого отношения к миграциям эти принципы не имеют.
VD>У тебя каша в рассуждениях.
Никакой.
VD>Причем тут lazy?
При том, что без лейзи этот вариант похож на хождение на лыжах по льду. При создании или апдейте тебе придется оперировать именно объектом, для этого придется доставать его из базы. Оторвись от этих теоретических абстракций и напиши прикладуху мелкую, под MVC и тулкит. Сразу все встанет на свои места.
VD>То что ты описываешь Ассоциацию или даже свойство не значит, что через это свойство можно будет foreach-ем перебрать эти зависимые объекты, и что они автоматом закэшируются или будут подниматься лениво.
VD>Ассоциацию позволяют описать те же сущности (ключи, констрэйны и даже поля) в более сжатой форме. VD>Кроме того они позволяют в дальнейшем использовать в запросах более лаконичный стинтаксис обращения к свойствам вместо использования соеденений (join-ов).
Вот ты сам при работе с тулкитом оставляешь в объектах FK поля или указываешь их через ассоциации? Во всех примерх linq и тулкита эти поля есть. Чтобы создать новый объект мне не нужно доствать из базы Airplane, мне достаточно знать его код. А у тебя вот какая-то каша получается, если убрать FK поля.
VD>Вот что об ассоциациях пишет сам IT:
хъ
Извини, но читать еще раз то что я уже когда то читал я не буду. Есть более емкая мысль — излагай.
VD>Так что БЛТулкит поддерижвает все что нужно. Просто, кончено же, он поддерживает все это дело на более низком уровне.
VD>Делая аналог Рельс для Немерла было бы логично предоставить более высокуровневый вариант описания (на ряду с низкоуровневым) который позволил уменшить объем не нужной писанины и получить дополнительный уровень абстракции.
В жизни не подумаю убирать FK поля в тулките, это бред полнейший. Заяви на его форуме, что фк поля там это дырявые абстракции.
VD>И из этого следут, что нужно оябзательно описывать ее на самом низком уровне выставляя на показ все (даже не нужные) детали?
Какие детали конкретно не нужны?
VD>А вот это большой вопрос. Кракто у тебя не очень получается. Ты хочешь заставить людей описывать тучи деталей. Лично мне совершенно по барабану каке там в БД создадутся констрэйны, ключи и т.п. Мне нужно вырзить отношение между сущностями и чтобы потом я мог сделать выборку по этим отношениями или вставить данные учитывая имеющиеся ассоциации.
Отношения 1-1 и 1-* описываются ровно одной простой строкой. Насчет *-* я еще подумаю.
VD>Рельсы и Грелься как раз предлагают такой путь. По умолчанию мы описываем все максимально просто и высокоуровнево, но если захотим, то можем спуститься на уровень ниже и описать конеретные детали.
VD>Так и надо поступать.
Да я вроде полностью скопировал идеологию рельсовых миграций, чего ты еще от меня хочешь?
VD>Проблемы Рельсов возникают отнюдь не во время описания миграций или эктив-рекордов. Они возникают когда с объектами начинают работать как с ОО-сущностями, а не как с кортежами и отношениями.
От этого я и пытаюсь избавиться. А ты зачем-то киваешь на статьи про дырявые абстракции и максимальное сокрытие деталей БД.
VD>Ты их пока что успешно игнорируешь.
Да ладно, все что более менее вменяемо я беру в расчет.
VD>Я тебе выдаю свои мысли. Делюсь совими знаниями и опытом. Ты же волен прислушаться или игнорировать.
VD>Где-то ты споришь потому что просто не понимаешь меня (видимо мешает терминалогия). Но где-то ты откровенно не прав. Я тут поделать ничего не могу. Проект твой и все в твоих руках. Держать тебя за руку я не мугу (не хочу). Просто бобидно когда делаютс откровенно не верные дизайнерские решения. Вот я и высказываю свое мнение, чтобы попытаться предупредить такие просчеты. Услышишь — хорош. Нет — ничего не поделаешь.
Большинство неверных решений тебе почудилось. Большинство своих предложений ты не желаешь хоть чуть детализировать. Поскольку мы смотрим с разных точек зрения я не могу ловить твои мысли на лету.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Ziaw, Вы писали:
VD>>>Я говорю о смысловой перегрузке, т.е. о том как это дело будет воспринимать человек.
Z>>Я воспринимаю это так: тип данных PersonId имеет способы преобразования в Person.
VD>Весьма странное восприятие. Тем более что PersonId не является типом данных, да и преобразования тут не происходит.
Тип данных [колонки] PersonId имеет способы преобразования в Person. Т.е. я могу произвести взаимно однозначное соответствие между значением PersionId и объектом типа Persion.
Здравствуйте, Ziaw, Вы писали:
Z>Тип данных [колонки] PersonId имеет способы преобразования в Person. Т.е. я могу произвести взаимно однозначное соответствие между значением PersionId и объектом типа Persion.
Для меня как дизайнера модели данных (БД) это все лишнее. Я бы желал просто задать ассоциацию между сущностями и пользоваться результатами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.