Сопоставление формулы 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 !!

Был бы признателен за любую помощь!


person Ondra Dvorak    schedule 09.05.2014    source источник


Ответы (2)


В 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

person Radim Köhler    schedule 10.05.2014

Большинство общих советов, данных Радимом Кёлером, верны, но что касается исходной проблемы формулирования формулы - 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 будет исходить из того, что они исходят от основного объекта, Человека, для которого вы определяете сопоставление. Иногда это может давать забавные результаты, но, скорее всего, в таблице Person нет столбцов Name или Street, и вы получите сообщение об ошибке от 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 и выберет из него улицу. Надеюсь, это всего одна улица, так как мы сопоставляем адреса по идентификатору. После нахождения улицы внешний подзапрос будет использовать ее для сопоставления с городом.

Мы также могли бы записать формулу как

        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 может встречаться в нескольких городах, поэтому у вас высока вероятность ошибки только потому, что вы сопоставляете адрес с городом по улице.

person quetzalcoatl    schedule 21.09.2018