Сопоставление формул FluentNhibernate с параметрами из объединенной таблицы
У меня проблема с отображением формулы FluentNhibernate. Мне нужно использовать столбец из объединенной таблицы в формуле.
Проблема демонстрируется в следующих трех таблицах: город, человек, адрес
class Person {
int PersonId { get; set; }
int AddressId { get; set; }
Address PersonAddress { get; set; }
string CityName { get; set; }
}
class Address {
int AddressId { get; set; }
string Street { get; set; }
}
class AddressMap<Address> {
Id(x => x.AddressId, "ADDRESS_ID");
Map(x => x.Street, "STREET");
}
class PersonMap<Person> {
Id(x => x.Id, "PERSON_ID");
References(x => x.PersonAddress).Column("ADDRESS_ID);
Map(x => x.CityName).Formula("select Name from City c where c.street = STREET");
// Doesn't work, STREET is a part of the joined table !
}
Любая идея, как правильно написать отображение формулы? Когда я переписываю отображение, используя значения, которые сгенерирует NHibernate, все будет работать, тем не менее, это довольно грязное решение:
Map(x => x.CityName).Formula("select Name from City c where c.street = address1_.STREET");
// Works !!
Буду признателен за любую помощь!
2 ответа
Большинство общих советов, представленных Радимом Келером, верны, но в отношении исходной проблемы формулирования формулы - NH (по крайней мере,>=5, я не знаю, как это с более старыми) должно быть в состоянии охватить этот случай.
Прежде всего, ваша попытка потерпит неудачу, потому что в формулах любые имена столбцов, относящиеся к чему-либо, кроме текущего основного объекта, должны иметь префикс с псевдонимами. В противном случае NH будет считать, что имя столбца относится к текущему основному объекту.
Например, ваша первоначальная попытка была:
Map(x => x.CityName).Formula("select Name from City c where c.street = STREET");
^ fault ^CEE ^CEE ^fault
Обратите внимание, как вы добавили c
префикс к street
однозначно, который street
из города, а какой нет. NHibernate обнаружит такие префиксы и будет предполагать, что все столбцы с префиксами привязаны к таблицам, указанным в тексте sql. Так, c.street
будет считаться не важным. Тем не мение, STREET
а также Name
нет префиксов NH будет предполагать, что они происходят от основного объекта, Человека, для которого вы определяете отображение. Это может иногда давать забавные результаты, но, скорее всего, нет Name
ни Street
столбцы таблицы Person, и вы получите ошибку от rdbms.
Чтобы это исправить, у вас должно быть что-то вроде:
Map(x => x.CityName).Formula("select c.Name from City c where c.street = a.STREET");
^CEE ^AYE
Конечно, теперь другая ошибка скажет, что префикс a
не известно Конечно, мы нигде не определили это, поскольку у нас есть PERSON и CITY, а промежуточный ADDRESS полностью отсутствует в запросе sql.
Теперь взгляните на это:
Map(x => x.CityName)
.Formula(@"
select c.Name
from City c
where c.street =
( select a.Street
from Address a
where a.address_id = address_id
)"); ^
^ no prefix!
С внутренней address_id
не имеет префикса, NH предположит, что это от Person. Отлично. Других столбцов без префикса не существует, поэтому все остальные столбцы игнорируются и предполагаются привязанными к областям, определенным в самом тексте SQL.
Таким образом, внутренний подзапрос выберет Адрес на основе Person.AddressID и выберет улицу из этого. Надеюсь, это всего лишь одна улица, так как мы сопоставляем адреса по ID. После нахождения улицы внешний подзапрос будет использовать ее для сопоставления с городом.
Мы также могли бы написать формулу как
select c.Name
from Address a
inner join City c no c.street = a.street
where a.address_id = address_id
с тем же эффектом и, вероятно, другой производительности.
Стоит отметить одну вещь, когда вы пишете подзапрос Формула или когда вы пишете where foo = (select x from...)
в подзапросе вы ДОЛЖНЫ убедиться, что подзапрос всегда возвращает ноль или один результат. Не два или больше. Многие РСУБД рассматривают такую вещь как ошибку. В случае этого запроса, то же самое street
имя может встречаться в нескольких городах, поэтому у вас высока вероятность неудачи только потому, что вы сопоставляете адрес с городом по улице.
В NHibernate нет прямого способа поддержать псевдоним другой объединенной таблицы. Зачем?
потому что эта объединенная таблица просто не должна быть частью объединения
Подумайте о лениво загруженном адресе, то есть не о части SELECT, или о некоторых проекциях без таблицы адресов.
Формула предназначена как умный или умный способ, как получить доступ к данным в текущей таблице. Или как создать какой-то независимый подвыбор, подзапрос.. и передать текущую строку id
в качестве эталонного фильтра.
Например, здесь, в сопоставлении свойств NHibernate Айенде, мы можем видеть:
<property name="CountOfPosts"
formula="(select count(*) from Posts where Posts.Id = Id)"/>
Генерация SELECT так:
SELECT ...
// the injected 'Id' is from current table
(select count(*) from Posts where Posts.Id = this_.Id)
FROM [MainTable] this_ // the alias of current table
Предложение: City
или название города может быть просто другой ссылкой (если это не напрямую строковое свойство адреса). Это позволит нам очень легко работать с ним (выбор, проекции, фильтрация, упорядочение по), и мы не будем зависеть от некоторых "скрытых, жестко закодированных" отображений. Это будет чистая модель:
Person.Address.City.Name